OAuth 2.1 para MCP: cómo proteger servidores remotos sin romper los clientes
Un servidor MCP remoto que lee documentos o ejecuta tools no puede confiar en que el cliente sea conocido. OAuth 2.1 no es un botón de login: es el contrato que descubre identidad, limita scopes y permite validar cada llamada sin convertir la autorización en un prompt.
Un servidor MCP remoto que lee documentos o ejecuta tools no puede confiar en que el cliente sea conocido. OAuth 2.1 no es un botón de login: es el contrato que descubre identidad, limita scopes y permite validar cada llamada sin convertir la autorización en un prompt.
OAuth 2.1 para MCP es el mecanismo para que un cliente obtenga un token con permiso limitado y un servidor MCP remoto compruebe ese token antes de exponer tools, recursos o acciones. En MCP, el servidor protegido es un resource server; el host del agente es el OAuth client; y tu proveedor de identidad emite los access tokens.
La propiedad evergreen es que el patrón no depende de un host concreto. Cambiarán SDKs y pantallas de consentimiento; seguirán siendo necesarios un resource identifier estable, metadata verificable, PKCE, token validation y una política de autorización que no viva dentro del modelo.
Checklist
Flujo OAuth 2.1 de un servidor MCP remoto
1. El cliente llama al endpoint MCP sin token o con token insuficiente. El servidor devuelve `401 Unauthorized` y un `WWW-Authenticate: Bearer` que apunta a su Protected Resource Metadata (PRM).
2. El cliente descarga el documento PRM. Ahí descubre el `resource` que debe aparecer como audiencia, el authorization server permitido y los scopes que el recurso entiende. Si el `resource` del JSON no coincide exactamente con el recurso pedido, debe rechazarlo.
3. El cliente descubre los endpoints OAuth u OpenID Connect del issuer, registra su identidad por pre-registro o CIMD cuando esté disponible y abre Authorization Code con PKCE. PKCE evita que otro proceso intercepte y canjee el código de autorización.
4. Tras consentimiento, el authorization server emite un access token dirigido a tu resource identifier. El cliente repite la llamada MCP con `Authorization: Bearer …`. El servidor valida firma o introspección, `iss`, `aud`, `exp`, scopes y cualquier claim de tenant antes de despachar una tool.
5. Cada tool ejecuta autorización propia, registra actor, cliente, tool, recurso y resultado, y devuelve un error de autorización seguro cuando corresponda. No guardes el access token en trazas, mensajes de error ni contenido de tool.
¿Te está sirviendo? Hay una dosis cada semana
Te resumo herramientas de IA para devs, agentes, MCP, seguridad y workflows en un email de 5 minutos. En español y sin ruido.
Suscribirme gratisProtected Resource Metadata: la pieza que suele faltar
Protected Resource Metadata es un JSON servido por el resource server, no por el proveedor de login. Según RFC 9728 y MCP, publica el identificador del recurso y los authorization servers autorizados. En una ruta MCP como `https://mcp.acme.test/remote`, el cliente puede buscar `https://mcp.acme.test/.well-known/oauth-protected-resource/remote` o seguir el `resource_metadata` del challenge, que debe tener prioridad.
Puntos a revisar
Lo que conviene comprobar
Evita meter aquí una lista fantasiosa de permisos. `scopes_supported` documenta lo que el servidor puede pedir; el challenge de una request concreta puede exigir un conjunto más preciso y el cliente debe tratar ese challenge como autoridad para ese intento. Es una forma de pedir consentimiento incremental sin entregar `admin:*` al primer clic.
Un ejemplo mínimo y explícito podría ser el siguiente. Los nombres de scopes son tuyos: diseña verbos y dominios que alguien de seguridad pueda revisar, no copias de los nombres de tools.
<div style="margin:28px 0;border:1px solid #dbe3ef;border-radius:12px;overflow:hidden;background:#0f172a;"> <div style="padding:10px 14px;background:#111827;color:#cbd5e1;font:13px Consolas,monospace;">/.well-known/oauth-protected-resource/mcp</div> <pre style="margin:0;padding:18px;overflow:auto;color:#e5e7eb;font:13px/1.55 Consolas,monospace;"><code>{ "resource": "https://mcp.example.com/mcp", "authorization_servers": ["https://login.example.com"], "scopes_supported": [ "issues:read", "issues:write", "deployments:read" ], "resource_name": "Example engineering MCP" }</code></pre> </div>
Implementación Node: challenge, metadata y guard de token
El SDK MCP puede encargarse del transporte y del registro de tools, pero el borde HTTP debe seguir devolver metadata y rechazar tokens inválidos antes de llegar al modelo o a los sistemas internos. Este ejemplo usa Express y `jose` para mostrar el contrato; adapta los endpoints y claims a tu proveedor de identidad. En producción, cachea JWKS respetando sus cabeceras y mantén las URLs de issuer y audiencia en configuración revisada, no en input del usuario.
<div style="margin:28px 0;border:1px solid #dbe3ef;border-radius:12px;overflow:hidden;background:#0f172a;"> <div style="padding:10px 14px;background:#111827;color:#cbd5e1;font:13px Consolas,monospace;">auth-boundary.mjs</div> <pre style="margin:0;padding:18px;overflow:auto;color:#e5e7eb;font:13px/1.55 Consolas,monospace;"><code>import express from "express"; import { createRemoteJWKSet, jwtVerify } from "jose"; const app = express(); const resource = "https://mcp.example.com/mcp"; const issuer = "https://login.example.com"; const jwks = createRemoteJWKSet(new URL(`${issuer}/.well-known/jwks.json`)); app.get("/.well-known/oauth-protected-resource/mcp", (_req, res) => { res.type("application/json").send({ resource, authorization_servers: [issuer], scopes_supported: ["issues:read", "issues:write"], }); }); async function requireScope(req, res, next) { const token = req.get("authorization")?.replace(/^Bearer\s+/i, ""); if (!token) { res.set("WWW-Authenticate", `Bearer resource_metadata="${resource.replace("/mcp", "/.well-known/oauth-protected-resource/mcp")}", scope="issues:read"`); return res.sendStatus(401); } try { const { payload } = await jwtVerify(token, jwks, { issuer, audience: resource }); const scopes = String(payload.scope || "").split(" "); if (!scopes.includes("issues:read")) return res.sendStatus(403); req.actor = { subject: payload.sub, tenant: payload.tenant_id }; return next(); } catch { return res.sendStatus(401); } } app.post("/mcp", requireScope, mcpHttpHandler);</code></pre> </div>
No copies este fragmento sin decidir si tu access token es JWT, opaco o ambos. Un token opaco normalmente se valida por introspección contra el authorization server; un JWT se valida contra claves públicas confiables. En ambos casos, la audiencia debe ser el resource identifier de tu MCP, no el nombre genérico de tu producto.
No prometas soporte universal antes de probar hosts reales. Algunos clientes aún llegarán con capacidades antiguas. Publica claramente qué versiones, mecanismo de registro y scopes admites; ofrece el fallback mínimo sin bajar la validación del token por «compatibilidad».
Errores que rompen autorización MCP
- Proteger solo la pantalla de consentimiento y dejar `/mcp` sin validar `Authorization` en cada request.
- Aceptar cualquier issuer que aparezca en un JWT o construir el JWKS URL con una claim no confiable.
- Comprobar firma y expiración, pero omitir audiencia, scopes, tenant y autorización del recurso concreto.
- Usar una API key global como identidad del usuario y registrar todas las acciones como si las hiciera el servidor.
- Devolver access tokens, authorization codes, cabeceras Bearer o datos de consentimiento en logs de trazas.
- Entregar `write` al conectar el servidor aunque el usuario solo quiera explorar datos de lectura.
- Confiar en que el modelo no invocará una tool peligrosa si el system prompt dice que tenga cuidado.
Checklist
Conclusión
La autorización MCP bien hecha no se nota cuando todo va bien: el host descubre la identidad necesaria, obtiene permiso mínimo y la tool funciona. Se nota cuando alguien conecta un cliente nuevo, intenta reutilizar un token contra otro recurso o pide una acción que no le corresponde; ahí el servidor debe fallar de forma predecible y auditable.
Mi recomendación es empezar con un solo recurso remoto y dos scopes de lectura/escritura, no con un catálogo enorme de permisos. Publica PRM, valida `aud` e `iss`, aplica autorización de negocio en cada tool y escribe los tests hostiles antes de abrir el servidor a más clientes. Es menos vistoso que una demo de agente, pero es lo que evita convertir MCP en una llave maestra.
Preguntas frecuentes
¿Qué es OAuth 2.1 para MCP?
Es el patrón de autorización que permite a un cliente MCP obtener un access token limitado y a un servidor MCP remoto validarlo antes de exponer tools o recursos protegidos.
¿Necesita OAuth un servidor MCP local por STDIO?
Normalmente no. Un servidor local puede usar credenciales del entorno o de una librería local; OAuth está pensado sobre todo para transportes HTTP remotos donde cliente y servidor no comparten una frontera de confianza.
¿Qué es Protected Resource Metadata en MCP?
Es un documento JSON del servidor MCP que declara el resource identifier, los authorization servers y scopes. El cliente lo descubre desde `WWW-Authenticate` o una ruta `/.well-known/` para iniciar OAuth correctamente.
¿Basta con validar la firma del JWT?
No. También debes validar issuer, audiencia, expiración y scopes, y luego aplicar autorización de negocio por usuario, tenant y recurso concreto en la tool.
¿Debo usar Dynamic Client Registration en MCP?
Puede ser un fallback de compatibilidad. La especificación actual prefiere pre-registro cuando existe relación previa y Client ID Metadata Documents cuando cliente y servidor no se conocen de antemano.
¿OAuth protege contra prompt injection?
No directamente. OAuth limita quién puede invocar capacidades; sigue siendo necesario tratar contenido externo como no confiable, validar argumentos y exigir aprobación para efectos sensibles.
Cómo proteger un servidor MCP remoto con OAuth 2.1
- Delimitar recurso. Define una URL HTTPS estable para el endpoint MCP que será la audiencia esperada del token.
- Diseñar scopes. Separa lectura, escritura y acciones de alto impacto; evita permisos globales basados en nombres de tools.
- Publicar metadata. Sirve Protected Resource Metadata con resource exacto, issuer permitido y scopes soportados.
- Emitir challenge. Devuelve 401 con `WWW-Authenticate` y `resource_metadata` cuando no haya token o falte scope.
- Configurar OAuth. Usa Authorization Code con PKCE, discovery OAuth/OIDC y redirect URIs registrados con coincidencia exacta.
- Validar access token. Comprueba firma o introspección, issuer, audiencia, expiración, scopes y claim de tenant en cada llamada.
- Autorizar tool. Evalúa usuario, tenant, rol, ID de recurso y estado de negocio antes de llamar a sistemas aguas abajo.
- Auditar sin secretos. Registra actor, client ID, tool, recurso y resultado; redacta token, código y cabeceras.
- Probar denegaciones. Añade casos de token de otra audiencia, scope insuficiente, issuer falso, tenant cruzado y mutación sin aprobación.
Fuentes y referencias
También te puede interesar
MCP en producción: seguridad, permisos y supply chainMCP outputSchema y structuredContent para agentesPrompt injection en agentes de IAMCP Apps: UI interactiva para toolsOpenTelemetry GenAI para agentesRecibe una lectura semanal de herramientas IA para devs
Cada semana te resumo herramientas de IA para devs, agentes, MCP, seguridad y workflows en un email de 5 minutos. En español y sin ruido.
Suscribirme gratis