MCP (Model Context Protocol): El 90% lo Trata Como un Estándar de API — y el Talón de Aquiles es la Seguridad
Guía completa de MCP (Model Context Protocol): cómo funciona, sus tres primitivas, el error de confundirlo con APIs REST, y el agujero de seguridad que nadie aborda.
MCP No Es un Estándar de API. Es una Inversión de Cómo Hablas con la IA
El 90% de los desarrolladores trata MCP como "el nuevo REST para IA".
*Error. *
MCP no estandariza cómo llamas a una API desde un modelo. Estandariza cómo un modelo se convierte en el anfitrión al que otros servidores se conectan.
La diferencia no es académica. Es arquitectónica.
En REST, tú llamas a la API. El server controla el acceso. Tú te autenticas. El server decide si responderte.
En MCP, el modelo (host + client) inicia la conexión. Pero es el server el que expone herramientas que el modelo puede ejecutar. El server puede estar escribiendo ficheros, lanzando comandos shell, mutando tu base de datos.
*La seguridad está invertida. Y el spec no dice nada sobre eso. *
Llevo meses integrando MCP en producción. He visto a equipos desplegar servers que ejecutan shell commands sin autenticación. He visto a desarrolladores asumir que "MCP tiene auth" porque el spec menciona transports.
No tiene.
Y el hecho de que nadie esté hablando de esto en los tutoriales de MCP es... preocupante.
---
El Verdadero Problema: Cómo el 90% Malinterpreta MCP
Hay dos errores que se repiten en cada conversación sobre MCP.
Error #1: "MCP es el function calling de Anthropic"
❌ Crees que MCP compite con OpenAI function calling.
✅ MCP es un protocolo de capa inferior. Function calling es una feature de un solo modelo. MCP es un estándar abierto para que cualquier modelo descubra e invoque herramientas.
OpenAI function calling te deja definir tools en el API call. El schema viaja en el request. El modelo decide si llamarlas.
MCP separa el descubrimiento de herramientas de su ejecución. El server declara qué tools tiene. El cliente (el modelo) las descubre durante el handshake de capabilities. No necesitas hardcodear schemas en cada llamada.
Pero esto significa que MCP no reemplaza REST. Lo envuelve.
Cada Tool en MCP es, por debajo, una llamada JSON-RPC. Cada Resource es un URI que apunta a datos. Los MCP servers son wrappers delgados sobre tus APIs existentes.
*No vas a tirar tu REST API. Vas a ponerle un MCP server delante. *
Error #2: "MCP es fácil de desplegar — solo montas un server"
En local, sí. El transport `stdio` funciona. Lanzas el server desde Claude Desktop y el modelo lo consume.
En remoto, el infierno.
MCP para servidores remotos usa SSE (Server-Sent Events). No define autenticación. No define autorización. No define rate limiting.
El spec dice, textualmente, que la seguridad se delega al transport layer.
Traducción: *tú te encargas de todo. *
Y el 90% de los tutoriales que he visto no mencionan ni una línea de auth en sus ejemplos de MCP server.
---
El Agujero de Seguridad que Nadie Quiere Mirar
MCP tiene tres primitivas: Resources, Tools y Prompts.
Resources: datos que el modelo puede leer (URIs con esquema `file://`, `db://`, etc.)
Tools: acciones que el modelo puede ejecutar (funciones con parámetros tipados)
Prompts: plantillas reutilizables para orquestar flujos
El problema no está en las primitivas. Está en que no hay control de acceso entre ellas.
Si expones un Tool que ejecuta `exec()` en el shell, cualquier modelo conectado puede invocarlo. No hay scopes. No hay permisos por herramienta. No hay autenticación por endpoint.
Mira este ejemplo de un MCP server vulnerable:
```python
MCP server vulnerable — sin validación, sin auth, sin sandbox
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
server = Server("dangerous-server")
@server.list_tools()
async def handle_list_tools():
return [
Tool(
name="run_shell_command",
description="Ejecuta un comando shell",
inputSchema={
"type": "object",
"properties": {
"command": {"type": "string"}
}
}
)
]
@server.call_tool()
async def handle_call_tool(name: str, arguments: dict):
if name == "run_shell_command":
import subprocess
⚠️ Sin validación. Sin sandbox. Sin auth.
result = subprocess.run(
arguments["command"],
shell=True,
capture_output=True
)
return TextContent(type="text", text=result.stdout.decode())
```
Cualquier cliente MCP — Claude Desktop, un agente custom, Grok Build con su soporte nativo de MCP — puede llamar a ese Tool. Sin token. Sin API key. Sin rate limit.
El modelo decide si ejecutarlo. Y los modelos no son buenos decidiendo qué es seguro ejecutar.
---
El Mapa de Conceptos: Cómo Funciona Realmente MCP
Antes de que construyas nada, necesitas entender el flujo real.
```
┌─────────────────────────────────────────────────┐
│ HOST (AI App) │
│ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ Client │◄──►│ Server │ │ MCP Server │ │
│ │ (LLM) │ │ (MCP) │ │ (stdio/SSE)│ │
│ └──────────┘ └──────────┘ └─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ Resources Tools Prompts │
└─────────────────────────────────────────────────┘
```
Paso 1 — Handshake de capabilities:
El cliente envía `initialize` con su versión de protocolo y capacidades. El server responde con las suyas.
```json
{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {
"tools": {},
"resources": {
"subscribe": true
}
}
}
}
```
Paso 2 — Descubrimiento:
El cliente pide `tools/list`, `resources/list`. El server devuelve lo que ofrece.
Paso 3 — Ejecución/Suscripción:
El cliente invoca `tools/call` o se suscribe a cambios en un Resource vía `resources/subscribe`.
Este flujo es elegante. Permite que el servidor y el cliente evolucionen independientemente. El modelo descubre qué puede hacer en tiempo real, no en un schema hardcodeado.
*Pero en ningún paso se verifica quién es el cliente. *
---
El Patrón de 5 Capas para MCP Seguro
Vale, el spec no tiene auth. ¿Y qué haces?
Construyes tu propia seguridad. Aquí está el framework que uso en producción: El Patrón de 5 Capas para MCP Seguro.
Capa 1: Transporte con Autenticación Explicita
No uses `stdio` para remoto. Usa SSE con un middleware de auth.
```python
MCP server con validación de API key en el transport
from mcp.server.sse import SseServerTransport
from starlette.applications import Starlette
from starlette.responses import JSONResponse
app = Starlette()
sse = SseServerTransport("/messages/")
Middleware de autenticación
@app.middleware("http")
async def auth_middleware(request, call_next):
api_key = request.headers.get("X-API-Key")
if not api_key or api_key != SECRET_KEY:
return JSONResponse({"error": "unauthorized"}, status_code=401)
return await call_next(request)
```
Capa 2: Validación de Inputs por Tool
Cada Tool handler debe validar sus parámetros explícitamente. No confíes en que el modelo envíe lo correcto.
```python
@server.call_tool()
async def handle_call_tool(name: str, arguments: dict):
if name == "send_email":
Validación explícita — el modelo puede alucinar parámetros
to = arguments.get("to")
if not to or "@" not in to:
return TextContent(
type="text",
text="Error: 'to' debe ser un email válido"
)
Whitelist de destinatarios permitidos
if to not in ALLOWED_RECIPIENTS:
return TextContent(
type="text",
text=f"Error: {to} no está en la whitelist"
)
Ejecutar solo después de validar
return await send_email(to, arguments.get("body", ""))
```
Capa 3: Rate Limiting por Cliente
MCP no tiene rate limiting en el spec. Implementa el tuyo.
```python
from collections import defaultdict
import time
rate_limits = defaultdict(lambda: {"count": 0, "window_start": time.time()})
RATE_LIMIT = 10 # llamadas por minuto
RATE_WINDOW = 60
def check_rate_limit(client_id: str) -> bool:
now = time.time()
entry = rate_limits[client_id]
if now - entry["window_start"] > RATE_WINDOW:
entry["count"] = 0
entry["window_start"] = now
entry["count"] += 1
return entry["count"] <= RATE_LIMIT
```
Capa 4: Sandboxing de Ejecución
Cualquier Tool que ejecute comandos, escriba ficheros o haga llamadas de red debe estar sandboxed.
```python
import shlex
import subprocess
@server.call_tool()
async def handle_tool(name: str, arguments: dict):
if name == "run_safe_command":
Whitelist de comandos permitidos
ALLOWED_COMMANDS = ["ls", "cat", "pwd", "date"]
cmd = arguments.get("command", "")
parts = shlex.split(cmd)
if parts[0] not in ALLOWED_COMMANDS:
return TextContent(
type="text",
text=f"Error: '{parts[0]}' no está permitido"
)
Timeout para evitar ejecuciones indefinidas
result = subprocess.run(
parts, capture_output=True, timeout=30
)
return TextContent(type="text", text=result.stdout.decode())
```
Capa 5: Logging y Auditoría
Cada llamada a Tool debe loguearse con timestamp, cliente, parámetros y resultado.
```python
import logging
import json
logger = logging.getLogger("mcp_audit")
@server.call_tool()
async def handle_tool(name: str, arguments: dict):
client_id = request.headers.get("X-Client-Id", "unknown")
logger.info(json.dumps({
"action": "tool_call",
"tool": name,
"client": client_id,
"params": arguments,
"timestamp": time.time()
}))
... ejecución y log del resultado
```
---
Grokl Build y MCP: Lo que Cambia con los Nuevos Agentes
El anuncio de Grok Build de xAI (mayo 2026) confirma una tendencia: los coding agents están adoptando MCP como estándar nativo.
Grok Build soporta MCP servers "out of the box". Instala plugins como `browser-review` desde un marketplace y los MCP servers se conectan automáticamente.
Esto es genial para la portabilidad de herramientas. Pero también significa que el agujero de seguridad de MCP se escala.
Si despliegas un MCP server vulnerable y Grok Build lo descubre, cualquier prompt que un desarrollador ejecute podría invocar comandos shell sin restricciones. No es teoría. Es el modelo de ejecución actual.
La solución no es evitar MCP. Es construir MCP servers con las cinco capas de seguridad desde el día uno.
---
Cuándo Usar MCP (y Cuándo NO)
| Situación | ¿MCP? | Alternativa |
|---|---|---|
| Una sola integración con un modelo específico | ❌ | Function calling nativo |
| Múltiples modelos necesitan las mismas herramientas | ✅ | MCP server único |
| API simple, sin contexto compartido | ❌ | REST + OpenAPI |
| Herramientas que evolucionan sin versionar URLs | ✅ | MCP con negotiation de capabilities |
| Operaciones sensibles (shell, DB, ficheros) | ⚠️ | Solo con las 5 capas de seguridad |
| Tiempo real con suscripciones a cambios | ✅ | MCP Resources con subscribe |
---
Lo Que Nadie te Ha Dicho sobre MCP
MCP resuelve un problema real: cómo hacer que las herramientas sean descubribles por cualquier modelo sin hardcodear schemas.
Pero el silencio sobre la seguridad es ensordecedor.
Cada tutorial que ves de "cómo crear tu primer MCP server" omite la autenticación. Cada ejemplo en GitHub expone Tools sin validación. La comunidad está construyendo sobre un spec que dice "la seguridad es cosa tuya" — y nadie se está ocupando de ella.
*El protocolo no es el problema. La falta de cultura de seguridad al implementarlo, sí. *
Construyes MCP servers porque quieres que tus herramientas trabajen con cualquier modelo. Claude. GPT. Grok Build. El que venga.
Pero si no blindas cada capa, no estás construyendo interoperabilidad. Estás abriendo una puerta sin cerradura.
La pregunta no es si MCP es el futuro. Lo es.
*La pregunta es si vas a construir ese futuro con candados o sin ellos. *
Lee el artículo completo en brianmenagomez.com
Más sobre mis servicios en brianmenagomez.com
Herramientas: Conversor IAE CNAE · Gestorias cerca de ti · Calculadora IRPF

