Saltar al contenido

MCP

Gobernar servidores MCP con RFC 9728 y RFC 8707 — lo que realmente funciona

Por Olivares AI 11 min de lectura

Conectas Claude Code a un servidor MCP. El servidor requiere autenticación. Tu cliente envía una petición; el servidor responde con un 401 Unauthorized y una cabecera WWW-Authenticate: Bearer que incluye una URL resource_metadata. Lo que sucede a continuación es un flujo OAuth 2.1 definido por tres RFC y un conjunto de extensiones específicas de MCP que, en conjunto, resuelven uno de los problemas más complejos en seguridad de agentes: asegurar que un token obtenido para hablar con este servidor MCP no pueda reutilizarse contra aquel otro.

Este artículo traza el flujo completo — lo que dice la especificación, lo que los RFC realmente aportan y dónde se esconden las trampas de seguridad en la práctica.

El flujo: de un 401 a un token vinculado al recurso

El modelo de autorización MCP (revisión 2025-11-25, que la release candidate — congelada el 2026-05-21 — mantiene sin cambios de cara a la spec final prevista para el 2026-07-28) es un proceso en dos fases. La Fase 1 es detección: el 401 con WWW-Authenticate: Bearer resource_metadata="..." indica al cliente que este servidor está protegido con OAuth y dónde encontrar sus metadata. La Fase 2 es acceso autorizado: consumir las metadata, descubrir el authorization server, obtener un token vinculado a este servidor concreto y utilizarlo.

Los pasos en detalle:

  1. 401 + WWW-Authenticate: El servidor MCP rechaza una petición sin autenticar. El parámetro resource_metadata en el challenge apunta al documento de Protected Resource Metadata del servidor.

  2. Obtención de PRM (RFC 9728): El cliente hace GET a la URL /.well-known/oauth-protected-resource. La respuesta es un documento JSON que declara la URI canónica del recurso, el o los authorization servers que lo protegen y los scopes que el recurso soporta. El cliente valida que el campo resource del documento coincida con el servidor al que pretendía acceder — una discrepancia es una señal de suplantación y debe rechazarse.

  3. Descubrimiento del AS (RFC 8414): Usando la URL del issuer del array authorization_servers del PRM, el cliente recorre los candidatos well-known (/.well-known/oauth-authorization-server, después /.well-known/openid-configuration) para obtener las metadata del authorization server. El issuer en el documento devuelto debe ser byte-identical al issuer con el que se construyó la URL well-known — no “equivalente tras normalización”, no “suficientemente parecido”. Idéntico byte a byte.

  4. Obtención del token (RFC 8707): El cliente solicita un token al token endpoint del AS, incluyendo resource=<URI canónica del servidor> tanto en la petición de autorización como en la de token. Esto vincula la audiencia del token a este servidor MCP concreto. El AS emite un token cuya audiencia es ese recurso, y el cliente lo presenta al servidor.

  5. Acceso autorizado: El cliente usa el token para llamar a los métodos de introspección de solo lectura del servidor MCP (tools/list, resources/list, etc.). El token demuestra que el cliente está autorizado; el resource indicator demuestra que el token se emitió para este servidor.

Lo que RFC 9728 realmente aporta

RFC 9728 (Protected Resource Metadata) es un mecanismo de descubrimiento. Responde a la pregunta: “¿qué authorization server protege este recurso y qué espera?” No autentica al servidor, no valida el token ni aplica control de acceso. Esas son preocupaciones separadas.

El documento PRM tiene un campo obligatorio (resource) y un conjunto de campos opcionales, de los cuales authorization_servers es el más relevante. La especificación MCP refuerza la opcionalidad del RFC: al menos un authorization server es obligatorio. Un documento PRM que no liste ninguno se trata como error.

El campo resource es la URI canónica del recurso protegido. El cliente debe compararla con el servidor al que pretendía contactar y rechazar cualquier discrepancia:

// RFC 9728 §3.3: el valor resource del PRM DEBE identificar el recurso
// protegido al que el cliente se dirige — comparacion directa de cadenas
// contra el resource indicator al que este cliente vincula sus tokens.
if prm.Resource != c.resource {
    return authServerMetadata{}, fmt.Errorf(
        "mcp: oauth: protected resource metadata declares resource %q, "+
        "expected %q (RFC 9728 §3.3 reject)", prm.Resource, c.resource)
}

Esta comprobación previene una clase de ataque en la que un documento PRM malicioso o mal configurado afirma representar a un recurso diferente. La comparación no se normaliza — es una coincidencia directa de cadenas contra la URI canónica del recurso que el cliente calculó para la URL del servidor que le proporcionaron.

Resource indicators de RFC 8707 — por qué importa la vinculación del token

Sin resource indicators, un access token obtenido de un AS podría usarse en cualquier resource server que ese AS proteja. Si tienes dos servidores MCP — digamos, un servidor de documentación de solo lectura y un sandbox de ejecución de código — ambos detrás del mismo proveedor de identidad, un token obtenido para uno podría presentarse al otro. Esto es un problema de confused deputy.

RFC 8707 lo resuelve añadiendo un parámetro resource a las peticiones de autorización y de token. El AS emite un token cuya audiencia es explícitamente esa URI de recurso. Un resource server que valide correctamente rechaza un token cuya audiencia no coincida con su propia identidad.

En la práctica, la petición de token incluye el resource indicator junto con el grant:

form := url.Values{
    "grant_type": {"client_credentials"},
    "resource":   {c.resource}, // RFC 8707 — vincular el token a la audiencia
}

Esto aparece en todos los tipos de grant que usa el conector: client credentials, redención de authorization code y rotación de refresh token. El resource indicator no es opcional — está presente en cada petición de token, de modo que cada token queda vinculado a su audiencia por construcción.

La comprobación byte-identical del issuer

La validación más crítica para la seguridad en todo el flujo es también la más sencilla de enunciar y la más fácil de implementar mal: el valor del issuer en el documento de metadata del AS debe ser byte-identical al issuer que el cliente usó para construir la URL well-known.

No igual ignorando mayúsculas. No equivalente tras normalizar el esquema. No lo mismo tras eliminar una barra final. Idéntico byte a byte. RFC 8414 sección 3.3 es explícito al respecto, y la especificación MCP hereda el requisito.

// discoverASMetadata: RECHAZA un documento cuyo issuer no sea
// BYTE-IDENTICAL al issuer para el que se obtuvo (RFC 8414 §3.3).
// Un documento con discrepancia es una senal de suplantacion.
if as.Issuer != issuer {
    return authServerMetadata{}, fmt.Errorf(
        "mcp: oauth: AS metadata at %s declares issuer %q, "+
        "expected %q (RFC 8414 §3.3 reject)", cand, as.Issuer, issuer)
}

La razón de tanta rigidez: un atacante que controle DNS o se sitúe en la ruta de red puede servir un documento de metadata que apunte a su propio token endpoint mientras afirma ser un issuer legítimo. Si el cliente normalizase el issuer antes de comparar, https://auth.example.com y https://AUTH.example.com coincidirían — y el documento del atacante sería aceptado. La comparación byte-identical cierra esta vía.

La misma disciplina se traslada a RFC 9207 (validación del issuer en la respuesta de autorización). Cuando el cliente inicia un flujo de authorization code, registra el issuer de las metadata del AS ya validadas. En la redirección de vuelta, el parámetro iss en la respuesta debe coincidir con ese valor registrado — de nuevo, byte a byte, sin normalización — antes de que el authorization code se redima. Esta es la defensa contra mix-up: sin ella, un AS malicioso podría interceptar el code y hacer que el cliente lo redima en el token endpoint del atacante.

Identificación del cliente: CIMD sustituye a DCR

La especificación MCP define un orden de prioridad para cómo un cliente se identifica ante el authorization server:

  1. Credenciales pre-registradas — el operador aprovisiona un client_id y client_secret de antemano
  2. CIMD (Client ID Metadata Documents) — el cliente aloja un documento JSON en una URL HTTPS; esa URL es el client_id
  3. Dynamic Client Registration (RFC 7591) — el cliente se registra a sí mismo en el registration endpoint del AS
  4. Solicitar al usuario — no aplicable para agentes headless

DCR está deprecado en la release candidate de la spec final prevista para el 2026-07-28, en favor de CIMD. El motivo es operativo: DCR crea estado persistente de cliente en el authorization server. Cada agente que se registra deja detrás un par client_id/client_secret que el AS debe almacenar, y nadie los rastrea ni los revoca. Para una flota de agentes, esto es proliferación de credenciales sin control.

CIMD invierte el modelo. El cliente aloja un documento en una URL que controla. El AS obtiene el documento cuando necesita validar al cliente, lo cachea según las cabeceras HTTP de cache y no almacena nada permanentemente. Rotar claves es actualizar el documento. Decomisionar un cliente es eliminar el documento. No se acumulan registros huérfanos en el AS.

La contrapartida: CIMD requiere que el cliente exponga un endpoint HTTPS. Para un plano de gobierno self-hosted, esto es natural — el plano ya ejecuta servicios HTTPS. Para una herramienta CLI en el portátil de un desarrollador, lo es menos, por lo que las credenciales pre-registradas siguen siendo la primera opción en el orden de prioridad.

Una identidad CIMD no puede portar un secreto compartido (la URL del documento es pública — un secreto en ella sería una fuga de credenciales). La autenticación del cliente utiliza private_key_jwt (RFC 7523): el cliente firma un JWT de corta duración con una clave privada cuya contraparte pública se publica en el campo jwks del documento CIMD.

La regla de “nunca pasar tokens”

Una defensa estructural que es fácil pasar por alto: el conector solo utiliza tokens que ha obtenido él mismo, para un servidor concreto, a través del flujo de descubrimiento descrito arriba. Nunca acepta un token de un tercero y lo reenvía a un servidor MCP. Nunca lee un token de una petición entrante y lo pasa.

Esta es la defensa contra confused deputy a nivel de protocolo. Si un cliente reenviase tokens recibidos, un atacante podría presentar un token con privilegios bajos y conseguir que el cliente lo reenviase a un recurso con privilegios altos (o viceversa — extraer un token de alto privilegio engañando al cliente para que lo presentase a un servidor controlado por el atacante). Al obtener sus propios tokens y no tocar los de nadie más, el conector no puede actuar como relay de tokens.

El token passthrough no solo está desaconsejado — es estructuralmente imposible. El cliente HTTP del conector para flujos OAuth es independiente de cualquier handler de peticiones entrantes. No existe un code path que lea un bearer token de una petición entrante y lo escriba en una saliente.

SSRF: la defensa contra DNS rebinding

Cada URL de metadata y de token endpoint que el conector obtiene pasa por una protección SSRF en dos capas. La primera es una comprobación pre-flight: la URL debe ser HTTPS (excepto loopback para desarrollo local) y no debe ser una dirección IP reservada literal.

La segunda es una comprobación en el momento de la conexión que cierra el TOCTOU de DNS rebinding. Un hostname que resuelve a una IP pública durante la comprobación pre-flight podría revincularse a una IP privada en el momento de abrir el socket. El cliente HTTP del conector instala una función net.Dialer.Control que inspecciona la IP concreta resuelta en el momento de la conexión y rechaza cualquier dirección reservada. Esta es la comprobación autoritativa — el pre-flight es un rechazo rápido para casos obvios, pero la comprobación en el momento de la conexión es la que importa.

Scope step-up

Un servidor MCP puede responder a una petición con WWW-Authenticate: Bearer error="insufficient_scope" scope="mcp:tools:list mcp:resources:read". La especificación (SEP-835/SEP-2350) define cómo lo gestiona el cliente: calcular la unión de los scopes previamente solicitados y los scopes que el servidor acaba de exigir, y después adquirir un nuevo token con ese conjunto ampliado. La unión preserva los permisos previamente otorgados y añade los nuevos. El step-up ocurre una sola vez — un segundo challenge de insufficient-scope sobre la misma petición no se reintenta, para prevenir bucles infinitos.

El servidor puede ser stateless en su challenge: nombra solo los scopes que necesita la operación en curso, no el conjunto completo que el cliente podría haber solicitado antes. La acumulación del lado del cliente hace que esto funcione sin que el servidor rastree el historial de scopes por cliente.

Lo que esto significa en la práctica

El flujo OAuth para servidores MCP está bien especificado y, cuando se implementa con rigor, aborda las superficies de ataque reales: reutilización de tokens entre servidores, suplantación de metadata, SSRF por DNS rebinding y proliferación descontrolada de credenciales de cliente. La comprobación byte-identical del issuer, el resource indicator en cada petición de token y la regla estructural de no-passthrough son las defensas centrales. No son funcionalidades que se puedan implementar a medias — cada una es una comprobación rígida que falla de forma cerrada.

El problema más difícil es operativo. Los equipos que despliegan servidores MCP necesitan:

  • Publicar un documento PRM válido en /.well-known/oauth-protected-resource con un campo resource que coincida con la URI canónica de su servidor
  • Usar un AS que soporte resource indicators — muchos proveedores de identidad aún no lo hacen, o tratan el parámetro resource como informativo en lugar de vinculante de audiencia
  • Migrar de DCR a CIMD antes de que la deprecación se convierta en eliminación, o pre-registrar credenciales explícitamente
  • Fijar las credenciales a un issuer para prevenir la reutilización silenciosa entre issuers cuando cambie la topología del AS

La documentación del conector MCP cubre la configuración operativa, y el modelo de seguridad describe cómo el token vinculado al recurso encaja en el mapa de acceso general.

Artículos relacionados

Preguntas frecuentes

¿RFC 9728 garantiza que el servidor MCP al que me conecto es legítimo?

No. RFC 9728 permite al cliente descubrir qué authorization server protege un recurso y qué scopes requiere, pero es metadata sobre el recurso, no prueba de su identidad. Un certificado TLS demuestra el hostname del servidor; PRM indica cómo autenticarse ante él. Son complementarios: PRM sin verificación TLS es descubrimiento contra un host no verificado, y TLS sin PRM deja al cliente adivinando cómo obtener un token.

¿Por qué se ha deprecado Dynamic Client Registration en la especificación MCP si todavía funciona?

DCR crea estado persistente de cliente en el authorization server: un client_id y client_secret que el AS debe almacenar y gestionar. Para una flota de agentes headless esto se convierte en un problema de proliferación de credenciales sin control: cada agente se registra a sí mismo y nadie rastrea ni rota esos registros. CIMD (Client ID Metadata Documents) lo sustituye con un documento alojado que el cliente controla y que el AS obtiene bajo demanda — sin estado persistente en el AS, sin registros huérfanos, y la rotación de claves es una actualización del documento en lugar de un nuevo registro.

Mira a qué pueden llegar tus agentes

Olivares AI es la plataforma abierta y self-hosted para tu parque de IA. Despliégalo en tu propia infraestructura y obtén el mapa de acceso que tus equipos de seguridad y plataforma llevan pidiendo.