Cómo funciona MCP: el modelo nunca llama a tu servidor
Cómo funciona MCP por dentro: host, cliente, servidor y modelo. Quién ejecuta las tools, qué ve el modelo y dónde viven los permisos, según la spec 2026-07-28.

Imagina una revisión de código cualquiera. Alguien ha montado un servidor MCP que consulta la base de datos de pedidos y lo defiende así: "Tranquilo, el modelo solo puede llamar a las tools que yo expongo".
Suena sensato. Pero parte de una idea equivocada de cómo funciona MCP.
El modelo no llama a nada. No abre conexiones ni ve tu token. Escribe una petición y otra pieza decide si se ejecuta. Si no sabes cuál, no sabes dónde poner los permisos ni por dónde te van a atacar.
En corto: en MCP el modelo nunca habla con el servidor: solo emite una petición de tool. El host (la app de IA) decide si se ejecuta, su cliente MCP envía el tools/call y el resultado vuelve al modelo como contexto. Por eso los permisos viven en el host y la descripción de cada tool forma parte, en la práctica, del prompt.
Si llegas sin contexto, empieza por qué es MCP y para qué sirve. Aquí abrimos el mecanismo, citando la especificación 2026-07-28, la vigente a 6 de noviembre de 2026.
¿Cómo funciona MCP por dentro?
MCP funciona en tres piezas: el host (la app de IA) crea un cliente por cada servidor conectado, descubre sus tools con tools/list y las ejecuta con tools/call cuando el modelo las pide; el modelo no forma parte del protocolo y solo propone llamadas.
Por debajo, el Model Context Protocol es JSON-RPC 2.0.
La spec nombra tres participantes: host, cliente y servidor. El modelo ni siquiera está en la lista. La guía de arquitectura lo deja claro: MCP se ocupa solo del intercambio de contexto y "no dicta cómo las aplicaciones de IA usan los LLM".
Esa ausencia es la clave para entender cómo funciona MCP: el modelo depende del host, no del protocolo.
Arquitectura de MCP: quién hace qué
El host (Claude Code, VS Code o tu app) crea un cliente MCP por servidor. El servidor expone tools, resources y prompts, en local por stdio o en remoto por Streamable HTTP.
| Pieza | Qué hace | Qué ve | Qué no ve | Riesgo principal |
|---|---|---|---|---|
| Host | Orquesta: monta el contexto, llama al modelo, pide aprobación y decide qué se ejecuta | Todo: conversación, tools de todos los servidores, resultados | El código interno del servidor | Aprobar demasiado o enseñar al usuario menos de lo que ve el modelo |
| Cliente MCP | Mantiene una conexión con un servidor y envía tools/list y tools/call |
Mensajes JSON-RPC con su servidor | Los demás servidores | Conectarse a un servidor no verificado |
| Servidor | Declara tools y las ejecuta cuando le llega un tools/call |
Nombre de la tool, argumentos y credenciales de la petición | La conversación, el prompt del usuario y los otros servidores | Ejecutar argumentos sin validar |
| Modelo | Genera texto o una petición de tool con argumentos | Nombres, descripciones, esquemas y resultados que el host le pasa | Credenciales, transporte, proceso del servidor | Obedecer instrucciones escondidas en descripciones o resultados |
Fíjate: el servidor no ve la conversación. Desde la 2026-07-28 el protocolo es stateless (cada petición lleva versión y capacidades en _meta) y "MCP no tiene sesión a nivel de protocolo". El servidor recibe argumentos sueltos.
Cómo funciona MCP en una llamada real: de tools/list a tools/call
Así funciona MCP paso a paso, desde que el host descubre las tools hasta que el modelo recibe el resultado:
- Opcional: el cliente envía
server/discoverpara conocer versiones y capacidades. - El cliente pide
tools/list: nombre, descripción einputSchemade cada tool. - El host mete esas definiciones en el contexto del modelo.
- El modelo responde con una petición de tool. Es texto estructurado, no una llamada de red.
- El host decide: ejecuta, pide confirmación o bloquea.
- Si sigue adelante, el cliente envía
tools/callal servidor. - El servidor ejecuta y devuelve
content(y opcionalmentestructuredContent). - El host añade el resultado a la conversación y vuelve a llamar al modelo.
Así se ve en el cable. Omito _meta, igual que hacen los ejemplos de la spec:
// cliente → servidor
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {} }
// servidor → cliente
{
"jsonrpc": "2.0", "id": 2,
"result": {
"resultType": "complete",
"tools": [{
"name": "buscar_pedido",
"description": "Devuelve el estado de un pedido por su ID",
"inputSchema": {
"type": "object",
"properties": { "pedido_id": { "type": "string" } },
"required": ["pedido_id"]
},
"annotations": { "readOnlyHint": true }
}]
}
}
// el host aprueba la petición del modelo → cliente → servidor
{
"jsonrpc": "2.0", "id": 3, "method": "tools/call",
"params": { "name": "buscar_pedido", "arguments": { "pedido_id": "A-1042" } }
}
// servidor → cliente
{
"jsonrpc": "2.0", "id": 3,
"result": {
"resultType": "complete",
"content": [{ "type": "text", "text": "Pedido A-1042: enviado el 3 de noviembre" }],
"isError": false
}
}
Si las tools cambian, el servidor avisa con notifications/tools/list_changed a los clientes suscritos con subscriptions/listen, que vuelven a pedir la lista. Guarda este detalle para la parte de riesgos.
Cómo diseñar esas tools lo cubro en de API REST a servidor MCP en TypeScript.
¿Dónde viven los permisos en MCP?
En el host. Es la parte de cómo funciona MCP que más se malinterpreta: el servidor valida lo suyo, pero quien convierte una petición del modelo en ejecución es la app.
La spec de tools dice que "siempre DEBERÍA haber un humano en el bucle con capacidad de denegar invocaciones" (SHOULD, no MUST). Y en los principios de seguridad del documento principal, en minúscula y sin peso normativo: los hosts deben obtener consentimiento explícito del usuario antes de invocar cualquier tool.
Las anotaciones (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) ayudan al host a decidir qué confirmar. Pero las escribe el servidor, y la spec es tajante: los clientes DEBEN tratarlas como no fiables salvo que vengan de servidores de confianza. Un readOnlyHint: true es una promesa, no una garantía.
Al servidor le toca validar entradas, controlar accesos, limitar la frecuencia y sanear salidas. Las cuatro son MUST.
La descripción de una tool es parte del prompt
El modelo elige tool leyendo la description y el inputSchema. Ese texto entra en su contexto igual que tu system prompt. Si el servidor es malicioso, la descripción es un canal de inyección.
Invariant Labs lo bautizó tool poisoning el 1 de abril de 2025: instrucciones escondidas en la descripción, invisibles para el usuario y visibles para el modelo.
Documentaron dos variantes más. El rug pull: el servidor cambia la descripción después de que la aprobaste. Y el shadowing: la descripción de una tool altera cómo el agente usa otra de un servidor de confianza.
El debate llegó a Hacker News en el hilo "The “S” in MCP Stands for Security" (730 puntos y 183 comentarios a 6 de noviembre de 2026, según la API de HN Algolia). Luca Beurer-Kellner, coautor del post de Invariant, lo resumió allí: "Instruction level attacks like tool poisoning, cannot be prevented like this, since they are prompt injections and hijack the executing LLM itself".
Aislar el proceso del servidor no basta: el ataque va contra el modelo.
Lo mismo vale para los resultados: un tools/call que devuelve un issue o una web mete texto de terceros en el contexto. Está explicado en inyección indirecta de prompts en agentes.
Sampling: cuando el servidor pide usar el modelo
Sampling es el único mecanismo con el que un servidor MCP puede pedir una generación al modelo del host: envía sampling/createMessage dentro de un InputRequiredResult y el host decide si la hace.
Ni aquí habla el servidor con el modelo. Pide al cliente y el host decide. La spec recomienda que el usuario pueda editar el prompt y revisar la respuesta, y el cliente puede ignorar el systemPrompt del servidor sin avisarle.
Además, sampling está deprecado desde la versión 2026-07-28 (registro de deprecaciones), junto con roots y logging. Podrá eliminarse en la primera versión publicada a partir del 28 de julio de 2027.
La migración recomendada: integrar directamente la API del proveedor. Sigue vigente elicitation (elicitation/create), que pide datos al usuario, no al modelo.
Límites de este diseño
El protocolo no puede obligar a nada. La spec lo admite: MCP "no puede hacer cumplir estos principios de seguridad a nivel de protocolo". Confirmar, mostrar argumentos o revisar una tool tras list_changed depende de cada host. El mismo servidor puede ser seguro en uno y peligroso en otro.
El usuario no ve lo mismo que el modelo. Invariant comprobó que el diálogo de confirmación de un cliente popular no enseñaba los argumentos completos de la llamada, y que el usuario solo ve una versión simplificada de cada tool. Aprobaste algo que no leíste entero.
Confirmar todo mata la utilidad. En el mismo hilo de HN se defendió que "security and usefulness are opposing forces". Si cada lectura pide un clic, el usuario aprueba sin mirar.
Sin sesión, el estado es cosa tuya. Si una tool devuelve un identificador (un carrito, una transacción), la spec advierte (en una sección orientativa) que es un nombre, no un permiso: el servidor autoriza cada llamada.
Y a veces MCP sobra. Si tus tools solo las usa tu propio agente, definirlas directamente en el código, como en este servidor de herramientas con el SDK de Anthropic, te ahorra una capa entera.
Cómo auditar hoy un servidor MCP que ya usas
- Abre la configuración de tu host y lista los servidores conectados.
- Lanza
tools/listen cada uno con el MCP Inspector. - Lee cada
descriptionentera como si fuera parte de tu system prompt, porque lo es. - Revisa qué tools aprueba tu host sin preguntar.
La conclusión sobre cómo funciona MCP: la frontera de seguridad no está en el servidor, está en el host. Configúralo como si el modelo fuera a obedecer a cualquiera, porque puede pasar.
Si quieres el cuadro completo de cómo se construye un agente con tools, MCP, memoria y permisos sin perder el control, lo tienes en el ebook gratuito El Developer Agéntico (194 páginas).
Y si prefieres verlo aplicado a un producto real con Claude Code, está en el curso Construye con IA.
Preguntas frecuentes
¿El servidor MCP ve la conversación?
No. Recibe tools/call con nombre y argumentos, y sin sesión cada petición llega aislada. Solo ve lo que el modelo copie en los argumentos, que puede incluir datos de la conversación.
¿Quién decide si una tool se ejecuta?
El host. El modelo solo propone la llamada; la app la ejecuta, pide confirmación o la bloquea. La spec recomienda un humano capaz de denegarla.
¿El modelo ve mi API key?
No, si está bien configurada. Las credenciales viven en el transporte (cabeceras, tokens OAuth) o en el entorno del servidor. Solo entra en el contexto si la pegas en el chat o un resultado de tool la incluye.
¿Puede un servidor MCP llamar al modelo?
No directamente. Con sampling pide una generación al cliente y el host decide si la hace. Y sampling está deprecado desde la 2026-07-28: si el servidor necesita un LLM, que use la API del proveedor.
¿Qué pasa si un servidor cambia sus tools a mitad de sesión?
Envía notifications/tools/list_changed y el cliente vuelve a pedir la lista. Es legítimo, pero abre la puerta al rug pull: lo que aprobaste ayer puede no ser lo de hoy. Un host prudente vuelve a mostrar las tools que cambian.
¿Puedo fiarme de readOnlyHint para autoaprobar tools?
Solo si confías en el servidor. Las declara él mismo y la spec obliga a tratarlas como no fiables si no es de confianza. Ordenan la experiencia, no son un control de seguridad.
Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.
¿Quieres construir con agentes en serio?
Curso «Construye con IA» con Claude Code, de la idea al producto.
Developer con más de 15 años en frontend y creador de Dominicode. Escribe sobre IA aplicada al desarrollo de software.
¿Te resultó útil este artículo?
Compártelo con tu comunidad y ayuda a otros desarrolladores.