Ir al contenido

Cómo funciona MCP, a fondo

Para los técnicos: las partes del Model Context Protocol, los mensajes que se envían y cómo se protege una conexión. Verificado contra la especificación actual, revisión 2026-07-28, con el propio servidor de MCP Team como ejemplo.

Revisión de la especificación
2026-07-28, la actual
Mensajes
JSON-RPC 2.0
Transportes
stdio para servidores locales, Streamable HTTP para los remotos
Los servidores ofrecen
Herramientas (tools), recursos (resources) y prompts
Los clientes ofrecen
Elicitation. Sampling y roots todavía funcionan, pero están en desuso
Acceso por HTTP
OAuth 2.1, cuando el servidor lo necesita

1Tres partes: host, cliente, servidor

El host es la app de IA que usas: Claude, ChatGPT, un editor de código. Trabaja con el modelo, guarda la conversación y decide qué necesita tu aprobación.

Por cada servidor al que se conecta, el host crea un cliente. Un cliente, un servidor: así cada conexión queda separada.

Un servidor ofrece datos y acciones. Puede correr en tu computadora o en internet. Por diseño, solo ve los requests que le envían: ni la conversación completa ni los otros servidores.

Un cliente por cada conexión a un servidor. El host guarda la conversación; cada servidor solo ve lo que le envían.

Dos capas

MCP tiene dos capas. La capa de datos es lo que se dice: mensajes JSON-RPC para descubrimiento, herramientas, recursos, prompts y notificaciones. La capa de transporte es cómo viaja: abrir la conexión, enmarcar cada mensaje y manejar la autorización.

2Cada mensaje es JSON-RPC 2.0

MCP no inventa un formato propio. Cada mensaje es un objeto JSON-RPC 2.0, de uno de tres tipos.

Request

Pide algo. Lleva un id y un method, como tools/list. En MCP el id nunca puede ser null.

tools/listcliente → servidor
{"jsonrpc": "2.0","id": 2,"method": "tools/list","params": {"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28","io.modelcontextprotocol/clientInfo": {"name": "ExampleClient","version": "1.0.0"},"io.modelcontextprotocol/clientCapabilities": {}}}}

Respuesta

Contesta un request, con el mismo id, y trae un result o un error. Desde 2026-07-28 cada resultado dice su resultType: complete, o input_required cuando el servidor necesita algo más para terminar.

Respuestaservidor → cliente
{"jsonrpc": "2.0","id": 2,"result": {"tools": […],"resultType": "complete",…}}

Notificación

Un mensaje de una sola vía, sin id. Nadie la contesta. Así viajan los avisos de progreso y de cambios.

Notificaciónejemplo de la especificación
{"jsonrpc": "2.0","method": "notifications/tools/list_changed"}

En la revisión actual solo el cliente envía requests. El servidor contesta cada uno, y puede enviar notificaciones sobre el request que está trabajando. Si necesita algo del usuario o del cliente, lo pide dentro de su respuesta en vez de enviar un request propio.

Los errores usan los códigos estándar de JSON-RPC, como -32601 (método no encontrado) y -32602 (parámetros inválidos). MCP añade tres: -32020 headers que no coinciden, -32021 falta una capacidad del cliente y -32022 versión del protocolo no soportada.

3Ya no hay handshake: cada request se sostiene solo

Hasta la revisión 2025-11-25, cada conexión empezaba con un handshake. El cliente enviaba initialize con su versión del protocolo y sus capacidades, el servidor contestaba con las suyas, el cliente confirmaba con notifications/initialized y solo entonces empezaba el trabajo.

La revisión 2026-07-28 eliminó el handshake y las sesiones del protocolo. Ahora cada request lleva en _meta la versión del protocolo y las capacidades del cliente, y debe identificar al cliente también. El servidor no puede depender de nada que dijo un request anterior, aunque sea en la misma conexión.

Todo servidor tiene que contestar server/discover con sus versiones, capacidades e identidad. Llamarlo primero es opcional: el cliente puede enviar su request directamente y, si la versión no coincide, el servidor contesta con el error -32022 y las versiones que soporta.

El servidor de MCP Team habla las dos. Un request 2026-07-28 se atiende por sí solo, y un cliente más viejo que abre con initialize recibe el handshake de 2025-11-25. Los dos diagramas de abajo siguen sus respuestas reales.

2026-07-28: cada request se sostiene solo
  1. Del cliente al servidor: server/discoverOpcional: ¿qué versiones y funciones tienes?
  2. Del servidor al cliente: resultVersión 2026-07-28, capacidad tools
  3. Del cliente al servidor: tools/listLa versión y las capacidades van en _meta
  4. Del servidor al cliente: resultSeis herramientas, de get_profile a fetch
  5. Host: el modelo escoge get_fact
  6. Del cliente al servidor: tools/callget_fact con query: "sunday hours"
  7. Del servidor al cliente: resultTexto para el modelo, y la misma respuesta en JSON
2025-11-25 y antes: primero el handshake
  1. Del cliente al servidor: initializeVersión 2025-11-25, capacidades, datos del cliente
  2. Del servidor al cliente: resultVersión acordada, capacidades, datos del servidor, instrucciones
  3. Del cliente al servidor: notifications/initializedUna notificación: no hay respuesta, solo HTTP 202
  4. Del cliente al servidor: tools/listLa versión va en el header MCP-Protocol-Version
  5. Del servidor al cliente: resultLas mismas seis herramientas
  6. Del cliente al servidor: tools/callget_fact, con los mismos argumentos
  7. Del servidor al cliente: resultLa misma respuesta
server/discovercliente → servidor
{"jsonrpc": "2.0","id": 1,"method": "server/discover","params": {"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28","io.modelcontextprotocol/clientInfo": {"name": "ExampleClient","version": "1.0.0"},"io.modelcontextprotocol/clientCapabilities": {}}}}
Respuestaservidor → cliente · acortado con …
{"jsonrpc": "2.0","id": 1,"result": {"resultType": "complete","supportedVersions": ["2026-07-28"],"capabilities": { "tools": { "listChanged": true } },"instructions": "This connector is Example Café's business brain: facts the owner h…","ttlMs": 0,"cacheScope": "private","_meta": {"io.modelcontextprotocol/serverInfo": {"name": "mcp-team","version": "0.1.0",…}}}}

instructions es texto que el host le puede pasar al modelo. MCP Team lo usa para decir qué es el conector y cómo tratar sus datos.

Antes de 2026-07-28, esa misma información llegaba con el handshake. MCP Team todavía lo contesta:

initializecliente → servidor
{"jsonrpc": "2.0","id": 1,"method": "initialize","params": {"protocolVersion": "2025-11-25","capabilities": {},"clientInfo": {"name": "ExampleClient","version": "1.0.0"}}}
Respuestaservidor → cliente · acortado con …
{"jsonrpc": "2.0","id": 1,"result": {"protocolVersion": "2025-11-25","capabilities": { "tools": { "listChanged": true } },"serverInfo": {"name": "mcp-team","version": "0.1.0",…},"instructions": "This connector is Example Café's business brain: facts the owner h…"}}

4Dos transportes: stdio y Streamable HTTP

stdio, para servidores locales

El cliente arranca el servidor como un subproceso en la misma máquina. Los mensajes van por stdin y stdout, un objeto JSON por línea. El servidor puede escribir logs en stderr, nunca en stdout. No hay red ni OAuth: un servidor local lee sus credenciales del entorno.

Streamable HTTP, para servidores remotos

El servidor tiene un solo endpoint, y cada mensaje es su propio POST. La respuesta es JSON simple, o un stream de eventos (SSE) solo para ese request, que puede traer avisos de progreso antes del resultado.

El cliente también copia campos clave a headers, para que los gateways puedan enrutar sin leer el cuerpo: MCP-Protocol-Version en cada request y, nuevos en 2026-07-28, Mcp-Method y, en una llamada a herramienta, Mcp-Name. Si un header y el cuerpo no coinciden, el servidor tiene que rechazar el request. Los servidores también tienen que revisar el header Origin, para frenar el DNS rebinding.

Una llamada a herramienta a MCP Team, por HTTPcliente → servidor
POST /mcp/<secreto> HTTP/1.1Host: mcp.teamContent-Type: application/jsonAccept: application/json, text/event-streamMCP-Protocol-Version: 2026-07-28Mcp-Method: tools/callMcp-Name: get_fact

Si quitas Mcp-Method, el servidor de MCP Team contesta 400 Bad Request:

RespuestaHTTP 400
{"jsonrpc": "2.0","id": 3,"error": {"code": -32020,"message": "Bad Request: the request headers and body disagree: the body names method tools/call but the required Mcp-Method header is absent","data": {"mismatch": {"header": "(missing)","body": "the body names method tools/call but the required Mcp-Method header is absent"}}}}

Lo que se fue, o va de salida

  • El transporte HTTP+SSE original, de 2024-11-05: en desuso desde 2025-03-26. Usa Streamable HTTP.
  • Las sesiones (Mcp-Session-Id), el stream por GET y los streams reanudables (Last-Event-ID): eliminados de Streamable HTTP en 2026-07-28.
  • Los avisos de cambios ahora llegan por un stream subscriptions/listen que abre el cliente. Reemplaza el stream por GET y resources/subscribe.

5Lo que ofrecen los servidores, y lo que los clientes ofrecen de vuelta

Un servidor puede ofrecer tres tipos de cosas. La diferencia está en quién decide cuándo se usan.

  • Herramientas (tools)

    Quién decide: el modelo

    Funciones que el modelo puede llamar para buscar algo o hacer una acción.

    tools/listtools/call

  • Recursos (resources)

    Quién decide: la app

    Datos con un URI, como un archivo o el esquema de una base de datos, que la app añade como contexto.

    resources/listresources/readresources/templates/list

  • Prompts

    Quién decide: el usuario

    Plantillas que el usuario escoge, a menudo como comandos con barra (/).

    prompts/listprompts/get

Los clientes pueden ofrecer cosas de vuelta. El servidor las pide en medio de un request.

  • Elicitation

    El servidor le pide información al usuario. El modo formulario muestra un formulario corto descrito por un JSON Schema plano. El modo URL manda al usuario a una página web, y es obligatorio para contraseñas, API keys y datos de pago, para que nunca pasen por el cliente.

  • Sampling

    En desuso. El servidor le pide una respuesta al modelo del cliente. Los servidores nuevos deben llamar directamente a un proveedor de modelos.

  • Roots

    En desuso. El cliente le dice al servidor qué carpetas importan. Los servidores nuevos deben recibir las rutas como argumentos de herramienta o en su configuración.

Sampling, roots y los logs del servidor quedaron en desuso (deprecated) en 2026-07-28. Siguen funcionando por al menos doce meses.

Cómo pide algo un servidor ahora

Los servidores ya no envían requests propios. El servidor contesta con resultType: "input_required" y un grupo de inputRequests, por ejemplo un elicitation/create. El cliente reúne las respuestas y envía el request original otra vez, con inputResponses y un id nuevo.

La respuesta del servidorejemplo de la especificación
{"jsonrpc": "2.0","id": 2,"result": {"resultType": "input_required","inputRequests": {"github_login": {"method": "elicitation/create","params": {"mode": "form","message": "Please provide your GitHub username","requestedSchema": {"type": "object","properties": {"name": { "type": "string" }},"required": ["name"]}}}},"requestState": "eyJsb2NhdGlvbiI6Ik5ldyBZb3JrIn0..."}}
El cliente lo intenta otra vezejemplo de la especificación
{"jsonrpc": "2.0","id": 3,"method": "tools/call","params": {"name": "get_weather","arguments": { "location": "New York" },"inputResponses": {"github_login": {"action": "accept","content": { "name": "octocat" }}},"requestState": "eyJsb2NhdGlvbiI6Ik5ldyBZb3JrIn0..."}}

MCP Team solo usa herramientas. Su respuesta a server/discover solo menciona tools, y nunca le pide nada al cliente.

6Una herramienta de cerca: get_fact de MCP Team

Una herramienta es un nombre, una descripción que lee el modelo y un JSON Schema para sus argumentos. Esta es una entrada real del tools/list de MCP Team, para un café de ejemplo.

name
Lo que llama el modelo. Único dentro del servidor.
title
Un nombre para mostrarle a la gente.
description
Lo que lee el modelo para decidir cuándo usar la herramienta.
inputSchema
JSON Schema de los argumentos, versión 2020-12 a menos que el esquema diga otra. MCP Team escribe sus esquemas en zod, con límites de largo.
outputSchema
Opcional: la forma del JSON que devuelve la herramienta. MCP Team no declara uno.
annotations
Pistas sobre cómo se comporta la herramienta. Más en la sección 10.
Respuestatools/list · una de seis herramientas completa, el resto acortado con …
{"jsonrpc": "2.0","id": 2,"result": {"tools": [{ "name": "get_profile", … },{"name": "get_fact","title": "Example Café: look up one fact","description": "Looks up a single fact in Example Café's business brain (for example a price, an offer, a policy, a booking link or a contact detail) by its exact key or by a few words describing it. Returns the value with its label, section and when it was last updated, or says that it is not in the brain.","inputSchema": {"type": "object","$schema": "https://json-schema.org/draft/2020-12/schema","properties": {"key": {"description": "Exact fact key, e.g. prices.drop-in-class.","type": "string","maxLength": 120},"query": {"description": "A few words describing the fact, e.g. 'cancellation policy'.","type": "string","maxLength": 200}}},"annotations": {"readOnlyHint": true,"destructiveHint": false,"idempotentHint": true,"openWorldHint": false}},{ "name": "list_upcoming", … },{ "name": "propose_change", … },{ "name": "search", … },{ "name": "fetch", … }],"resultType": "complete","ttlMs": 0,"cacheScope": "private","_meta": {"io.modelcontextprotocol/serverInfo": {"name": "mcp-team","version": "0.1.0",…}}}}

La llamada

Alguien le pregunta al asistente del café: ¿abren el domingo por la mañana? El modelo escoge get_fact, y el cliente envía:

tools/callcliente → servidor
{"jsonrpc": "2.0","id": 3,"method": "tools/call","params": {"name": "get_fact","arguments": { "query": "sunday hours" },"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28","io.modelcontextprotocol/clientInfo": {"name": "ExampleClient","version": "1.0.0"},"io.modelcontextprotocol/clientCapabilities": {}}}}
Respuestaservidor → cliente · acortado con …
{"jsonrpc": "2.0","id": 3,"result": {"content": [{"type": "text","text": "Sunday hours (Schedule & dates): 7 am to 1 pm. Breakfast until noon.\nKey: schedule.sunday-hours\nLast updated: 2026-10-14"}],"structuredContent": {"found": true,"key": "schedule.sunday-hours","section": "schedule","label": "Sunday hours","value": "7 am to 1 pm. Breakfast until noon.","updated_at": "2026-10-14","event_start": null,"event_end": null},"resultType": "complete","_meta": {"io.modelcontextprotocol/serverInfo": {"name": "mcp-team","version": "0.1.0",…}}}}
  • content es lo que lee el modelo: texto simple, aquí con la clave del dato y su fecha.
  • structuredContent es la misma respuesta en JSON, para el código que prefiere campos en vez de texto.
  • Cuando una herramienta no puede hacer su trabajo, igual devuelve un resultado, con isError: true y una explicación que el modelo puede usar para corregirse. Envía una key de 500 caracteres y MCP Team contesta: Input validation error: … expected string to have <=120 characters.
  • Un request que el servidor no puede correr recibe un error de JSON-RPC. Pídele a MCP Team una herramienta que no existe y devuelve -32602: Tool delete_everything not found.

Las seis herramientas

HerramientaQué haceAnnotations
get_profileLa memoria aprobada en markdown, sección por secciónreadOnlyHint: true
get_factUn dato, por su clave o por unas palabrasreadOnlyHint: true
list_upcomingLo que viene con fecha, lo más cercano primeroreadOnlyHint: true
propose_changeGuarda una sugerencia que el dueño tiene que aprobarreadOnlyHint: false, destructiveHint: false
searchLos datos que coinciden, con id, título y url: la forma que espera ChatGPTreadOnlyHint: true
fetchUn dato completo, por el id de un resultado de búsquedareadOnlyHint: true

Las seis también dicen openWorldHint: false: solo tocan la memoria de este negocio.

7Cuando cambia la lista de herramientas

Un servidor que va a avisar los cambios en su lista de herramientas declara listChanged: true. En 2026-07-28, un cliente que quiere enterarse abre un request de larga duración, subscriptions/listen, y dice qué quiere recibir, como toolsListChanged. El servidor confirma con notifications/subscriptions/acknowledged, envía notifications/tools/list_changed cada vez que la lista cambia, y el cliente vuelve a llamar tools/list. Los prompts y los recursos funcionan igual, y un cliente también puede vigilar recursos específicos.

El servidor de MCP Team lo declara y acepta la suscripción, como ves abajo. Sus seis herramientas son las mismas en cada request, así que el stream no tiene nada que avisar.

subscriptions/listencliente → servidor
{"jsonrpc": "2.0","id": 4,"method": "subscriptions/listen","params": {"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28","io.modelcontextprotocol/clientInfo": {"name": "ExampleClient","version": "1.0.0"},"io.modelcontextprotocol/clientCapabilities": {}},"notifications": { "toolsListChanged": true }}}
Primer evento del streamservidor → cliente
{"jsonrpc": "2.0","method": "notifications/subscriptions/acknowledged","params": {"notifications": { "toolsListChanged": true },"_meta": {"io.modelcontextprotocol/subscriptionId": 4}}}

Los resultados de las listas también traen pistas para el caché: ttlMs, cuántos milisegundos sigue fresco el resultado, y cacheScope, public o private. MCP Team envía ttlMs: 0 y private: la lista se considera vieja de inmediato, y una copia en caché no se comparte entre quienes llaman.

8Entrar a un servidor remoto: OAuth 2.1

En MCP la autorización es opcional. Cuando un servidor HTTP la necesita, la especificación se apoya en OAuth 2.1. El servidor MCP hace de resource server, y un servidor de autorización, de la misma empresa o de otra, autentica al usuario y emite los tokens.

Los servidores locales por stdio se saltan todo esto y leen sus credenciales del entorno.

El flujo, paso a paso

  1. El cliente llama al servidor sin token. Recibe 401 y un header WWW-Authenticate que puede apuntar a la metadata del recurso protegido del servidor (RFC 9728). Si no la indica, el cliente busca ese documento en un URL well-known.

  2. Ese documento dice cuál es el servidor de autorización. El cliente lee la metadata de ese servidor (RFC 8414 u OpenID Connect Discovery).

  3. El cliente se identifica: con un ID registrado de antemano, o con un Client ID Metadata Document, un URL HTTPS que describe al cliente. Dynamic Client Registration todavía funciona, pero está en desuso.

  4. El usuario entra y aprueba en el navegador. El cliente usa PKCE con S256, nombra este servidor exacto en un parámetro resource (RFC 8707) y verifica el valor iss cuando la respuesta lo trae (RFC 9207).

  5. El cliente cambia el código por un access token y lo envía en cada request como Authorization: Bearer …, nunca en el query string del URL.

  6. El servidor verifica que el token se emitió para él y rechaza cualquier otro. Nunca le pasa el token a otras APIs.

Paso 1, el retoejemplo de la especificación
HTTP/1.1 401 UnauthorizedWWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource", scope="files:read"

El menor privilegio

Los clientes piden el grupo más pequeño de scopes. Cuando una acción necesita más, el servidor contesta 403 con insufficient_scope y los scopes que hacen falta, y el cliente le pide al usuario que apruebe esos además de los que ya tiene.

10Seguridad que aguanta

MCP no puede imponer la seguridad por sí solo. La especificación se la deja a los hosts, los clientes y los servidores. Estos cinco hábitos son los que más pesan; debajo de cada uno, cómo lo aplica MCP Team.

  1. Da el menor acceso que funcione

    Pide el grupo más pequeño de scopes, añade más solo cuando una acción lo necesite, y que cada servidor haga una sola cosa.

    En MCP Team: El enlace puede leer datos aprobados y dejar sugerencias. Nada más. Los datos marcados como privados nunca le llegan.

  2. Que una persona apruebe las acciones

    Los hosts tienen que pedirle permiso al usuario antes de que corra una herramienta, y siempre debe haber una persona que pueda decir que no. Los clientes deben mostrar lo que le van a enviar a una herramienta antes de enviarlo.

    En MCP Team: La única herramienta que escribe, propose_change, solo guarda una sugerencia. Nada cambia hasta que el dueño la aprueba, y no se aceptan más cuando hay 25 esperando.

  3. Trata lo que regresa como no confiable

    Las descripciones y los resultados de las herramientas entran al contexto del modelo, y un texto escondido en ellos lo puede desviar. Eso es prompt injection; cuando se esconde en la descripción de una herramienta le dicen tool poisoning. Los clientes deben revisar los resultados antes de que los vea el modelo, y los servidores tienen que limpiar lo que devuelven.

    En MCP Team: Los datos vienen de páginas web y de texto pegado, así que el servidor quita caracteres invisibles (de ancho cero, cambios de dirección, caracteres de etiqueta de Unicode), limita los largos y evita que un valor empiece su propio título. Sus instrucciones le dicen al modelo que los valores son datos, no instrucciones.

  4. Lee las annotations como pistas, no como promesas

    readOnlyHint, destructiveHint, idempotentHint y openWorldHint describen lo que hace una herramienta. Un cliente no debe confiar en ellas si vienen de un servidor en el que no confía.

    En MCP Team: Cinco herramientas dicen que solo leen. propose_change dice que escribe, pero que no destruye nada. Las seis dicen que se quedan dentro de la memoria de este negocio.

  5. Revisa cada entrada

    Los servidores tienen que validar lo que reciben las herramientas, controlar el acceso y limitar la cantidad de llamadas. Por HTTP, tienen que revisar el header Origin.

    En MCP Team: Los argumentos se validan contra el esquema de cada herramienta antes de que corra, y cada request tiene que traer un secreto válido.

11Fuentes

Verificado contra estas páginas de la especificación y su documentación en modelcontextprotocol.io (en inglés), revisión 2026-07-28 a menos que el enlace diga otra. Los ejemplos de MCP Team salen de correr el código de su servidor con un café de ejemplo.

Míralo con tu propio negocio

Pega tu página web y mira cómo preparamos la memoria de tu negocio. Gratis, sin registrarte.

¿Quieres la IA conectada a tus propias herramientas? Habla con nosotros →

Solo leemos páginas públicas. ¿Ya tienes una memoria? Entrar.