FastMCP 4.1.2 obliga a fijar el emisor OAuth
Actualizar FastMCP no activa por sí solo la protección OAuth: hay que configurar el emisor de las credenciales.
El problema no está en el token, sino en quién lo recibe
Un cliente MCP que usa credenciales OAuth de máquina necesita pedir tokens a un authorization server. Si el servidor MCP al que se conecta puede influir en el descubrimiento OAuth, el cliente debe comprobar que no va a presentar sus credenciales ante un emisor distinto del esperado. Ese es el hueco que FastMCP 4.1.2 intenta cerrar para los flujos de credenciales de cliente y private_key_jwt. (github.com)
Esto importa especialmente cuando un agente tiene acceso a servicios que no son de juguete. Un MCP autoalojado puede terminar llamando a una API de Laravel, consultando repositorios Git, ejecutando tareas contra Docker o entrando en piezas del homelab. Si ese agente reutiliza una identidad de máquina, entregar el secreto al servidor de autorización equivocado es un problema bastante más serio que un fallo de autenticación.
Actualizar no activa la protección
La parte poco intuitiva es que instalar FastMCP 4.1.2 no basta. La protección solo entra en funcionamiento cuando se configura explícitamente issuer en ClientCredentialsOAuthProvider o en PrivateKeyJWTOAuthProvider. Sin ese valor, la compatibilidad se mantiene, pero el cliente todavía acepta el servidor de autorización encontrado durante el descubrimiento. (github.com)
La documentación oficial del SDK de Python marca además la omisión de issuer como obsoleta y avisa de que será obligatorio en la versión 3.0. La conclusión práctica es sencilla: esta actualización merece una revisión de configuración, no solo un cambio de versión en el lockfile. Si el proveedor de OAuth está oculto en un servicio de agentes, una tarea de CI o un contenedor auxiliar, conviene localizarlo antes de desplegar.
La configuración que revisaría
issuer debe ser la URL identificadora del authorization server que emitió el client_id y el client_secret. La referencia correcta es el valor issuer publicado por su documento /.well-known/oauth-authorization-server, no una URL aproximada elegida a mano. Cuando el descubrimiento del MCP apunta a otro emisor, el flujo se detiene con OAuthFlowError en lugar de construir una petición de token con esas credenciales. (github.com)
Este es un ejemplo mínimo basado en la interfaz documentada. El almacenamiento en memoria sirve para entender la configuración, pero no debería ser la elección final para un proceso que necesite conservar tokens entre reinicios.
import os
from mcp.client.auth.extensions.client_credentials import (
ClientCredentialsOAuthProvider,
)
oauth = ClientCredentialsOAuthProvider(
server_url="https://mcp.interna.example/mcp",
storage=token_storage,
client_id=os.environ["MCP_CLIENT_ID"],
client_secret=os.environ["MCP_CLIENT_SECRET"],
issuer="https://auth.interna.example",
scope="mcp.read mcp.write",
)El proveedor usa client_secret_basic por defecto para la petición al endpoint de tokens. Si el authorization server exige que las credenciales vayan en el cuerpo del formulario, la opción documentada es token_endpoint_auth_method="client_secret_post". No cambiaría ese ajuste por costumbre: debe coincidir con el método admitido por el servidor de autorización. (github.com)
Qué cambia en un MCP de casa
En una instalación propia, el valor de esta medida no depende de tener una infraestructura enorme. Basta con que un agente pueda leer información de Git, accionar servicios Docker o invocar una API interna con permisos relevantes. Separar el endpoint MCP del emisor OAuth esperado evita que la información descubierta desde un servidor remoto decida dónde se presenta la identidad de máquina.
También es una buena razón para separar clientes y permisos. Un agente que solo lista despliegues no debería compartir credenciales con otro capaz de borrar volúmenes, administrar repositorios o modificar datos de Laravel. El binding de emisor limita un desvío concreto, pero los scopes y la segmentación de identidades siguen siendo la barrera que reduce el impacto cuando algo falla.
La letra pequeña
Esto no convierte automáticamente un MCP en seguro. No sustituye la validación del servidor al que se conecta el agente, la protección de secretos, el uso de HTTPS ni una política de permisos mínimos. Tampoco arregla un issuer mal configurado: si se fija la URL de un emisor no deseado, el cliente obedecerá esa configuración.
Hay que tener cuidado también con los entornos donde se cambia de proveedor OAuth entre desarrollo, staging y producción. Cada entorno debe usar el issuer que corresponda a las credenciales cargadas allí, y los secretos deben llegar mediante variables de entorno o un gestor de secretos, nunca desde el repositorio. La documentación recomienda precisamente no guardar client_secret en el código fuente. (github.com)
Por último, esta mejora afecta a clientes OAuth de máquina escritos con estos proveedores del SDK de Python. No protege por sí sola integraciones que no usen OAuth, clientes implementados con otra biblioteca o secretos que ya hayan sido expuestos. Es una defensa específica y útil, siempre que se active de verdad configurando issuer.
Fuente: FastMCP v4.1.2: License to Deny Documentación oficial: OAuth clients en el SDK de Python de MCP