← Level 6
Level 6· Lektion 6 von 8

Autenticación MCP. OAuth 2.1 y Magic Link sin dolor

Cómo hacer público tu servidor MCP sin pasar bearer tokens por todos lados. El centro de control para cualquier versión SaaS.

Si solo ejecutas tu MCP server para ti, no necesitas autenticación. Confías en ti mismo. En cuanto lo abres a otros usuarios, la auth es obligatoria, si no, el primer atacante random escribe en tu base de datos. En esta lección mostramos cómo se hace limpio hoy.

Por qué OAuth 2.1 y no solo bearer tokens

La vía simple sería: cada usuario recibe una API key, la pone en su claude_desktop_config.json, fin. Muchos MCP servers lo aceptan así. El problema:

  • La key está en texto plano en un fichero de config. Si el usuario pierde el dispositivo, la key se pierde.
  • No puedes revocar la key fácilmente sin que el usuario pida una nueva y la vuelva a meter en todas partes.
  • Sin scopes (solo "todo" o "nada").
  • Sin refresh flow al caducar.

OAuth 2.1 con PKCE resuelve todos esos puntos. El usuario hace clic en "iniciar sesión" una vez, recibe un magic link al email, el cliente lo cambia automáticamente por un token, todo entre bastidores. Casi invisible para el usuario. A ti como operador te da control total.

Qué es distinto en MCP frente a OAuth normal

El OAuth web normal corre en navegador. Los clientes MCP no siempre son navegadores, son apps como Claude Desktop, Codex, Cursor. La spec exige:

  • El MCP server expone su OAuth metadata en /.well-known/oauth-authorization-server.
  • El cliente detecta en la primera tool call que hace falta auth, abre el navegador del usuario, redirige al authorization endpoint.
  • PKCE es obligatorio (code challenge + verifier), porque las apps móviles y desktop no pueden mantener un client secret de forma segura.
  • El flow de refresh-token tiene que estar soportado para que la sesión dure más de una hora sin re-login constante.

El truco del Magic Link

En lugar de gestionar contraseñas, usa magic links. El usuario introduce su email, le envías un email con un enlace de un solo uso, el enlace lleva al auth server y libera el code. El cliente lo cambia por un token.

Pros:

  • Sin flow de password reset (la mayor fuente de soporte en SaaS).
  • El usuario no puede tener "contraseña incorrecta".
  • El email es a la vez login e identificador de cuenta.
  • Funciona cuando el usuario cambia de dispositivo.

Contras:

  • Tienes que tener SMTP bien montado (DKIM, SPF, sender score bueno). Si no, tus magic links acaban en spam.
  • Sin login offline (sin acceso al email, sin login).

Para la mayoría de aplicaciones MCP server el trade-off es claro: magic link gana.

Los cuatro endpoints a construir

  1. GET /.well-known/oauth-authorization-server. Metadata. JSON estático, escribe una vez y olvídate.
  2. GET /authorize. Página de login orientada al usuario. Muestra un formulario de email, envía el magic link, muestra "revisa tu email".
  3. GET /callback (destino del magic link), consume el código de un solo uso, genera el authorization code, redirige al cliente.
  4. POST /token, cambia authorization code por access token + refresh token. Tiene que verificar PKCE.

Eso es. Sin más endpoints. Access token tiene 1 hora de vida, refresh token unos días o semanas.

Scopes

Define al menos tres:

  • read, solo llamar tools de lectura.
  • write, tools de escritura permitidas.
  • admin, tools de gestión (borrar, exportar, cambiar config).

La mayoría de los usuarios reciben read + write. Admin es para ti o casos explícitos.

En la authorization request el cliente lista los scopes deseados. Los muestras en el login ("esta app quiere: leer, escribir"), el usuario acepta o rechaza.

Almacenamiento del token en el cliente MCP

El cliente (Claude Desktop, Codex) suele guardar el token cifrado en el keychain del sistema (Mac) o el credential manager (Windows). Como operador del server no te tienes que preocupar.

Lo que sí debes hacer: comunicar la caducidad de tokens limpiamente. Cuando un token caduca, devuelve un 401 limpio con WWW-Authenticate: Bearer error="invalid_token", y el cliente sabe que tiene que refrescar.

Errores que vas a cometer

  • Olvidar el state parameter. El state de OAuth protege contra CSRF. Sin él un atacante puede redirigirte a un login. Obligatorio.
  • No verificar el PKCE verifier. Si tu server no comprueba el verifier de verdad, PKCE no sirve para nada.
  • Refresh token de vida corta. Si el refresh caduca a la hora, el usuario tiene que re-loguearse constantemente. Refresh no es access. 14 días o más es normal.
  • Sin token revocation. Si un usuario pierde su dispositivo, tiene que poder revocar el token. Construye un endpoint en dashboard.
  • Olvidar el scope check en el handler. Cada handler de tool tiene que verificar que el token aportado tiene el scope necesario. Si no, te saltas todo el trabajo en una línea.

Tenant isolation, el error más importante a evitar

Si tu server tiene varios usuarios, CADA query a la BD tiene que llevar un filtro de tenant_id. El usuario A no debe ver datos del usuario B. Suena obvio pero se olvida casi siempre en algún sitio: en findUnique, en funciones de búsqueda, en batch deletes.

Pattern recomendado: tenant_id como campo obligatorio en cada modelo de BD + scanner CI estático que comprueba que todas las queries Prisma/SQL contienen tenant_id. Un bug aquí es una fuga de datos cruzando fronteras de usuario, el peor error que un SaaS server puede cometer.

Ejemplo mínimo viable

El flow núcleo en pseudocódigo:

POST /authorize  (con email, client_id, code_challenge, state)
  -> generar magic_token (aleatorio), guardar {email, client_id, code_challenge, state, caduca: +15min}
  -> enviar email con enlace: /callback?magic_token=xxx
  -> mostrar página "revisa tu email"

GET /callback?magic_token=xxx
  -> lookup magic_token, invalidar inmediatamente
  -> generar authorization_code (aleatorio, 5min vida)
  -> redirect a client_redirect_uri?code=xxx&state=original_state

POST /token (con code, code_verifier)
  -> verificar code existe, no caducado, no consumido
  -> verificar PKCE: SHA256(code_verifier) == code_challenge original
  -> generar access_token + refresh_token, devolver
  -> marcar code como consumido

Es el flow completo. Todo lo demás es decoración.

Qué necesitas además en producción

Rate limiting en /authorize (si no, spam). Tokens firmados con HMAC (pepper + secret). Logging por intento de login. Dashboard de admin para overview y revocación de tokens. Todo son preocupaciones que se pueden deslocalizar, ningún show-stopper. Puedes empezar con el flow mínimo y añadir.

Sigue

El MCP de StudioMeyer Memory (memory.studiomeyer.io) usa exactamente este pattern. El espejo OSS del pattern de memoria portable está en github.com/studiomeyer-io/local-memory-mcp. La capa auth en sí vive en el repo principal privado pero el pattern está documentado y es portable.

La Lección 7 muestra cómo combinar varios MCP servers autenticados en un setup multi-agente. Es el capítulo de escalado.

Estás leyendo sin cuenta. Login guarda tu progreso para que retomes donde lo dejaste. Iniciar sesión →