Convertir una API en servidor MCP: REST y OpenAPI paso a paso
Envuelve una API REST existente como servidor MCP que los agentes puedan llamar sin adivinar. Genera herramientas desde un archivo OpenAPI con FastMCP, créalas a mano en TypeScript, gestiona la autenticación y los trabajos lentos de imagen o video, y después prueba con el Inspector y publica por stdio o HTTP.
Tu API REST ya funciona, pero los agentes siguen equivocándose al usarla. Adivinan los nombres de los parámetros, se atascan con respuestas JSON de 40 KB y llaman a DELETE cuando querían llamar a GET. La solución no es un modelo más inteligente. Es una capa intermedia ligera: un servidor MCP que le indica al agente exactamente qué acciones existen, cómo es la entrada de cada una y qué devuelve. Este tutorial muestra cómo convertir una API en servidor MCP a partir de su especificación OpenAPI, primero con código generado y después a mano, incluyendo autenticación, trabajos lentos, pruebas y despliegue.
Necesitas tres cosas antes de empezar: una API a la que ya puedas llamar con curl, su archivo OpenAPI 3.x (o la paciencia para escribirlo) y un cliente MCP como Claude Desktop, Cursor o VS Code para probar el resultado. Una primera versión funcional lleva una tarde. El pulido es donde se va el tiempo de verdad, y también es de donde sale la calidad.
💡 Versión corta: MCP envuelve tu API en herramientas. Cada herramienta tiene un nombre, una descripción y un JSON Schema para su entrada. El agente elige herramientas leyendo esas descripciones, así que las descripciones importan más que la fontanería HTTP.
Por qué envolver una API como MCP
REST se diseñó para desarrolladores que leen la documentación una vez y escriben código sobre ella. Un agente funciona de otra manera. Lee lo que el servidor lista al inicio de una sesión y decide qué llamada hacer solo a partir de esa lista. Si la lista es vaga, adivina. Si la lista es enorme, consume su ventana de contexto antes de que el usuario haya escrito nada.
Un servidor MCP resuelve los dos problemas como lo haría una operadora de centralita: recibe una petición clara, la dirige a la línea correcta y devuelve una respuesta limpia.
Lo que el agente ve realmente
Cuando un cliente se conecta, pide al servidor su lista de herramientas. Cada entrada incluye un name, una description, un inputSchema escrito en JSON Schema y, opcionalmente, un outputSchema y un conjunto de annotations. Esa es toda la superficie. El agente nunca ve tus rutas, tus verbos HTTP ni tus códigos de estado. Ve nombres, frases y esquemas.
De REST a MCP de un vistazo
Cada parte de una operación de OpenAPI tiene su equivalente en el lado de MCP:
REST / OpenAPI
Herramienta MCP
operationId
Herramienta name
summary y description
Herramienta description
Parámetros de ruta, de consulta y del cuerpo
inputSchema, un único objeto JSON Schema plano
Esquema de respuesta 200
outputSchema y contenido estructurado
Respuestas 4xx y 5xx
Resultado con isError: true y un mensaje legible
Esquema de seguridad
Configuración del servidor: token en variable de entorno, o OAuth para servidores remotos
Enlaces de paginación
Entradas explícitas cursor y limit
El contenido estructurado y los esquemas de salida llegaron con la revisión 2025-06-18 de la especificación, así que comprueba que tu versión del SDK los soporte antes de depender de outputSchema.
Asignar operaciones de OpenAPI a herramientas
Abre la especificación y resiste la tentación de exponerlo todo. Una API de 120 endpoints se convierte en un servidor de 120 herramientas, y solo la lista de herramientas puede consumir miles de tokens en cada conversación. Empieza poco a poco, pon buenos nombres y describe cada cosa como lo haría un compañero de trabajo.
Elige operaciones, no endpoints
Hazle cuatro preguntas a cada endpoint antes de convertirlo en herramienta:
¿Le pediría una persona a un asistente que haga esto en lenguaje natural?
¿Es seguro llamarlo dos veces si el agente reintenta?
¿La respuesta cabe en unos pocos kilobytes, o puedes recortarla hasta que quepa?
¿Pertenece a otro público, como administración, facturación o herramientas internas?
Lo que falle en la primera o en la última pregunta se queda fuera. Cinco a diez herramientas bien elegidas superan a cien en bruto. Los flujos de varios pasos merecen una sola herramienta: si "crear un carrito, añadir artículos y pagar" siempre ocurre en secuencia, el agente debería ver una única acción place_order.
Nombra y describe cada herramienta
Parte de operationId y reescríbelo como un verbo y un sustantivo. Una buena descripción responde a tres preguntas: qué hace la herramienta, cuándo usarla en lugar de sus vecinas y qué devuelve.
Generado
Reescrito
Nombre
OrdersController_findAll
search_orders
Descripción
"Find all"
"Busca pedidos por correo del cliente, estado o rango de fechas. Devuelve hasta 20 pedidos con id, estado y total. Usa get_order para las líneas del pedido."
Aplana los parámetros de ruta, de consulta y del cuerpo en un único objeto. Mantén los enums, marca los campos obligatorios, da a cada propiedad una descripción breve con un valor de ejemplo y establece límites como maximum y maxLength para que el modelo no pida 10.000 filas. Una operación de OpenAPI como esta:
{
"name": "get_order",
"description": "Fetch one order by id. Returns status, total and line items. Use search_orders when you only have an email.",
"inputSchema": {
"type": "object",
"properties": {
"order_id": { "type": "string", "description": "Order id, for example ord_8f2c1" }
},
"required": ["order_id"]
}
}
Dos formas de construir el servidor
Puedes generar un servidor directamente desde la especificación en minutos, o escribir cada herramienta a mano. La mayoría de los equipos hacen ambas cosas: generan primero para ver la forma general y después afinan a mano las cinco herramientas que importan.
Generar con FastMCP
La biblioteca de Python FastMCP puede construir un servidor directamente desde un documento OpenAPI:
El servidor lee la especificación, crea las herramientas y reenvía cada llamada a través del cliente httpx que le pases, que también es donde vive la cabecera de autenticación. Los mapas de rutas eliminan las rutas de administración e internas antes de que el agente las vea.
⚠️ Comprobación de versión: el mapeo por defecto cambia entre versiones principales de FastMCP. Las versiones recientes convierten cada operación en una herramienta, mientras que las 2.x más antiguas asignaban algunas rutas GET a recursos. Fija tu versión, define los mapas de rutas de forma explícita y confirma la ruta de importación en la documentación de la versión que instalaste.
Cuando la generación se queda corta
La propia documentación de FastMCP advierte de que los servidores curados dan a los modelos resultados notablemente mejores que los convertidos automáticamente, sobre todo en APIs con muchos endpoints y parámetros. Lo verás en la primera prueba:
Nombres como get_orders_by_id_using_get que ningún humano escribiría
Descripciones copiadas de la documentación para desarrolladores, escritas para lectores que ya conocen el sistema
Respuestas que devuelven todos los campos, incluidas las marcas internas
Cuatro herramientas que debían haber sido una
Arréglalas en este orden: podar, renombrar, reescribir descripciones, recortar respuestas, unir flujos.
Construir a mano en TypeScript
Para las herramientas que importan, el SDK oficial de TypeScript te da control total. Instala @modelcontextprotocol/sdk y zod, y después registra cada herramienta con un esquema y un manejador:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const API = "https://api.example.com";
const TOKEN = process.env.ORDERS_API_TOKEN;
const server = new McpServer({ name: "orders", version: "1.0.0" });
server.registerTool(
"get_order",
{
title: "Get order",
description:
"Fetch one order by id. Returns status, total and line items. Use search_orders when you only have an email.",
inputSchema: { order_id: z.string().describe("Order id, for example ord_8f2c1") },
annotations: { readOnlyHint: true },
},
async ({ order_id }) => {
const res = await fetch(`${API}/orders/${encodeURIComponent(order_id)}`, {
headers: { Authorization: `Bearer ${TOKEN}` },
});
if (!res.ok) {
return {
isError: true,
content: [{ type: "text", text: `Orders API returned ${res.status}. Check the id and try again.` }],
};
}
const order = await res.json();
return { content: [{ type: "text", text: JSON.stringify(order) }] };
}
);
await server.connect(new StdioServerTransport());
Dos detalles hacen el trabajo pesado. La anotación readOnlyHint indica al cliente que esta llamada es segura de ejecutar sin pedir confirmación, y la rama de error devuelve isError: true con un mensaje legible, para que el agente pueda reintentar en lugar de quedarse parado.
Autenticación, secretos y salvaguardas
El servidor guarda las credenciales. El modelo nunca las tiene.
Mantén los tokens fuera de los prompts
Lee el token de la API desde una variable de entorno o un gestor de secretos cuando arranque el proceso. Nunca lo aceptes como argumento de una herramienta, nunca lo repitas en un mensaje de error y nunca registres las cabeceras de las peticiones. Crea el token más restrictivo que permita la API: un token de solo lectura para un servidor de solo lectura. En servidores remotos, el flujo de autorización de MCP se basa en OAuth 2.1, así que cada usuario inicia sesión con su propia cuenta y cada llamada lleva sus propios permisos en lugar de un superusuario compartido.
Anota las herramientas arriesgadas
Las anotaciones son pistas que ayudan a los clientes a decidir cuándo pedir confirmación al usuario:
Anotación
Actívala cuando
readOnlyHint: true
La herramienta solo lee, como un GET o una búsqueda
destructiveHint: true
La herramienta borra o sobrescribe datos
idempotentHint: true
Repetir la llamada con la misma entrada no cambia nada más
openWorldHint: true
La herramienta accede a sistemas ajenos a los tuyos, como la web abierta
Trátalas como pistas, no como garantías, porque un cliente no debería fiarse de las anotaciones de un servidor que no conoce. La protección real está de tu lado: publica la primera versión de solo lectura, añade las herramientas de escritura de una en una y dale a las destructivas una entrada dry_run o confirm para que el agente tenga que ser explícito.
Trabajos lentos, sondeo y medios
La generación de imágenes, el render de video y la exportación de informes comparten un patrón: la API responde al instante con un id de trabajo, y el resultado llega segundos o minutos después. Una herramienta que se bloquea durante tres minutos agotará el tiempo de espera en la mayoría de los clientes. Una barra de comandas en la cocina resuelve el mismo problema en un restaurante: tomas el pedido, entregas un ticket y avisas con el número cuando el plato está listo.
Crear, consultar, obtener
Divide el trabajo en tres herramientas: una lo inicia, otra consulta su estado y otra lo cancela. La herramienta de inicio devuelve un id y una indicación de cuándo volver a consultar. La herramienta de consulta devuelve un objeto de estado pequeño, queued, running, succeeded o failed, más una URL en cuanto haya algo que obtener. Devuelve enlaces, no bytes de archivos: una imagen de 5 MB pegada en el contexto no le sirve a nadie.
server.registerTool(
"get_render",
{
description:
"Check a render started with start_render. Call again after next_poll_in_seconds until status is succeeded or failed.",
inputSchema: { render_id: z.string() },
annotations: { readOnlyHint: true },
},
async ({ render_id }) => {
const job = await api(`/renders/${render_id}`); // api() is your fetch helper
const done = job.status === "succeeded" || job.status === "failed";
const body = {
status: job.status,
url: job.output?.[0] ?? null,
next_poll_in_seconds: done ? null : 5,
};
return { content: [{ type: "text", text: JSON.stringify(body) }] };
}
);
Un ejemplo real de imagen y video
El conector propio de PicassoIA sigue este diseño. Sus herramientas generate_image, edit_image, generate_video_picassoia y generate_video_seedance devuelven un predict_id en cuanto una GPU acepta el trabajo, junto con un tiempo estimado. Después, el agente llama a get_generation con el next_poll_in_seconds devuelto y repite hasta que el estado sea succeeded o failed. Una herramienta cancel_generation detiene un trabajo en curso, con una advertencia honesta en sus instrucciones: un video que la GPU ya está renderizando ya no se puede cancelar.
Debajo hay una API REST de estilo Replicate en https://api.picassoia.com/v1 con autenticación por token bearer. POST /v1/models/{owner}/{name}/predictions crea un trabajo, GET /v1/predictions/{id} lo lee y POST /v1/predictions/{id}/cancel lo detiene. Eso la convierte en un objetivo de conversión de manual, y los cuatro modelos detrás del conector se corresponden con sus cuatro herramientas de generación:
Los límites también deben aparecer en las descripciones de las herramientas. La API permite 5 predicciones simultáneas por cuenta, compartidas entre tokens y conexiones MCP, con prompts de hasta 4.000 caracteres, así que una buena descripción le indica al agente que espere a que termine un trabajo en curso antes de lanzar un sexto. Revisa las condiciones actuales del plan en el sitio de PicassoIA antes de construir un producto sobre la API.
Probar y luego publicar
Un agente es un probador implacable: usa tus herramientas de formas que no habías previsto. Prueba con herramientas adecuadas antes de entregárselo.
Ejecuta el MCP Inspector
El MCP Inspector es la interfaz oficial de depuración. Apúntalo a tu servidor, por ejemplo npx @modelcontextprotocol/inspector node dist/server.js, y lista todas las herramientas, permite llamar a cada una con JSON en bruto y muestra el resultado exacto que recibiría un cliente. Después conecta un cliente real y ejecuta diez prompts realistas. En cada uno, comprueba tres cosas: ¿eligió el agente la herramienta correcta?, ¿rellenó bien los argumentos? y ¿la respuesta le dio lo necesario para contestar?
Elige stdio o HTTP
stdio
HTTP en streaming
Funciona como
Proceso local iniciado por el cliente
Servicio remoto detrás de una URL
Autenticación
Variables de entorno en el equipo del usuario
OAuth o tokens bearer
Ideal para
Herramientas personales y desarrollo
Equipos y APIs compartidas
Cuidado con
No imprimir nunca logs en stdout
TLS, límites de tasa, escalado horizontal
HTTP en streaming reemplazó al antiguo transporte HTTP más SSE en la revisión 2025-03-26 de la especificación, y el protocolo sigue cambiando, así que fija la versión de tu SDK y lee su registro de cambios antes de actualizar.
Cinco errores comunes
Exponer todos los endpoints. Las listas de herramientas cuestan tokens en cada turno.
Escribir logs en stdout en un servidor stdio. Stdout transporta el propio protocolo. Envía los logs a stderr.
Devolver la carga útil completa del origen. Recorta a los campos que el agente necesita y pagina el resto.
Lanzar excepciones. Devuelve un resultado isError con un mensaje que indique qué probar a continuación.
Descripciones que se solapan. Si dos herramientas suenan parecido, el agente tira una moneda al aire. Indica cuándo preferir cada una.
Escribir veinte descripciones de herramientas a mano es tedioso, y un modelo de lenguaje (LLM) hace bien el trabajo si le das reglas. En PicassoIA, Claude Sonnet 5 encaja bien: su página de modelo incluye las tareas de programación en varios pasos y el uso de herramientas entre sus puntos fuertes, acepta un prompt de sistema y te permite elegir cuánto razonamiento hace.
Abre el modelo. Ve a la página de Claude Sonnet 5 en PicassoIA.
Define el prompt de sistema una vez. Por ejemplo: You write MCP tool definitions. For each OpenAPI operation return a verb_noun name, a description that says what the tool does, when to use it and what it returns, and a flat JSON Schema with example values. Never copy internal parameter names.
Pega una operación cada vez en el campo del prompt, o un pequeño grupo de operaciones relacionadas. Una especificación completa de 5 MB produce resultados confusos.
Elige el nivel de esfuerzo.low es el más rápido y desactiva el razonamiento, medium es adecuado para un lote de operaciones sencillas y high compensa con cuerpos de petición anidados.
Deja el máximo de tokens en 8192, el valor por defecto, para lotes de cinco a ocho operaciones.
Adjunta una captura de pantalla si solo tienes documentación renderizada. El campo de imagen admite una.
Revisa antes de publicar. Pasa cada borrador por el Inspector y corrige los nombres que se solapen.
💡 ¿Necesitas una salida que siempre sea JSON válido? GPT 5 Structured está diseñado para devolver JSON limpio, lo que encaja con los borradores de esquemas que vas a cargar directamente en código.
Crea tu propio kit de herramientas para agentes
Elige una API, cinco herramientas y una tarde libre. Publica primero la versión de solo lectura, pruébala con diez prompts reales y solo después añade las herramientas que escriben o borran.
Los agentes necesitan cosas que mostrar, no solo datos que leer. Pruébalo tú mismo en Picasso IA: escribe un prompt en PicassoIA Image, elige 16:9 y luego refina el resultado con PicassoIA Image Editor Pro, que admite hasta tres imágenes de referencia. Cuando la imagen fija se vea bien, anímala con Picasso IA Video o alarga la toma a diez segundos con Seedance 2.5 Lite. Experimenta con tus propias escenas y, cuando estés listo, construye la herramienta MCP que permita a tu agente hacer lo mismo.