Cómo un servidor MCP llama a tu API en nombre de los usuarios: una estrategia de tokens en producción
Explica la estrategia de tokens salientes para servidores MCP: por qué el passthrough de tokens y M2M fallan, y cómo el intercambio de tokens mantiene los permisos alineados con los usuarios.
YijunDeveloper
Deja de perder semanas en la autenticación de usuarios
Lanza aplicaciones seguras más rápido con Logto. Integra la autenticación de usuarios en minutos y concéntrate en tu producto principal.
En nuestro artículo anterior, compartimos nuestra experiencia general construyendo el servidor MCP remoto de Logto. Este artículo cubre el diseño de la arquitectura y el flujo OAuth en detalle.
Con el servidor MCP como frontera, la autenticación para un servidor MCP remoto tiene dos partes: entrada y salida.
Entrada: el cliente MCP (VS Code, Cursor, etc.) inicia sesión mediante OAuth, obtiene un token de acceso y lo utiliza para acceder a tu servidor MCP
Salida: cuando gestiona llamadas de herramientas, el servidor MCP solicita tu propia API de negocio en nombre del usuario
La parte de entrada está bien definida en la especificación MCP, con muchas discusiones e implementaciones en la comunidad. También escribimos una guía de implementación antes. La parte de salida recibe mucha menos atención: el servidor MCP solo tiene un token para acceder a él mismo. ¿Cómo puede llamar a tu API de negocio en nombre del usuario?
Esta es la pregunta con la que estuvimos luchando mientras construíamos el servidor MCP de Logto. Logto Cloud es un producto B2B multi-tenant típico: un usuario puede pertenecer a múltiples tenants, y los permisos vienen del rol del usuario en cada tenant. La IA no solo necesita actuar como el usuario, también debe llegar al tenant correcto.
Este artículo sigue nuestro proceso real de decisión:
Por qué el servidor MCP debe desplegarse de forma independiente, como un recurso protegido separado
Dos enfoques para llamadas salientes que rechazamos (passthrough de token y token M2M) y sus problemas
El diseño final, intercambio de tokens más token de sujeto, incluyendo cómo los tokens de organización gestionan la multi-tenencia
Algunos principios a seguir si construyes algo similar
El problema principal: el doble rol de un servidor MCP#
Desde la perspectiva de OAuth, un servidor MCP remoto cumple dos roles al mismo tiempo:
Para el cliente MCP, es un servidor de recursos: el cliente debe presentar un token para acceder
Para la API de negocio, es un cliente: presenta un token para acceder a otro servicio
En otras palabras, el servidor MCP juega un rol diferente en cada lado, y cada lado utiliza un token diferente. El token que el cliente MCP obtiene mediante OAuth tiene el servidor MCP como audiencia, por lo que la API de negocio lo rechazará al validar la audiencia.
Así que la parte de salida es esencialmente un problema de delegación: ¿cómo logra el servidor MCP llamar a APIs downstream como el usuario, dentro de los permisos del usuario, sin tener las credenciales del usuario?
Decisión de arquitectura: el servidor MCP como servicio independiente#
Construir el endpoint MCP dentro del servicio de la API de negocio, compartiendo el mismo proceso y stack auth, parece la opción más directa. Después de analizarlo, elegimos despliegue independiente:
Aislamiento de riesgo: cuando tomamos la decisión, el SDK oficial de MCP no estaba listo para producción, y el protocolo evolucionaba rápidamente (el cambio de transporte de SSE a Streamable HTTP es un ejemplo). Logto es un servicio IAM y la disponibilidad del acceso de inicio de sesión es esencial. Con despliegue independiente, si el servidor MCP falla, solo se cae el punto de entrada IA. El servicio principal sigue intacto.
Iteración independiente: el ecosistema MCP cambia semanalmente y los problemas de compatibilidad requieren hotfixes en cualquier momento, mientras que el core tiene un proceso estricto de release con pruebas de regresión. Los despliegues separados evitan que uno frene el ritmo del otro.
Libertad de runtime: un servicio independiente puede elegir el entorno que mejor le convenga. El servidor MCP de Logto ejecuta en Cloudflare Workers: sin estado, escala por solicitud y casi sin ops. La opción embebida no permite esto.
El servidor MCP está desplegado en su propio dominio, mcp.logto.io, sin acoplamiento privado al servicio principal. Si un día queremos open source, no hay impedimento.
La opción embebida tampoco resuelve el problema del token: el endpoint MCP y la API de negocio compartirían el mismo identificador de recurso, así que el token que obtiene el cliente MCP traerá permisos completos de la API. La cuestión de "¿con qué permisos se ejecuta esta llamada?" pasa de intercambio de tokens entre servicios a paso de permisos en proceso, y el problema sigue existiendo. El despliegue independiente nos fuerza a diseñar el límite de permisos explícitamente, que es justo lo que trata el resto de este artículo.
El servidor MCP debe tener su propio identificador de recurso#
La discusión acerca de salidas parte de una pregunta más básica: ¿cuál debe ser la audiencia del token que recibe el cliente MCP?
Reutilizar el identificador de recurso de la API de negocio es la opción más directa: la Management API de Logto ya es un recurso OAuth estándar, así que el cliente MCP podría pedir su token directamente y el servidor MCP lo reenvía tal cual. Muchas implementaciones tempranas de servidores MCP hicieron exactamente esto.
El precio: si la audiencia del token MCP es la API de negocio, la frontera de permisos deja de existir.
Tus herramientas MCP se vuelven decoración: el token accedería toda la API sin pasar por las herramientas
El daño potencial de un token filtrado pasa de "unas pocas operaciones controladas expuestas por el MCP" a "toda la API de gestión"
Auditoría independiente, rate limiting y delimitación de permisos para escenarios MCP no tendrían base
Así que nuestra primera decisión: el servidor MCP es un recurso protegido separado, con su propio identificador de recurso (https://mcp.logto.io) y su propio scope.
Esto separa claramente los límites de permisos y también hace concreta la pregunta de salida: el token solo puede acceder al servidor MCP, entonces ¿qué usa el MCP para llamar la API?
Logto Console es una SPA. La forma de llamar a la Management API es simple: el usuario inicia sesión mediante OAuth en el navegador, obtiene un token con la Management API como audiencia, y el frontend llama la API directamente con ese token.
¿Podría el servidor MCP funcionar como otro tipo de Console? Dejar que el cliente MCP solicite el token de la API de negocio en el login y el servidor MCP simplemente lo reenvía sin conversión:
La ventaja de este enfoque es su extrema simplicidad: el servidor MCP solo reenvía tokens. Pero tiene problemas evidentes:
Primero, entra en conflicto con la decisión sobre el límite de permisos. El passthrough exige que el cliente MCP tenga el token de la API de negocio, justo lo que ya rechazamos.
Segundo, el servidor MCP deja de ser un recurso protegido real. La audiencia de los tokens que recibe no es la suya, así que la validación de audiencia pierde sentido y solo queda "verificar firma y emisor". Esto no cuadra con cómo la especificación MCP define la autorización (el servidor MCP debe actuar como servidor de recursos y declararse por RFC 9728 Protected Resource Metadata). Prácticamente enmascara un servicio backend como una SPA de navegador.
Tercero, los clientes MCP no colaborarán. Un cliente MCP compatible pide tokens según el Protected Resource Metadata, y la audiencia será el servidor MCP. No hay forma estándar de que solicite el token de la API de negocio, así que este camino no funciona en el lado cliente.
Si el token de usuario no funciona, ¿y si usamos la identidad del propio servidor MCP?
Se da al servidor MCP una aplicación M2M (máquina a máquina), obtiene un token con client credentials y llama la API de negocio con él. Esto es común entre servicios internos.
La autenticación de entrada funciona sin problemas: el token del cliente MCP tiene como audiencia el servidor MCP, este lo valida normalmente y después opera con su propio token M2M.
El gran defecto es que los permisos del token M2M no tienen relación con los permisos del usuario:
Exceso de permisos: los del token M2M indican "qué puede hacer el servidor MCP", no "qué puede hacer este usuario". Un usuario solo-lectura podría eliminar aplicaciones porque el token M2M tiene ese permiso
Pérdida de identidad: la API downstream siempre ve la aplicación M2M como llamante y las auditorías no se pueden relacionar con nadie concreto
'Deputy' confundido: el servidor MCP se vuelve un proxy de alto privilegio; cualquiera que logre que ejecute una llamada obtiene sus permisos
M2M es adecuado para escenarios sin contexto de usuario, como tareas programadas o sincronización entre sistemas. Un servidor MCP es distinto: cada llamada la inicia un usuario específico, así que debe ejecutarla con su identidad y permisos.
El diseño final: intercambio de tokens + token de sujeto#
Juntando los aprendizajes de ambos enfoques, obtenemos los requisitos del diseño correcto:
El token que sostiene el cliente MCP solo puede acceder al servidor MCP (lección del enfoque 1)
Al llamar APIs downstream, el servidor MCP debe actuar como el usuario, dentro de sus permisos (lección del enfoque 2)
Esto apunta a un mecanismo estándar: intercambio de tokens (RFC 8693). El servidor MCP toma una credencial que representa al usuario y la intercambia en el servidor de autenticación por un token de la API downstream. La identidad y permisos del usuario se mantienen durante el intercambio.
En Logto, esta "credencial que representa al usuario" viene de la función de suplantación de usuario: el token de sujeto. Es una credencial de vida corta y un solo uso, que el servidor solicita para un usuario específico, señalando que "el siguiente intercambio se hace como este usuario". El usuario no debe crear ni configurar nada, el flujo es automático. El token de sujeto es efímero y caduca tras usarse.
Hay cuatro roles en la arquitectura. El servidor MCP se despliega independientemente en mcp.logto.io:
El flujo de tokens tras una llamada de herramienta:
Paso a paso:
① Validación de entrada. El cliente MCP llama a una herramienta con el token de usuario. La audiencia del token es el identificador del servidor MCP, y su scope es mcp:all. El servidor MCP verifica firma, emisor, audiencia y scope, y obtiene la identidad del usuario. Entrada termina aquí. Este token nunca va downstream.
② Identidad de servicio. El servidor MCP usa sus propias credenciales M2M para obtener un access token con scope dedicado, access:mcp:api. Este scope tiene un solo propósito: llamar al endpoint dedicado del siguiente paso.
③ Petición del token de sujeto, el paso clave de toda la cadena. El servidor MCP llama a POST /api/mcp/subject-tokens, un endpoint abierto para MCP en Cloud, presentando dos credenciales al mismo tiempo:
Header Authorization: el token M2M, que prueba "soy el MCP oficial"
Header x-mcp-user-token: el token de usuario, prueba de "este usuario me ha autorizado y la autorización sigue válida"
Cloud verifica completamente el token de usuario: firma, emisor, expiración, la audiencia debe ser el identificador del MCP, y el scope debe incluir mcp:all. Tras verificar, el userId viene directo del claim sub. El endpoint no permite especificar usuario.
Este diseño previene el abuso de la credencial M2M. Si el endpoint aceptara cualquier userId, cualquiera con la M2M podría suplantar a cualquier usuario. Así, el MCP solo puede intercambiar credenciales para usuarios cuyo token válido esté presente.
El token de sujeto emitido es efímero y single-use. La implementación jamás lo cachea y pide uno fresco en cada uso.
④ Intercambio por tokens de trabajo. Con el token de sujeto, se realiza el intercambio estándar para obtener dos tipos de tokens según convenga:
Token de Cloud API: para operaciones a nivel de usuario como listar y crear tenants
Token de organización: limitado a un tenant objetivo (cada tenant en Logto Cloud es una organización). El token es emitido como el usuario y sus permisos son exactamente los del usuario en ese tenant
⑤ Llamada de salida. Se llama la API de negocio con el token intercambiado y se devuelve el resultado al cliente MCP.
El contexto multitenant se resuelve también en el paso de intercambio: el tenant vive en la capa de intercambio, no de conexión. list_tenants lista opciones con el token de Cloud API, el usuario elige en la conversación, y el MCP intercambia un token de organización para el tenant elegido. Un solo endpoint sirve a todos los tenants, sin despliegues por tenant, y un tenant creado a mitad de conversación está disponible al instante.
Si la comparamos con los fallos de los enfoques anteriores, cada punto está cubierto:
El radio de acción del token de usuario está contenido (lección del enfoque 1): su audiencia es solo el servidor MCP. Aunque se filtre, solo podría llamar a esas herramientas.
El token M2M no puede ser abusado (lección del enfoque 2): ya no llama a la API de negocio directamente y solo prueba la identidad del servicio. Emitir un token de sujeto exige concurrentemente el token válido del usuario; el MCP solo puede intercambiar credenciales de usuarios que le han autorizado realmente. El problema del 'confused deputy' desaparece.
No se almacenan credenciales de usuario: los tokens de sujeto se solicitan al momento y se descartan tras usarse. El MCP solo almacena sus credenciales M2M.
La revocación está acoplada: si el usuario revoca la autorización MCP, el token de usuario deja de ser válido, el check de x-mcp-user-token falla y la cadena downstream se detiene.
Los permisos siempre van alineados al usuario: los tokens intercambiados se emiten como el usuario. Un usuario solo-lectura sigue siéndolo vía MCP y la escalada de privilegios falla a nivel del auth server. Los logs de auditoría muestran la identidad real del usuario.
Visto desde atrás, el uso correcto de la credencial M2M en el diseño final es: prueba "quién soy", mientras la habilidad de "actuar como el usuario" se intercambia en el momento usando la autorización válida del usuario.
En resumen, la estrategia de tokens para un servidor MCP remoto se basa en algunos puntos clave:
Entrada y salida son dos tramos independientes de autenticación, y la audiencia del token MCP debe ser el propio MCP
La salida se resuelve por intercambio de tokens: si tu servidor de auth permite intercambiar directamente el token de entrada, el intercambio estándar basta; si no (por ejemplo, el de Logto requiere un token de sujeto como input), usa una función de suplantación para iniciar el intercambio desde el servidor
Multi-tenencia no cambia el mecanismo. El contexto organizacional es un parámetro del intercambio y los productos single-tenant simplemente lo omiten
La IA actúa en nombre del usuario, así que los permisos del token downstream deben restringirse a ese usuario
Un test sencillo: imagina que el token que sostiene el cliente MCP se filtra. Todo lo que debe poder hacer el atacante es llamar a esas herramientas del MCP, siempre dentro de los permisos del usuario real. Si pudiera llegar a la API completa directamente, la frontera de permisos estaría rota.
El ecosistema MCP sigue evolucionando rápido. La auth de entrada está bien resuelta por la especificación, pero "cómo llama downstream el MCP" aún depende de cada equipo. Esperamos que nuestra experiencia sea una referencia útil.