CodeGym /Cursos /ChatGPT Apps /Pruebas de inicio de sesión y acceso mediante MCP Jam: No...

Pruebas de inicio de sesión y acceso mediante MCP Jam: None, Bearer, OAuth with credentials, Default OAuth

ChatGPT Apps
Nivel 10 , Lección 4
Disponible

1. MCP Jam como laboratorio para la autorización

MCP Jam no es «otra herramienta rara», sino tu banco de pruebas que puede desempeñar el papel de cliente MCP. En esencia, es un emulador del comportamiento de ChatGPT al trabajar con un servidor MCP: sabe leer .well-known/oauth-protected-resource, lanzar el flujo OAuth, acoplar tokens a las solicitudes y mostrar qué salió mal exactamente.

Un punto práctico muy importante: si consigues un flujo de Default OAuth satisfactorio en MCP Jam, estás aproximadamente al 80 % listo para la integración con una ChatGPT App real. Todo lo que hace ChatGPT al vincular la cuenta (linking), Jam ya lo sabe hacer, solo que con registros y controles más transparentes.

En la lección anterior configuramos la autorización básica para nuestro servidor MCP de práctica GiftGenius: elegimos la variante de verificación de token (JWT o introspection), implementamos .well-known/oauth-protected-resource y un middleware que protege las herramientas. Ahora veremos cómo se comporta todo esto en MCP Jam en diferentes modos de autorización.

Nuestro objetivo en esta lección es aprender a:

  • cambiar conscientemente los modos de autorización en Jam (None, Bearer, OAuth with credentials, Default OAuth);
  • entender qué envía exactamente Jam al servidor MCP en cada modo;
  • diagnosticar qué parte del sistema se rompió: MCP Server, Auth Server o los metadatos;
  • comprobar que las herramientas protegidas funcionan solo con token, y las abiertas — también sin él.

2. Nuestro servidor MCP de práctica: qué probamos

Para no hablar en abstracto, recordemos brevemente el contexto. Continuamos con nuestra aplicación de aprendizaje GiftGenius: es una ChatGPT App que ayuda a elegir regalos y muestra al usuario sus pedidos y listas de deseos.

En el lado del servidor MCP ya tenemos:

  • una herramienta abierta, por ejemplo search_gifts — se puede invocar de forma anónima;
  • una herramienta protegida, por ejemplo list_user_orders — debe funcionar solo para un usuario autenticado y exigir el scope mcp:tools.

El servidor puede:

  • publicar .well-known/oauth-protected-resource;
  • verificar el token (JWT o mediante introspection — elegiste un enfoque en la lección anterior);
  • extraer del token sub (id de usuario), scope, aud y pasarlos a los controladores de las herramientas.

Un middleware típico de verificación de token en Node.js/TypeScript puede verse así:

// middleware/auth.ts
export function requireScope(requiredScope: string) {
  return async (req: any, res: any, next: () => void) => {
    const header = req.headers["authorization"];
    if (!header?.startsWith("Bearer ")) {
      res
        .status(401)
        .set(
          "WWW-Authenticate",
          `Bearer realm="mcp", resource_metadata="${process.env.BASE_URL}/.well-known/oauth-protected-resource", scope="${requiredScope}"`
        )
        .json({ error: "unauthorized" });
      return;
    }

    // aquí ya verificas el token (firma, exp, aud, scope...)
    // y pones el resultado en req.user
    next();
  };
}

Este middleware se usará antes de las herramientas protegidas de MCP. Si no hay token, devolvemos 401 y un WWW-Authenticate correcto con resource_metadata, tal y como exige la especificación MCP Authorization. Ya hiciste un análisis detallado de la verificación del token y de las funciones auxiliares en la lección anterior; aquí lo usamos como dado.

3. Modos de autorización en MCP Jam: visión general

En MCP Jam hay varios modos de autorización para conectarse a un servidor MCP. Corresponden a patrones típicos de OAuth: desde la ausencia completa de token hasta un Authorization Code + PKCE completo.

Resumamos brevemente:

  1. None (No Auth) — Jam no añade en absoluto el encabezado Authorization. Es acceso anónimo. Adecuado para servidores MCP abiertos y para comprobar que los recursos cerrados responden correctamente con 401 y WWW-Authenticate.
  2. Bearer Token — Jam añade Authorization: Bearer <token>, que insertas manualmente en la interfaz. Adecuado para comprobaciones rápidas: el token ya se obtuvo en algún lugar (curl, UI de Keycloak), y quieres verificar el comportamiento del recurso MCP.
  3. OAuth with credentials (Client Credentials) — Jam obtiene el token por sí mismo mediante client_credentials en el Auth Server, usando el Client ID y Secret indicados. Es un modo de «cliente confidencial», más parecido a autorización servidor-servidor sin participación del usuario.
  4. Default OAuth (Authorization Code + PKCE) — el modo principal para clientes tipo ChatGPT (public client sin secreto). Jam lee por sí mismo resource_metadata, encuentra el Auth Server, lanza el navegador con /authorize, realiza el flujo PKCE y obtiene un token de usuario.

Para mayor claridad, lo plegamos en una tabla.

Modo en Jam Qué envía Jam Quién obtiene el token Escenario típico
None Sin Authorization Nadie Herramientas anónimas, comprobación 401
Bearer Token Bearer <manual> Tú (curl, UI del IdP) Pruebas de la lógica del Resource Server
OAuth with cred. Bearer <client token> Jam vía client_credentials Herramientas de servicio/administración
Default OAuth Bearer <user token> Jam mediante Authorization Code+PKCE Inicio de sesión de usuario como en ChatGPT

Ahora recorreremos cada modo y veremos cómo pasar por él nuestro servidor MCP GiftGenius.

4. Modo None: comprobamos que el servidor rechaza correctamente

Empecemos con el modo más primitivo: sin autorización.

En MCP Jam eliges tu servidor (por ejemplo, http://localhost:4000/mcp) y, en la configuración de la conexión, estableces el modo de autorización None.

Qué sucede en este caso:

  • Jam establece la conexión MCP;
  • al invocar una herramienta no añade el encabezado Authorization;
  • puedes invocar cualquier herramienta abierta (por ejemplo, search_gifts);
  • al invocar una herramienta protegida (por ejemplo, list_user_orders) tu servidor debe responder con 401 Unauthorized.

Es importante que para ese 401 el servidor añada un WWW-Authenticate correcto. Un ejemplo de respuesta con campos adicionales realm y scope, cercano a lo recomendado por OpenAI y la especificación MCP Authorization:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp",
  resource_metadata="https://giftgenius.example.com/.well-known/oauth-protected-resource",
  scope="mcp:tools"
Content-Type: application/json

{"error": "unauthorized"}

Al ver una respuesta así, Jam entiende: el recurso está protegido, de ahí debe obtener los metadatos (resource_metadata), y qué scopes se esperan. En el modo None simplemente te mostrará un error, pero en el modo Default OAuth irá automáticamente al resource_metadata indicado e iniciará el flujo OAuth.

Desde el punto de vista de la depuración, en el modo None compruebas:

  • que las herramientas abiertas funcionan sin token;
  • que las herramientas protegidas nunca se ejecutan de forma anónima;
  • que el encabezado WWW-Authenticate cumple la especificación (incluye Bearer y resource_metadata).

Parece una comprobación trivial, pero muchísimos problemas empiezan porque el 401 se devuelve sin WWW-Authenticate o con un parámetro incorrecto en él (por ejemplo, el obsoleto resource_metadata_uri en lugar del actual resource_metadata).

5. Modo Bearer Token: prueba rápida de la lógica del Resource Server

El siguiente paso es el modo en el que ya tienes un token válido (obtenido fuera de Jam) y quieres probar precisamente la lógica del Resource Server: si acepta/rechaza correctamente ese token, si trabaja bien con scope y audience y si vincula sub con el usuario de tu servicio.

En MCP Jam cambias el modo a Bearer Token e introduces en el campo del token, por ejemplo:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

Ahora Jam añadirá a cada solicitud MCP el encabezado:

Authorization: Bearer eyJhbGciOi...

Tu servidor MCP recibe la solicitud, pasa por el middleware requireScope("mcp:tools"), decodifica el JWT y verifica los claims. Un código típico de verificación se puede escribir de forma simplificada así:

// auth/verifyToken.ts
import jwt from "jsonwebtoken";

export function verifyToken(header: string) {
  const token = header.replace("Bearer ", "");
  const payload = jwt.verify(token, process.env.JWT_PUBLIC_KEY!);
  // aquí puedes comprobar aud, scope, etc.
  return payload as { sub: string; scope?: string };
}

Y usarlo en el middleware:

// dentro de requireScope
const payload = verifyToken(header);
if (!payload.scope?.includes(requiredScope)) {
  res.status(403).json({ error: "insufficient_scope" });
  return;
}
(req as any).user = { id: payload.sub };
next();

En el modo Bearer puedes experimentar:

  • introducir un token sin el scope requerido y asegurarte de que el servidor responde con 403/401;
  • introducir un token con aud incorrecto y comprobar que el servidor lo rechaza;
  • introducir un token caducado para verificar el error invalid_token.

Este es un modo de «prueba de choque» local de la lógica del Resource Server sin participación del inicio de sesión por UI y PKCE. Todo lo que verificas aquí luego se aplica tal cual a los tokens que ChatGPT o Jam obtendrán en el modo Default OAuth.

6. Modo OAuth with credentials (Client Credentials): token «en nombre de la aplicación»

Ahora — un modo menos común, pero útil para entender: OAuth with credentials, es decir, el grant client_credentials. En Jam indicas:

  • Client ID
  • Client Secret
  • los scopes necesarios (por ejemplo, mcp:tools)

Jam ejecuta una solicitud al token_endpoint de tu Auth Server aproximadamente de esta forma:

POST /oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&
client_id=<ID>&
client_secret=<SECRET>&
scope=mcp:tools

El Auth Server emite un token donde sub suele significar el propio cliente (por ejemplo, sub = "mcp-jam-test-client"), y no un usuario concreto. Jam empieza a usar ese token como un Bearer normal.

Para qué puede ser útil esto en el mundo MCP:

  • herramientas de servicio/administración no vinculadas a un usuario concreto (por ejemplo, exportación de logs, health-check, soporte técnico);
  • comprobar que el servidor MCP distingue entre tokens de usuario y tokens del «cliente», si tu lógica de negocio lo tiene en cuenta.

En el contexto de ChatGPT Apps este modo normalmente no se usa, porque ChatGPT, como public client, no guarda secretos (y un public client por definición no debe tener client_secret). Pero en Jam ayuda a ver la diferencia entre:

  • «Simplemente metí un token ya obtenido» (modo Bearer);
  • «Jam fue a por el token con credenciales del cliente» (OAuth with credentials).

En el servidor de práctica puedes, por ejemplo, crear una herramienta MCP especial admin_list_all_orders, accesible solo con un token con grant_type=client_credentials y el rol correspondiente. No es una parte obligatoria de la lección de hoy, pero sí un experimento útil.

7. Modo Default OAuth: Authorization Code + PKCE completo, como en ChatGPT

Ahora — la estrella principal: Default OAuth. Es el modo más cercano a lo que hace ChatGPT al vincular la cuenta de tu App. El cliente lee resource_metadata, va al Auth Server, abre al usuario la página de inicio de sesión, obtiene un authorization code y lo intercambia por un access token mediante Authorization Code + PKCE S256.

Veamos la secuencia de pasos. Para mayor claridad — un diagrama.

sequenceDiagram
    participant Jam as MCP Jam (Client)
    participant RS as MCP Server (Resource)
    participant PRM as /.well-known/oauth-protected-resource
    participant AS as Auth Server (Keycloak/Auth0)
    
    Jam->>RS: Llamada a la herramienta protegida (sin token)
    RS-->>Jam: 401 + WWW-Authenticate (resource_metadata=PRM)
    Jam->>PRM: GET /.well-known/oauth-protected-resource
    PRM-->>Jam: JSON con resource, authorization_servers, scopes_supported...
    Jam->>AS: GET /authorize?client_id=...&code_challenge=...&scope=...
    Note right of AS: El usuario inicia sesión y otorga consentimiento
    AS-->>Jam: redirección con authorization_code
    Jam->>AS: POST /token (code + code_verifier)
    AS-->>Jam: { access_token, scope, expires_in, ... }
    Jam->>RS: Invocación de tool con Authorization: Bearer <access_token>
    RS-->>Jam: Resultado correcto de la herramienta

Qué debes comprobar en este modo:

  1. Respuesta 401/WWW-Authenticate correcta del servidor MCP. Si el servidor no devuelve resource_metadata o devuelve una URL incorrecta, Jam no podrá leer el PRM ni iniciar el flujo OAuth.
  2. Documento válido .well-known/oauth-protected-resource. Debe contener resource, authorization_servers, scopes_supported y demás correctos para que Jam entienda adónde ir por tokens y qué scopes solicitar.
  3. Configuración correcta del Auth Server.
    • Habilitado Authorization Code Flow con PKCE S256.
    • El Client ID corresponde a lo esperado en el PRM (o se registra mediante DCR — Dynamic Client Registration).
    • El Redirect URI en el Auth Server coincide exactamente con el que usa Jam.
  4. PKCE S256. Jam forma el code_challenge y espera que el Auth Server soporte el método S256. Si PKCE está desactivado o solo se soporta plain, el flujo fallará.
  5. Scopes y audience. El Auth Server debe emitir un token con el aud adecuado y los scopes solicitados (mcp:tools, etc.), y el servidor MCP debe verificarlos.

Como resultado de un Default OAuth exitoso obtendrás:

  • en Jam — una conexión con el servidor MCP en la que la herramienta protegida list_user_orders devuelve los datos correctos precisamente para el usuario con el que iniciaste sesión en el Auth Server;
  • en los logs del Auth Server — un authorize + intercambio de token exitosos;
  • en los logs del servidor MCP — validación correcta del token y extracción de sub.

Para depurar, a menudo ayuda añadir un logger sencillo en el manejador de la herramienta, para asegurarte de que realmente ves el userId del token:

// dentro del manejador de la herramienta MCP list_user_orders
export async function listUserOrders(args: any, context: any) {
  const user = context.user as { id: string };
  console.log("[MCP] listUserOrders for user", user.id);
  // después devuelves los pedidos de este usuario
}

8. Dónde se rompe: diagnóstico por modos

Ahora hablemos de cómo, según los síntomas en MCP Jam, entender dónde está el problema: en el servidor MCP, en el Auth Server o en los metadatos. Esta sección es una especie de checklist de diagnóstico por modos.

Si en el modo None:

Invocas una herramienta protegida y el servidor devuelve:

  • 200 OK y ejecuta la acción incluso sin token — significa que no tienes verificación de token antes de esa herramienta. Debes añadir un middleware o una verificación de scopes.
  • 401, pero sin WWW-Authenticate o con resource_metadata mal formado — Jam no sabrá adónde ir por los metadatos y no podrá iniciar Default OAuth. Corrige el encabezado siguiendo el ejemplo anterior.

Si en el modo Bearer Token:

  • Jam recibe de forma constante 401/403 incluso con un token que estás seguro de que es válido cuando llamas directamente (mediante curl o Postman). Lo más probable es que haya algo mal en la lógica del Resource Server: verificación incorrecta de aud/scope o clave pública errónea para la firma del JWT.
  • Si el token Bearer funciona en Jam, pero luego no funciona en Default OAuth — entonces el problema no está en el servidor MCP, sino en el Auth Server o el PRM: el token obtenido por Default OAuth difiere en scope/aud del que probaste manualmente.

Si en el modo OAuth with credentials:

  • Si Jam no puede obtener el token (error en el paso /token) — busca la causa en la configuración del cliente en el Auth Server (secret incorrecto, client_credentials no permitido o scope denegado).
  • Si hay token, pero el servidor MCP lo rechaza — quizá tu servidor espera un sub de usuario (email/ID de usuario), y en el token solo hay el identificador del cliente. O bien aud/scope no coinciden con lo esperado.

Si en el modo Default OAuth:

Este es el escenario con más trampas. Problemas frecuentes:

  • Redirect URI incorrectos. El Auth Server se queja de invalid_redirect_uri o simplemente no emite el código. Asegúrate de que el URI de Jam está añadido en la configuración del cliente del IdP sin barras sobrantes ni erratas.
  • PKCE ausente o no soportado. Si el Auth Server exige PKCE y Jam (o una versión antigua) no envía code_challenge, o al contrario — Jam envía S256 y el IdP no soporta ese método, verás invalid_request.
  • Scopes no coincidentes. En el PRM declaraste mcp:tools, pero al cliente en el IdP solo se le permite openid, o al revés — Jam pide más scopes de los que el IdP está dispuesto a emitir.
  • Audience (aud) incorrecta. El token se emite con un aud distinto del que espera el servidor MCP (por ejemplo, la URL de otro recurso). El servidor lo rechazará con razón.

Es muy importante aprender a mirar los logs de tres lugares:

  • MCP Jam — errores al analizar el PRM y al hacer solicitudes HTTP al Auth Server;
  • Auth Server — los logs de /authorize y /token te dirán por qué se niega;
  • Servidor MCP — motivos del rechazo del token (invalid_token, insufficient_scope, wrong_audience).

9. Cómo se relaciona con una ChatGPT App real

¿Por qué dedicamos tanto tiempo a jugar con Jam y no vamos directamente al Developer Mode de ChatGPT? Porque Jam es precisamente un banco de pruebas: te da control sobre los modos de autorización y muestra toda la «cocina interna» del flujo.

Cuando ejecutas Default OAuth en Jam y lo llevas al éxito, confirmas de facto:

  • .well-known/oauth-protected-resource del servidor MCP es correcto;
  • el Auth Server (Keycloak/Auth0/…) está configurado correctamente;
  • roles, scopes, audience y claims se ajustan a lo esperado;
  • el servidor MCP sabe verificar el token y asociarlo al usuario.

ChatGPT, conectado al mismo servidor MCP, hará lo mismo: leerá el PRM, irá al Auth Server, obtendrá un token y empezará a invocar herramientas con Authorization: Bearer.

La diferencia es que en ChatGPT ves solo el resultado final («cuenta vinculada con éxito» o «algo salió mal»), mientras que en Jam ves todo el protocolo y puedes entender paso a paso dónde exactamente «salió mal».

10. Mini-práctica: pruebas secuenciales de nuestro servidor MCP GiftGenius

Reunamos todo en un sencillo escenario secuencial que puedes repetir en tu proyecto.

Primero, arrancas tu servidor MCP (por ejemplo, pnpm dev:mcp) y te aseguras de que:

  • escucha en http://localhost:4000/mcp (o tu URL);
  • el endpoint /.well-known/oauth-protected-resource devuelve un JSON correcto;
  • el Auth Server (Keycloak) funciona y tiene configurado un public client para Jam/ChatGPT.

Luego:

  1. Modo None.
    Conectas Jam al servidor MCP sin autorización. Compruebas que:
    • search_gifts funciona;
    • list_user_orders devuelve 401 con un WWW-Authenticate correcto.
  2. Modo Bearer Token.
    Obtienes un access token a través de Keycloak (por UI o curl). Lo pegas en Jam, llamas a list_user_orders y te aseguras de que:
    • con un token válido la herramienta funciona y devuelve los pedidos del usuario concreto;
    • con un token sin mcp:tools o con otro aud — el servidor devuelve error.
  3. Modo OAuth with credentials.
    Si tienes un cliente confidencial: indicas client_id y client_secret en Jam, pones el scope necesario, llamas a una herramienta técnica (por ejemplo, admin_list_all_orders) y compruebas que solo funciona con ese token de servicio.
  4. Modo Default OAuth.
    Activas Default OAuth y llamas a list_user_orders. Jam:
    • recibirá 401 + WWW-Authenticate,
    • leerá el PRM,
    • abrirá el navegador, donde iniciarás sesión en Keycloak,
    • obtendrá el token mediante Authorization Code + PKCE,
    • invocará la herramienta MCP con el token, tras lo cual verás tus pedidos en la respuesta.

Si los cuatro modos funcionaron como se esperaba — enhorabuena, no solo «configuraste algo con Keycloak», sino que realmente entiendes cómo probar y depurar todo el flujo de autorización.

11. Errores típicos al trabajar con MCP Jam y al probar la autorización

En la práctica, estos problemas suelen manifestarse como patrones recurrentes de errores. A continuación — varios escenarios típicos de «cómo no hacerlo», para que los puedas reconocer por los síntomas.

Error n.º 1: esperar que una herramienta protegida funcione en el modo None.
A veces el desarrollador enciende Jam en el modo None, invoca list_user_orders y se sorprende del 401, y luego «por si acaso» quita la verificación del token del servidor. Como resultado, la herramienta MCP empieza a funcionar de forma anónima, lo cual es categóricamente inaceptable para datos personales y escenarios de comercio. El modo None sirve precisamente para comprobar que el servidor rechaza correctamente sin token y devuelve WWW-Authenticate con resource_metadata.

Error n.º 2: encabezado WWW-Authenticate olvidado o incorrecto.
Un caso muy común: el servidor devuelve 401 sin WWW-Authenticate o con el parámetro obsoleto resource_metadata_uri. Jam (como ChatGPT) en ese caso no entiende adónde ir por el Protected Resource Metadata, y Default OAuth simplemente no arranca. La variante mínimamente suficiente es WWW-Authenticate: Bearer resource_metadata="https://.../.well-known/oauth-protected-resource". Los campos realm y scope son opcionales; lo principal es no olvidar el propio resource_metadata.

Error n.º 3: probar solo el modo Bearer e ignorar Default OAuth.
El desarrollador obtiene manualmente un token, lo pega en Jam, ve que todo funciona y da la tarea por resuelta. Pero cuando llega el momento de conectar el ChatGPT real, resulta que .well-known es incorrecto, PKCE no está soportado, el Redirect URI no coincide y el linking falla. Probar el modo Bearer es necesario pero no suficiente. Es imprescindible ejecutar Default OAuth; de lo contrario, no verificarás la mitad de la configuración más importante del Auth Server y del PRM.

Error n.º 4: intentar usar client_credentials donde se necesita un token de usuario.
A veces, en un momento de frustración, el desarrollador activa en Jam el modo OAuth with credentials y empieza a obtener tokens mediante client_credentials, y luego los usa para herramientas de usuario como list_user_orders. Como consecuencia, sub en el token es el client_id, no un usuario real, y la lógica de negocio empieza a comportarse de forma extraña (por ejemplo, mostrar datos «generales» o fallar al intentar buscar un usuario con ese ID). Para escenarios de ChatGPT con usuarios reales se necesita Authorization Code + PKCE (Default OAuth), y client_credentials solo sirve para tareas de servicio.

Error n.º 5: desacuerdo de scopes y audience entre PRM, Auth Server y el servidor MCP.
En .well-known/oauth-protected-resource declaraste que el recurso es https://giftgenius.example.com, y los scopes soportados — ["mcp:tools"]. En el Auth Server, al cliente le emitieron un token sin aud, mientras que el servidor MCP, al verificar el token, espera estrictamente aud = "https://giftgenius.example.com" y la presencia de mcp:tools. Como resultado, el servidor MCP rechaza el token obtenido a través de Default OAuth, y tú gastas medio día buscando «magia». Comprueba siempre que PRM, la configuración del cliente en el IdP y la verificación en el middleware del servidor MCP están alineados respecto a audience y scope.

Error n.º 6: usar una versión antigua de MCP Jam.
La especificación MCP Authorization evoluciona activamente, aparecen nuevos campos (resource_metadata, flujo PKCE mejorado, herramientas de depuración). Si tu versión de Jam es antigua, puede que no entienda los campos recientes o que trabaje con nombres de parámetros obsoletos. Esto provoca bugs surrealistas: lo configuras todo según el último RFC, y Jam simplemente no sabe qué hacer con ello. Antes de desesperarte, asegúrate de que Jam está actualizado a la versión más reciente.

1
Cuestionario/control
Autenticación y acceso, nivel 10, lección 4
No disponible
Autenticación y acceso
Autenticación y acceso
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION