Model Context Protocol (MCP) es una interfaz JSON-RPC entre una aplicación de IA y un programa secundario. El programa secundario ofrece herramientas que el modelo puede llamar, archivos o registros que la aplicación puede adjuntar y plantillas de prompt que el usuario puede ejecutar. Este laboratorio define las piezas, conecta un servidor mínimo de Python en Claude Code y Cursor, y enumera los límites de seguridad que la especificación realmente establece.
Para qué sirve MCP
Antes de MCP, cada host inventaba su propia forma de plugin. Un ayudante de Kubernetes escrito para una aplicación de escritorio no se cargaba en otra. MCP divide el trabajo de la manera en que el Language Server Protocol dividió los conocimientos del lenguaje del editor: el host sigue siendo responsable del modelo y la UI; el servidor sigue siendo responsable de los datos y los efectos secundarios.
La base de la especificación es JSON-RPC, UTF-8, con los mismos significados de mensajes en cada transporte. Un transporte solo enmarca bytes. No cambia lo que es una herramienta.
Tres roles, de la especificación y del SDK oficial de Python:
| Rol | Lo que es | Ejemplo |
|---|---|---|
| Host | La aplicación LLM con la que la persona habla | Claude Code, Cursor, Claude Desktop |
| Cliente | El orador MCP dentro del host, uno por servidor | El conector que inicia python server.py |
| Servidor | El proceso que enumera herramientas, recursos y prompts | El programa corto a continuación |
El servidor nunca habla con el modelo. El host decide cuándo poner una definición de herramienta en el prompt y cuándo mostrar una plantilla de prompt en la UI.
Herramientas, recursos y prompts
La especificación agrupa las características del servidor por quién decide usarlas:
| Primitiva | Quién está en control | Lo que es | Ejemplo de DevOps |
|---|---|---|---|
| Herramienta | El modelo | Una función que el modelo puede llamar, incluyendo escrituras y llamadas API | Buscar un nombre de servicio |
| Recurso | La aplicación | Datos que el cliente adjunta como contexto | Una página de runbook marcada |
| Prompt | El usuario | Una plantilla que el usuario invoca, a menudo como comando slash o menú | «Resumir esta nota de incidente» |
Los clientes también pueden exponer solicitación: el servidor pide al usuario más información. Eso es opcional. Un servidor declara capacidades cuando un cliente se conecta. El MCPServer de Python declara herramientas, recursos y prompts para ti, y un cliente cuidadoso solo solicita familias que el servidor anunció (tools/list, tools/call, resources/list, resources/read, prompts/list, prompts/get).
La página MCP de Cursor dice que soporta herramientas, prompts, recursos, roots, solicitación y la extensión UI de Aplicaciones MCP. Si un host no puede dibujar una vista de aplicación, el resultado de la herramienta aún funciona como contenido MCP ordinario.
Transportes
La descripción general de transporte 2026-07-28 nombra dos enlaces estándar:
- stdio. El cliente inicia el servidor como un subproceso. Los mensajes son JSON-RPC delimitado por nueva línea en stdin y stdout. La cancelación es una notificación
notifications/cancelled. No imprimas registros a stdout mientras el servidor está sirviendo; el SDK de Python mueve stdout ordinario enjuagado a stderr para que unprintno corrompa la corriente. Usalogging, que escribe a stderr. - HTTP fluible. Un endpoint HTTP acepta POST. Cada mensaje JSON-RPC es su propio POST. La respuesta es un objeto JSON o un flujo de Eventos Enviados por Servidor limitado a esa solicitud. Este transporte reemplazó el anterior transporte HTTP+SSE de la revisión 2024-11-05, a partir de 2025-03-26. La revisión 2026-07-28 también eliminó el endpoint de flujo GET y sesiones a nivel de protocolo. Los clientes tienen que manejar servidores más antiguos a propósito.
El SDK de Python run() acepta stdio (el predeterminado), streamable-http y sse. La guía de ejecución dice que SSE aún funciona para clientes que no se han movido, y te dice que no construyas un nuevo servidor en él. Claude Code aún tiene --transport sse y un type de sse para servidores que ya existen, más http para HTTP fluible. En la configuración JSON, streamable-http es un alias de http. Una entrada con una url y sin type es un error: Claude Code trata un type faltante como stdio, omite el servidor y te pide establecer http, sse o ws. Cursor documenta tres transportes en su tabla: stdio, SSE y HTTP fluible.
Los transportes personalizados están permitidos si mantienen JSON-RPC y los patrones de mensajes. Un socket TCP o Unix sin procesar debe reutilizar el marco de stdio en lugar de inventar uno nuevo.
Un servidor Python mínimo
La línea SDK oficial documentada en py.sdk.modelcontextprotocol.io es v2. Necesita Python 3.11 o más reciente. FastMCP fue renombrado a MCPServer; el código nuevo no debe importar mcp.server.fastmcp.
pip install "mcp[cli]"
uv add "mcp[cli]" es la misma instalación. El extra [cli] proporciona el comando mcp. mcp dev necesita npx en PATH porque el Inspector es una aplicación Node.
Guarda esto como server.py. Es un catálogo cerrado de notas de código de salida, no una copia de la demostración aritmética del SDK. Las reglas del decorador coinciden con el SDK: el nombre de la función es el nombre de la herramienta, el docstring es la descripción y las sugerencias de tipo son el esquema de entrada.
from mcp.server import MCPServer
mcp = MCPServer("exit-notes")
NOTES = {
0: "The process finished without a reported error.",
1: "A generic failure. Read the last log lines before you retry.",
137: "128 plus signal 9. Often an external kill, including an OOM killer.",
143: "128 plus signal 15. The process was asked to terminate.",
}
@mcp.tool()
def explain_exit_code(code: int) -> str:
"""Explain a process exit code from a fixed note table."""
return NOTES.get(code, f"No note for exit code {code}.")
@mcp.resource("note://{code}")
def note(code: str) -> str:
"""Return the same note as a resource the host can attach."""
try:
key = int(code)
except ValueError:
return "Code must be an integer."
return NOTES.get(key, f"No note for exit code {key}.")
@mcp.prompt()
def incident_blurb(code: int) -> str:
"""Build a user message that asks for a one-line incident blurb."""
return (
"Write one sentence an on-call engineer can paste. "
f"Exit code: {code}. Use only the tool result. "
"If the note says you lack evidence, say so."
)
if __name__ == "__main__":
mcp.run()
mcp.run() sin argumento sirve stdio y bloquea. La guardia if __name__ importa porque mcp dev y mcp run importan el archivo. Compruébalo sin un host:
uv run mcp dev server.py
Abre la URL que imprime el Inspector. Llama explain_exit_code con 137. Deberías obtener la nota de la señal 9. Un no entero es rechazado por el SDK de la sugerencia de tipo, antes de que se ejecute la función. note://137 es una plantilla de recurso (el {code} es un parámetro), por lo que se muestra bajo plantillas, no como un recurso estático único. incident_blurb se muestra bajo prompts.
Para un servidor desplegado, el comando documentado es:
uv run mcp run server.py --transport streamable-http
La página de primeros pasos del SDK luego conecta un cliente a http://localhost:8000/mcp. No apuntes eso a internet público sin la sección de autorización a continuación.
Agrégalo a Claude Code
Ejecuta esto en una terminal, no dentro de una sesión claude existente. El -- es requerido. Todo después de él es el comando del servidor, no una bandera de Claude Code.
claude mcp add --transport stdio exit-notes -- python server.py
Eso escribe alcance local: solo tú, solo este proyecto, bajo la entrada del proyecto en ~/.claude.json. En Windows ese archivo es %USERPROFILE%\.claude.json. El alcance local es el predeterminado.
| Alcance | Bandera | Archivo | Quién lo ve |
|---|---|---|---|
| Local | --scope local (predeterminado) |
~/.claude.json, bajo este proyecto |
Tú, este proyecto |
| Proyecto | --scope project |
.mcp.json en la raíz del proyecto |
Cualquiera que clone el repo, después de aprobación |
| Usuario | --scope user |
~/.claude.json, mcpServers de nivel superior |
Tú, todos los proyectos |
Alcance del proyecto:
claude mcp add --scope project --transport stdio exit-notes -- python server.py
La forma .mcp.json resultante, que coincide con el ejemplo HTTP documentado y los campos stdio utilizados para claude mcp serve, es:
{
"mcpServers": {
"exit-notes": {
"type": "stdio",
"command": "python",
"args": ["server.py"]
}
}
}
Usa una ruta completa a python o a server.py si el PATH de la sesión es más delgado que tu shell. Claude Code establece CLAUDE_PROJECT_DIR en el proceso del servidor a la raíz del proyecto. En un .mcp.json de proyecto, expándelo como ${CLAUDE_PROJECT_DIR:-.} si lo referencias en command o args.
Dentro de una sesión, /mcp enumera servidores. Un servidor de proyecto permanece pendiente hasta que lo apruebes en una sesión interactiva de claude. Reinicia esas opciones con claude mcp reset-project-choices. Verifica un servidor con claude mcp get exit-notes.
Un servidor HTTP fluible remoto es la otra forma documentada:
claude mcp add --transport http exit-notes https://example.com/mcp
{
"mcpServers": {
"exit-notes": {
"type": "http",
"url": "https://example.com/mcp"
}
}
}
${VAR} y ${VAR:-default} se expanden en command, args, env, url y headers. No confirmes un token vivo. En la url y headers de un servidor remoto, Claude Code sustituye un conjunto documentado de nombres de variables de credencial con un valor vacío, por lo que un archivo de proyecto no puede reenviar esos secretos.
Cuando el mismo nombre existe en más de un alcance, toda la entrada del alcance más alto gana. Los campos no se fusionan. Local gana proyecto, que gana usuario, luego plugins, luego conectores de claude.ai. Una entrada de managedMcpServers de organización supera a eso. Los comandos de Claude Code del día a día están en la hoja de trucos. Un hook aún puede bloquear una herramienta después de que MCP la ha expuesto; ver la guía de hooks.
Agrégalo a Cursor
Cursor lee la configuración de MCP desde .cursor/mcp.json (este proyecto) o ~/.cursor/mcp.json (todos los proyectos). También puedes instalar desde Personalizar, o desde un mercado de equipo que un administrador publicó.
La tabla de campo STDIO marca type como requerido. command es requerido. args, env y envFile son opcionales. envFile funciona solo para stdio. Las entradas remotas usan url y headers opcionales. Los valores en command, args, env, url y headers pueden usar ${env:NAME}, ${userHome}, ${workspaceFolder}, ${workspaceFolderBasename} y ${pathSeparator}.
{
"mcpServers": {
"exit-notes": {
"type": "stdio",
"command": "python",
"args": ["${workspaceFolder}/server.py"]
}
}
}
Un servidor remoto:
{
"mcpServers": {
"exit-notes": {
"url": "https://example.com/mcp",
"headers": {
"Authorization": "Bearer ${env:EXIT_NOTES_TOKEN}"
}
}
}
}
Habilita el servidor bajo Personalizar. Cursor pregunta antes de ejecutar una herramienta MCP a menos que un Modo de Ejecución lo permita. Si el proceso falla, abre el panel Salida, elige MCP Logs y lee el error de inicio. Un servidor caído no detiene a los otros. El lado CLI del mismo archivo es agent mcp list y agent mcp list-tools <nombre>.
OAuth para servidores remotos utiliza el flujo de código de autorización con PKCE. Puedes suministrar un cliente fijo en un objeto auth (CLIENT_ID, CLIENT_SECRET opcional, scopes opcional) cuando el proveedor no admite registro de cliente dinámico. Registra ambas URLs de redirección que enumera la documentación si la gente inicia sesión desde la web y desde la aplicación de escritorio: https://www.cursor.com/agents/mcp/oauth/callback y http://localhost:8787/callback.
Seguridad
El modelo de confianza de Stdio es el que hay que recordar. El host inicia el servidor. Ambos pueden ejecutarse con los mismos privilegios del SO. El SDK no defiende a ninguno de los dos contra el otro. Un diálogo de consentimiento solo ayuda si lees el comando. Consecuencias prácticas, de las notas de seguridad del servidor local:
- Decide antes del lanzamiento: quién publicó el servidor y si lo aislarás. El transporte no hará eso por ti.
- Cada servidor en la sesión comparte el contexto del modelo. Un servidor malo puede dirigir cómo el agente usa las otras herramientas.
- Pasa secretos en el
envde ese servidor (o elenvde Cursor /${env:NAME}), no desde un perfil de shell que cada proceso hereda. - Prefiere un token de alcance estrecho. Mantén secretos de larga duración fuera del JSON si el cliente puede leerlos del almacén de secretos del SO.
- Un contenedor o VM es tu límite de aislamiento si el servidor es de terceros.
La autorización HTTP es opcional en la especificación. Cuando la uses, sigue la especificación de autorización HTTP. Las implementaciones de Stdio no deben usar ese flujo OAuth; deben tomar credenciales del entorno. En cualquier servidor HTTP: HTTPS excepto para localhost en desarrollo, tokens de acceso de corta vida, alcances de privilegio mínimo y sin tokens en registros.
El .mcp.json de proyecto de Claude Code se comparte a propósito. Trata una matriz args confirmada como un script confirmado. La lista propia de Cursor coincide con esto: instala desde una fuente que confíes, lee a qué puede llegar el servidor, restringe claves API y revisa el código antes de conectarlo a sistemas de producción.
Una Acción de GPT personalizado es un conector diferente: un documento OpenAPI dentro de ChatGPT, no un servidor MCP. n8n puede adjuntar un servidor MCP de registro como una herramienta en un nodo AI Agent; construye primero el flujo de trabajo, luego agrega el agente, como en el primer flujo de trabajo de n8n.
Preguntas frecuentes
¿Qué problema resuelve MCP?
MCP es una forma estándar para que una aplicación de IA llegue a herramientas y contexto. Escribes un servidor una vez. Claude Code, Cursor y otros hosts hablan el mismo protocolo JSON-RPC en lugar de un formato de plugin privado para cada producto.
¿Cuál es la diferencia entre un host, un cliente y un servidor?
El host es la aplicación que la persona usa, como Claude Code o Cursor. El cliente es la pieza dentro del host que habla MCP, un cliente por servidor. El servidor es el programa que ejecutas. Expone herramientas, recursos y prompts. No llama al modelo en sí.
¿Qué transporte MCP debería usar?
La especificación actual define stdio e HTTP fluible. Stdio es para un servidor que el host inicia en tu máquina. HTTP fluible es un único endpoint POST para un servidor que despliegas. SSE es el transporte HTTP más antiguo. El SDK de Python aún lo ejecuta y te dice que no construyas nada nuevo en él.
¿Cómo agrego un servidor MCP a Claude Code y a Cursor?
Claude Code: claude mcp add --transport stdio <nombre> -- <comando> [args], o un archivo .mcp.json con una entrada mcpServers. Cursor: .cursor/mcp.json en el proyecto, o ~/.cursor/mcp.json para cada proyecto, con command, args y env para un proceso local, o url para HTTP o SSE.
¿Está aislado un servidor MCP local?
No. Stdio significa que el host inicia tu programa como un proceso hijo. Las notas de seguridad oficiales dicen que el cliente y ese proceso comparten un dominio de confianza. El SDK no aísla el servidor. El aislamiento, si lo deseas, es un contenedor, una VM o los prompts de aprobación del host.
Cómo se verificó esta guía
Escrita a mano a partir de la documentación oficial, con cada comando comprobado de nuevo y una segunda revisión antes de publicar. Última revisión: .
Fuentes oficiales consultadas para este artículo
- modelcontextprotocol.io/specification/2026-07-28
- modelcontextprotocol.io/specification/2026-07-28/server
- modelcontextprotocol.io/specification/2026-07-28/basic/transports
- modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http
- modelcontextprotocol.io/specification/2026-07-28/basic/authorization
- modelcontextprotocol.io/docs/2026-07-28/tutorials/security/local-server-security
- py.sdk.modelcontextprotocol.io/
- py.sdk.modelcontextprotocol.io/get-started/first-steps/
- py.sdk.modelcontextprotocol.io/run/
- code.claude.com/docs/en/mcp
- cursor.com/docs/mcp

