
Authorization Server como PDP para acceso tool-scoped en MCP
1) Resumen ejecutivo
En arquitecturas agentic con MCP, el error de seguridad más repetido es validar OAuth correctamente en el borde del servidor, pero no en el borde real de riesgo: la invocación de herramienta (tools/call). Resultado: token “válido” + planner probabilístico = privilegios operativos excesivos.
La estrategia más robusta hoy:
- PDP en emisión (AS/IdP): decide permisos mínimos por intención.
- PEP determinista en ejecución (gateway/runtime): valida JWT (
iss,exp, firma,aud) y aplica autorización exacta porparams.name.
Contrato de seguridad mínimo:
- Recurso ligado en
aud(canonical URL). - Herramienta ligada por identificador exacto (
scopetokenizado o claim estructurado). - Delegación OBO vía token exchange con downscoping monotónico.
Esto reduce confused deputy, sobreprivilegio y abuso inducido por prompt sin depender de “que el LLM se porte bien”.
2) Enmarcado del problema
2.1 Dónde se rompe el modelo de mínimo privilegio
Flujo típico inseguro:
- Cliente obtiene token para “el MCP”.
- Gateway verifica firma y expiración.
- Agente llega a
tools/callcon ese token. - Se autoriza por “ser del servidor”, no por herramienta específica.
Consecuencia: el sistema delega una decisión sensible al comportamiento emergente del planner.
2.2 Riesgos concretos
- Confused deputy: el agente actúa con privilegios superiores al propósito original.
- Blast radius lateral: un token útil para lectura habilita escritura/destrucción en otro contexto.
- Escalada por ambigüedad: matching laxo (
contains) permite falsos positivos de scope. - Enumeración de superficie:
tools/listabierto expone catálogo completo para pivote técnico o error de planificación.
2.3 Qué sí y qué no cubre esta propuesta
Sí cubre:
- autorización determinista por recurso + herramienta;
- control reproducible y auditable;
- denegación fail-closed antes de ejecutar herramientas.
No cubre por sí sola:
- validación semántica profunda de argumentos de negocio;
- gobernanza HITL para acciones irreversibles;
- replay sin controles de sender-constrained token (DPoP/mTLS).
3) Arquitectura de referencia

3.1 Roles
- AS/IdP (PDP): transforma identidad + intención + política en claims firmados.
- Agent Gateway (PEP #1): controla acceso al endpoint del agente (audiencia de agente).
- MCP Gateway (PEP #2): controla
tools/call(audiencia MCP + permisos de tool). - MCP server(s): ejecutan solo tráfico pre-autorizado.
3.2 Invariantes
- Audience strictness:
auddebe contener el recurso canónico del endpoint llamado. - Tool exactness:
params.namedebe coincidir exactamente con permiso emitido. - Monotonicidad en OBO: token downstream nunca amplía privilegios upstream.
- Fail-closed: duda o inconsistencia => DENY.
3.3 Representación de permisos
Opción A — scope plano (rápida de desplegar)
{
"aud": "https://gw.example.com/mcp/payments",
"scope": "mcp:tool:accounts.list mcp:tool:payments.transfer"
}
Requisito: parseo por espacios y comparación exacta por token.
Opción B — claim estructurado (recomendada para multi-recurso)
{
"aud": [
"https://gw.example.com/mcp/payments",
"https://gw.example.com/mcp/crm"
],
"tool_permissions": [
{"rs":"https://gw.example.com/mcp/payments","name":"accounts.list"},
{"rs":"https://gw.example.com/mcp/payments","name":"payments.transfer"},
{"rs":"https://gw.example.com/mcp/crm","name":"crm.getCustomer"}
]
}
Ventaja: evita ambigüedad en aud[] y bloquea uso cruzado de tool en recurso incorrecto.
4) Secuencias de autorización (ALLOW/DENY)
4.1 ALLOW canónico

params.name.- Cliente solicita token para recurso MCP específico (
resource). - AS emite JWT con
audy permisos mínimos por intención. - Agente envía
tools/callconparams.name. - PEP MCP valida JWT +
aud+ permiso exacto. - Si todo coincide, forward al MCP server.
Resultado: ejecución autorizada y trazable.
4.2 DENY por audiencia

aud: el PEP rechaza con 401 invalid_token y no ejecuta herramienta.Token correcto criptográficamente, pero aud no incluye el recurso de ese endpoint.
Resultado: 401 invalid_token antes de evaluar la herramienta.
4.3 DENY por herramienta
aud correcto, pero params.name fuera de permisos emitidos.
Resultado: 403 insufficient_scope sin ejecución.
4.4 OBO/downscope en cadena agentic
Cliente -> agente con token upstream; agente intercambia por token downstream MCP (TTL corto y scopes mínimos).
Resultado: separación de privilegios por salto y reducción de superficie en caso de filtración.
5) Ejemplos prácticos (fallo y control)
Ejemplo A — Prompt injection intenta operación destructiva
Intención legítima: consultar saldo.
Tool permitida: accounts.list.
Planner propone por inyección: payments.transfer.
Evaluación PEP: payments.transfer no está en permisos.
Decisión: DENY determinista.
Ejemplo B — Substring bug en scope
Token trae scope="mcp:tool:payments.transfer.read".
Implementación insegura usa scope.contains("payments.transfer").
Ataque: se autoriza payments.transfer por coincidencia parcial.
Mitigación: tokenizar por espacios y comparar igualdad exacta del identificador esperado.
Ejemplo C — Multi-recurso (aud[]) sin binding (rs,tool)
Token válido para payments + crm, con lista plana de tools.
Riesgo: llamada a tool de payments sobre endpoint crm por error de routing o bug.
Mitigación: exigir tool_permissions con rs explícito y evaluar par (resource_id, tool_name).
Ejemplo D — Token passthrough sin exchange
Agente reusa token amplio del cliente directamente contra MCP.
Riesgo: hereda privilegio transitorio no previsto para el salto downstream.
Mitigación: RFC 8693 token exchange + TTL 60–300s + scopes mínimos por intención.
Ejemplo E — Enumeración en tools/list
tools/list abierto revela herramientas de administración no necesarias.
Mitigación recomendada:
- modo ALLOW+FILTER (solo herramientas autorizadas), o
- DENY total cuando el agente usa catálogo preconfigurado.
6) Implementación técnica (PEP determinista)
6.1 Algoritmo de decisión
verify_signature_and_standard_claims(token)
resource_id = canonicalize(request.host, request.basePath)
if !aud_contains(token.aud, resource_id):
deny(401, invalid_token)
tool_name = request.json.params.name
if !valid_tool_name(tool_name):
deny(400, invalid_request)
allowed = permissions_from(token.scope, token.tool_permissions)
if !allows(allowed, resource_id, tool_name):
deny(403, insufficient_scope)
allow_forward()
6.2 Canonicalización (evitar bypass)
- Recurso: normalizar scheme/host/port/path según contrato único.
- Herramienta: respetar política de case-sensitivity definida; si se impone lowercase, documentarlo y denegar variantes.
- Claims: rechazar dualidad conflictiva (
scopedice ALLOW,tool_permissionsdice DENY o viceversa).
6.3 Códigos de error sugeridos
401 invalid_token: firma/exp/iss/aud inválidos.403 insufficient_scope: tool no permitida.400 invalid_request: JSON-RPC mal formado oparams.nameinválido.
6.4 Telemetría mínima
Por cada tools/call registrar:
iss,sub,client_id,jti(si existe),intent_id.resource_idcanónico evaluado.params.name.- decisión y motivo (
aud_mismatch,tool_denied, etc.). - latencia de verificación.
7) Checklist de implementación
Política y diseño
- [ ] Catálogo de tools clasificado por riesgo (R/O, write, irreversible, admin).
- [ ] Convención de nombres de herramienta única y documentada.
- [ ] Fuente de verdad única para permisos (
scopevs claim estructurado).
Emisión de token (PDP)
- [ ]
resourceRFC 8707 usado explícitamente para tokens MCP. - [ ]
audemitido en forma canónica y verificable. - [ ] Downscope por intención en token exchange (sin ampliación lateral).
- [ ] TTL corto para tokens downstream.
Enforcement (PEP)
- [ ] Validación JWT completa antes de
tools/call. - [ ] Validación
audpor membresía exacta (string/array). - [ ] Match exacto de
params.namecontra permisos. - [ ] Fail-closed ante ambigüedad o claims inválidos.
- [ ]
tools/listfiltrado o denegado.
Hardening
- [ ] Evaluado DPoP/mTLS para reducir replay.
- [ ] Prohibido bearer token en query string.
- [ ] Alerta por picos de denegación en tools críticas.
Pruebas de conformidad
- [ ] ALLOW:
audcorrecta + tool permitida. - [ ] DENY:
audcorrecta + tool no permitida. - [ ] DENY:
audincorrecta + tool permitida. - [ ] DENY: intento de escalada en token exchange.
- [ ] Replay simulation (sin y con sender-constraint).
8) Referencias
- RFC 9700 — OAuth 2.0 Security BCP: https://datatracker.ietf.org/doc/rfc9700/
- RFC 8707 — Resource Indicators: https://www.rfc-editor.org/rfc/rfc8707
- RFC 8693 — OAuth 2.0 Token Exchange: https://www.rfc-editor.org/rfc/rfc8693
- RFC 9068 — JWT Profile for OAuth 2.0 Access Tokens: https://www.rfc-editor.org/rfc/rfc9068
- RFC 7519 — JWT: https://www.rfc-editor.org/rfc/rfc7519
- RFC 6749 §3.3 — Scope syntax: https://www.rfc-editor.org/rfc/rfc6749#section-3.3
- RFC 9449 — DPoP: https://www.rfc-editor.org/rfc/rfc9449
- RFC 9728 — Protected Resource Metadata: https://datatracker.ietf.org/doc/rfc9728/
- RFC 9396 — Rich Authorization Requests: https://datatracker.ietf.org/doc/html/rfc9396
- MCP Spec 2025-11-25 (tools): https://modelcontextprotocol.io/specification/2025-11-25/server/tools
- MCP Spec 2025-11-25 (authorization): https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization
- Keycloak 26.4 release notes: https://www.keycloak.org/2025/09/keycloak-2640-released
- Keycloak MCP authz server guide: https://www.keycloak.org/securing-apps/mcp-authz-server