empleado.uno
← Todas las integraciones

MCP Integration Guide

Cómo exponer las herramientas de tus sistemas para que el agente las use de forma predecible, con esquemas, permisos y errores bien definidos.

v1.1.1 · actualizado el 2026-07-04

Ver markdown crudo

MCP Integration Guide

Audience: AI coding agents and developers building a Model Context Protocol
(MCP) server that will be connected to this platform’s AI agents. If you were handed this URL and
asked to “build an MCP integration,” this is your authoritative, platform-specific spec.

This platform lets each tenant connect remote MCP servers so their AI agents gain extra tools
(look up an order, create a ticket, check inventory, …). The quality of that integration depends
entirely on how your server names tools, describes them, shapes their inputs, and returns
results.
Follow this guide and your server will plug in cleanly and be used deterministically
the agent will pick the right tool, with the right arguments, at the right time.

If you are an AI assistant generating the server: read the whole document first, then scaffold from
Section 5 and adapt.

1. What this is

A checklist-driven contract for a remote MCP server that this platform can consume. You expose
tools over MCP; the platform discovers them with tools/list, lets the tenant enable the ones they
want, and calls them with tools/call while an agent is conversing with an end user.

You do not need to know anything about this platform’s internals. You only need to:

  1. Serve MCP over a public HTTPS endpoint.
  2. Name and describe each tool well.
  3. Provide a strict input schema for each tool.
  4. Mark which tools read vs. which tools mutate.
  5. Return small, clean, actionable results.

2. Platform requirements

Hard requirements — a server that violates these cannot be connected:

  • HTTPS only. The endpoint URL must be https://…. Plain HTTP is rejected.
  • Transport: Streamable HTTP (recommended) or SSE. Use the official MCP server SDK so you get a
    compliant transport for free.
  • Public host. For SSRF safety the platform refuses private, loopback, and link-local addresses
    (e.g. localhost, 127.0.0.1, 10.x, 192.168.x, 169.254.x, ::1). Deploy somewhere with a
    public hostname.
  • Implement tools/list and tools/call. tools/list must return every tool with a
    name, a description, and an inputSchema. tools/call executes one tool.
  • Authentication (pick one): none · Authorization: Bearer <token> · API key in a custom header ·
    arbitrary custom header(s) · OAuth 2.0. Credentials are entered by the tenant at connect time and
    stored encrypted; your server just has to require them.
  • Keep payloads compact. The platform records invocations for auditing and truncates request and
    response bodies at ~32 KB. Large blobs are wasteful and get cut off — return references, not dumps.

Tool name length & namespacing

The platform namespaces your tool as mcp_<server>_<tool> and truncates the whole thing to 64
characters
(provider limit). Keep tool names short and make them distinct in their first ~20
characters
so they don’t collide after truncation.

3. Designing tools for deterministic use

This is the part that determines whether the agent uses your tools correctly. The agent decides
which tool to call and how almost entirely from the name, the description, and the
inputSchema you publish. Treat them as the API contract with the model.

3.1 Naming

  • Lowercase snake_case, verb-first, specific: get_order_status, create_support_ticket,
    search_inventory.
  • Avoid vague names (run, do, handle, query) and avoid two tools whose names only differ
    after character ~20 (see truncation note above).

3.2 Descriptions

  • One or two sentences of action-oriented prose: “Looks up the current status and ETA of an
    order by its ID.”
  • Describe what it does and when it’s useful, in plain language. The model infers applicability
    from this text.
  • Do not write procedural conditions like “only call this when the user is angry” or “call
    this after calling X”
    — the model does not reliably follow embedded control flow, and it makes the
    tool brittle. Describe capability, not orchestration.
  • Don’t restate the schema in prose; the schema already carries parameter detail.

3.3 Input schema (JSON Schema)

  • Always provide inputSchema with type: "object", properties, and an explicit required array.
  • Give every property a description. Put units, formats, and defaults in that description
    (“ISO 8601 date”, “defaults to 20”, “3-letter currency code”).
  • Use enum for closed sets so the model can’t invent values.
  • Prefer a few well-described parameters over one free-form query string.
  • Make truly-required things required; make everything else optional with a sane server-side
    default. Missing-but-required arguments cause failed calls and frustrated agents.

3.4 Read vs. write — set the hints

The platform default-denies destructive tools: on sync it auto-disables any tool whose name
matches a danger heuristic (delete | remove | drop | destroy | purge | truncate | wipe | revoke | cancel | terminate | uninstall | reset | deactivate) so a human must opt in. You control this
explicitly with annotations:

  • Add annotations.readOnlyHint: true to every tool that only reads. These can be enabled freely.
  • Add annotations.destructiveHint: true to every tool that mutates or deletes. These arrive
    disabled; the tenant turns them on deliberately.

Setting these correctly means safe tools are available immediately and dangerous ones are gated —
exactly the behavior you want.

3.5 Result shape

  • Return only what the agent needs to answer the user. Strip internal metadata — similarity
    scores, raw pagination cursors, opaque database IDs, debug fields.
  • Prefer short, human-readable strings the model can quote back. Round numbers; format dates.
  • Keep results small (remember the ~32 KB audit truncation). For big datasets, return the top few
    items plus a count, not everything.

3.6 Errors

  • Return clear, actionable error messages: “order_id not found”, “date must be in the future”.
    The model relays these to the user or self-corrects.
  • Never put secrets, tokens, or credentials in error text. The platform redacts known secret
    values from audit logs as a backstop, but do not rely on it — don’t emit them in the first place.

3.7 Latency & idempotency

  • Respond quickly. Tool calls happen inside a live conversation; slow tools degrade the experience
    and can trigger upstream retries.
  • Reads must be side-effect-free. Make writes idempotent where you can (safe to retry).

4. Auth & security checklist

  • Serve over HTTPS with a valid certificate.
  • Require authentication for anything non-public; follow least privilege for the credential the
    tenant will provision.
  • Support credential rotation — the tenant can re-enter credentials; don’t hard-pin one secret.
  • Validate and sanitize all tool inputs on your side. Treat tool arguments as untrusted.
  • Never log or return secrets. Scope each token to only the tools/data it needs.
  • The tenant’s credential is stored encrypted at rest on this platform and sent to your server
    only on each call — your job is to verify it and fail closed if it’s missing or invalid.

4.1 Private artifacts from chat media

When an end user sends an image, audio, document, or video and the tenant enables the agent skill
private_storage, the platform stores the file in a private bucket and calls your MCP tool with
short-lived signed references, not inline blobs.

Declare the media parameter your tool needs in inputSchema. The platform fills that declared
argument when a matching private artifact is available, and also includes _context.artifacts as
trusted metadata for correlation/backward compatibility. Do not rely on _context.artifacts as
the primary payload contract.

For example, if your tool declares an image URL:

{
  "type": "object",
  "properties": {
    "imageUrl": {
      "type": "string",
      "format": "uri",
      "description": "Short-lived signed URL for the image to inspect."
    }
  },
  "required": ["imageUrl"]
}

The platform calls tools/call with arguments like:

{
  "imageUrl": "https://storage.example/signed-url",
  "_context": {
    "source": "WHATSAPP",
    "externalId": "wamid.example",
    "artifacts": [
      {
        "id": "artifact_abc123",
        "kind": "document",
        "mimeType": "application/pdf",
        "filename": "quote.pdf",
        "sizeBytes": 18422,
        "sha256": "64-hex-character-checksum",
        "url": "https://storage.example/signed-url",
        "expiresAt": "2026-07-03T18:10:00.000Z",
        "delivery": "signed_url"
      }
    ]
  }
}

Supported private artifact argument shapes:

  • Kind-specific URL fields such as imageUrl, audioUrl, documentUrl, or videoUrl
    (camelCase or snake_case) with type: "string" and format: "uri".
  • A descriptor object field such as artifact, file, media, or attachment.
  • An array field such as artifacts, files, media, or attachments when your tool can
    receive multiple descriptors.

Integration rules:

  • Do not ask the agent to paste base64 into tool arguments. Fetch the url before expiresAt.
  • Treat the signed URL as a secret. Do not log it, store it permanently, or return it to the user.
  • Validate kind, mimeType, sizeBytes, and sha256 before persisting the file in your system.
  • If your tool schema is strict, allow the media parameter you declare plus an optional _context
    object. The platform injects _context server-side, so client-provided spoofed context is
    overwritten.
  • Return a concise result after processing the artifact, for example: “invoice saved as INV-123”.

5. Minimal MCP server (copy-paste)

A minimal TypeScript server using @modelcontextprotocol/sdk with one read-only tool and one
destructive tool, each with a strict schema and the correct annotation. Clone, adapt the handlers
to your backend, deploy behind HTTPS.

// server.ts —  npm i @modelcontextprotocol/sdk zod
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
import { z } from 'zod'

const server = new McpServer({ name: 'acme-orders', version: '1.0.0' })

// READ — safe, enabled immediately.
server.registerTool(
  'get_order_status',
  {
    title: 'Get order status',
    description: 'Looks up the current status and ETA of an order by its ID.',
    inputSchema: { order_id: z.string().describe('The order ID, e.g. "ORD-10293".') },
    annotations: { readOnlyHint: true },
  },
  async ({ order_id }) => {
    const order = await lookupOrder(order_id) // your backend
    if (!order) return { content: [{ type: 'text', text: 'order_id not found' }], isError: true }
    return {
      content: [{ type: 'text', text: `Order ${order_id}: ${order.status}, ETA ${order.eta}.` }],
    }
  },
)

// WRITE — destructive, arrives DISABLED; tenant opts in.
server.registerTool(
  'cancel_order',
  {
    title: 'Cancel order',
    description: 'Cancels an order that has not yet shipped.',
    inputSchema: {
      order_id: z.string().describe('The order ID to cancel.'),
      reason: z.string().optional().describe('Optional cancellation reason.'),
    },
    annotations: { destructiveHint: true },
  },
  async ({ order_id, reason }) => {
    const res = await cancelOrder(order_id, reason) // your backend
    return { content: [{ type: 'text', text: res.ok ? `Order ${order_id} cancelled.` : res.error }] }
  },
)

// Streamable HTTP transport over HTTPS (terminate TLS at your host / proxy).
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined })
await server.connect(transport)
// Wire `transport` to your HTTP framework's POST handler per the SDK docs.

Notes:

  • registerTool publishes name, description, and inputSchema via tools/list automatically.
  • The Zod .describe(...) calls become the per-property descriptions the agent reads.
  • Return errors as text with isError: true — clear and actionable, never leaking internals.

6. Self-check before you share your URL

Run through this before handing the endpoint to a tenant:

  • [ ] Endpoint is https:// on a public host; tools/list returns successfully.
  • [ ] Every tool has a clear, action-oriented description.
  • [ ] Every tool has an inputSchema with an explicit required array and a description on each
    property.
  • [ ] Read tools set annotations.readOnlyHint: true; write/delete tools set
    annotations.destructiveHint: true.
  • [ ] Tool names are short, distinct in their first ~20 chars, and snake_case.
  • [ ] Results are small and free of internal metadata; errors are actionable and secret-free.
  • [ ] Authentication is required where appropriate and credentials are validated server-side.

7. How this platform consumes your tools

So you know what happens after you connect:

  1. The tenant adds your server (URL + credentials) and the platform calls tools/list.
  2. Tools are stored as an allowlist: enabled tools can be used; destructive tools start
    disabled
    until a human enables them.
  3. Each AI agent then selects which of the enabled tools it may use. A tool is callable only if
    it is (a) enabled on the tenant allowlist and (b) selected on the agent and © the
    connection is active.
  4. During a conversation the agent calls tools/call with arguments matching your inputSchema.
    If it ever names a tool that isn’t available, the call fails fast — it cannot invent tools.
  5. Every invocation is audited (arguments, result, duration, success) with secrets redacted and
    bodies truncated at ~32 KB.

That’s the whole contract. Build to this guide and your integration will be picked up, gated
safely, and used deterministically.