← SCRAM AI Lab

Claude Code

Construye tu primer MCP server: el patrón real

Stdio sigue siendo la mejor opción para MCPs propios. HTTP solo si necesitas multi-cliente o auth. Una herramienta, un caso de uso atómico, schema Zod estricto.

May 21, 2026

9 lecturas

Stdio gana por default

Cuando empiezas con MCP, la documentación oficial te empuja a HTTP/SSE como si fuera lo moderno. Para un MCP propio que vas a usar tú o tu equipo desde Claude Code, stdio es superior: arranca instantáneo (sin servidor escuchando), no expone puertos, hereda el entorno del proceso padre (variables, credenciales del shell), y debugear es console.error que aparece en los logs de Claude. HTTP tiene sentido en exactamente dos escenarios: (a) varios clientes consumen el mismo MCP simultáneamente, o (b) necesitas autenticación OAuth con flujo de redirect. Para todo lo demás, stdio.

El patrón mínimo en TypeScript

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";

const server = new Server(
  { name: "scram-erp-mcp", version: "0.3.1" },
  { capabilities: { tools: {} } }
);

const QueryClienteSchema = z.object({
  rfc: z.string().regex(/^[A-Z&Ñ]{3,4}\d{6}[A-Z0-9]{3}$/),
});

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [{
    name: "lookup_cliente_by_rfc",
    description: "Busca un cliente del ERP por RFC. Devuelve nombre, status, último pedido. Use cuando el usuario mencione un RFC mexicano.",
    inputSchema: { type: "object", properties: { rfc: { type: "string" } }, required: ["rfc"] }
  }]
}));

server.setRequestHandler(CallToolRequestSchema, async (req) => {
  if (req.params.name !== "lookup_cliente_by_rfc") {
    throw new Error("Unknown tool: " + req.params.name);
  }
  const { rfc } = QueryClienteSchema.parse(req.params.arguments);
  const cliente = await fetchClienteFromERP(rfc);
  return { content: [{ type: "text", text: JSON.stringify(cliente, null, 2) }] };
});

const transport = new StdioServerTransport();
await server.connect(transport);
console.error("scram-erp-mcp ready");

Una herramienta, un caso de uso atómico

La tentación es exponer query_erp con un parámetro action que multiplexa todo. No lo hagas. El modelo elige herramientas por nombre y descripción; si las colapsas en una sola, el clasificador pierde resolución y vas a debugear por qué Claude invoca acciones equivocadas. Mejor cinco herramientas con nombres explícitos: lookup_cliente_by_rfc, list_pedidos_pendientes, get_inventario_sku, create_cotizacion, find_factura_by_folio. Cada una con su schema Zod.

Error handling que sirve al modelo

Los errores genéricos rompen al modelo. Si tu MCP responde "Internal server error" cuando un RFC no existe, Claude no sabe si reintentar, pedir aclaración o rendirse. Devuelve errores estructurados con intención:

return {
  content: [{
    type: "text",
    text: JSON.stringify({
      error: "cliente_not_found",
      rfc: rfc,
      hint: "Verifica el RFC. Si el cliente es nuevo, usa create_cliente primero."
    })
  }],
  isError: true
};

El campo hint es oro: le dice al modelo qué hacer después sin que tengas que entrenarlo en el system prompt.

Instrumentación desde el día uno

Loguea a stderr (stdout es para el protocolo MCP): timestamp, nombre de herramienta, latencia, código de salida. En producción enchúfalo a Loki. Sin esto, cuando alguien diga "el MCP está lento" no vas a tener cómo distinguir si el problema es tu MCP, el ERP, el modelo o la red.

Anti-patrones que vas a cometer

  • Devolver objetos enormes. Si el ERP regresa 200 campos por cliente, el modelo solo necesita 8. Filtra en el servidor.
  • No timeoutear llamadas externas. fetch sin AbortController con timeout de 5-10s te deja MCPs colgados y conversaciones muertas.
  • Aceptar input sin validar. Zod no es opcional; es la única defensa entre el modelo y tu base de datos.

Publica tu MCP cuando funcione, no cuando esté perfecto. La primera versión que uses tres días seguidos te va a enseñar más sobre el diseño que dos semanas planeándolo. ¿Qué herramienta de tu día a día te ahorraría más tiempo si Claude pudiera invocarla?

mcp
tutorial
claude-code
← Volver a SCRAM AI Lab