Autenticación
El servidor MCP acepta un Bearer token en la cabecera Authorization, y
nada más — sin cookie, sin certificado de cliente.
:::warning Nunca en la URL
Un token en la query string (?access_token=…, o parámetros como token,
bearer, api_key y key) no autentica, aunque venga junto a una
cabecera válida: la solicitud responde 401 con error="invalid_request" en
WWW-Authenticate. Un token en la URL se filtra en logs de proxy y CDN, en el
Referer y en el historial — la especificación de autorización del MCP y la
RFC 6750 lo prohíben.
:::
Ese token es un access token emitido por el Authorization Server de Kobana
(https://account.kobana.com.br), un JWT firmado con RS256 y emitido para
este recurso: su aud es https://mcp.kadu.ia.br (en staging,
https://mcp.staging.kadu.ia.br), y solo ese — un token con más de una
audience se rechaza. El token llega por uno de dos caminos — el flujo OAuth,
que hace el propio cliente, o un token personal que creas en la Cuenta
Kobana — y se verifica igual en los dos casos, con la misma verificación local
de firma de la API v1.
Authorization: Bearer eyJhbGciOiJSUzI1NiIs…
:::info Cada superficie tiene su audience
Un token emitido para el MCP no vale en la API (api.kadu.ia.br), y un
token de la API no vale aquí. Es lo que impide que el token entregado a un
asistente se reutilice como credencial REST — y al revés.
:::
Dos formas de conectar
| Flujo OAuth | Token personal | |
|---|---|---|
| Cómo lo obtienes | Pegas la URL en el cliente; abre el inicio de sesión de la Cuenta Kobana y autorizas | Lo creas en el asistente de tokens de la Cuenta Kobana, con el uso MCP, los recursos y accesos y la validez |
| Cómo lo usa el cliente | Por su cuenta, tras la autorización, renovándolo cuando expira | Lo pegas en el cliente como Authorization: Bearer <token> |
| Alcance | Los recursos y niveles de acceso que dejaste marcados en la pantalla de consentimiento | Los recursos y accesos que marcaste |
| Bueno para | Claude, ChatGPT, IDEs y cualquier cliente con OAuth | Clientes sin soporte de OAuth, scripts y automatizaciones |
El token personal tiene que crearse con el uso MCP. Un token creado para la
API lleva la audience de la API y aquí se rechaza con 401 — y al revés
también.
El flujo OAuth
No ejecutas ninguno de estos pasos: el cliente MCP lo hace todo por su cuenta a partir de la URL. Están aquí para cuando algo falle en el camino.
-
Desafío. Una solicitud sin token recibe
401con el desafío que todo resource server OAuth 2.1 debe enviar (RFC 9728):WWW-Authenticate: Bearer realm="kadu-mcp",error="invalid_token",resource_metadata="https://mcp.kadu.ia.br/.well-known/oauth-protected-resource" -
Metadata del recurso. El cliente lee el documento indicado. Es público y no lleva ningún secreto — solo punteros:
curl https://mcp.kadu.ia.br/.well-known/oauth-protected-resource{"resource": "https://mcp.kadu.ia.br","authorization_servers": ["https://account.kobana.com.br"],"bearer_methods_supported": ["header"],"scopes_supported": ["kadu/activities","kadu/environments","kadu/executions","kadu/files","kadu/memberships","kadu/memories","kadu/organizations","kadu/repository","kadu/roles","kadu/schedules","kadu/secrets","kadu/system_events","kadu/tasks","kadu/todos","kadu/usage","kadu/users","kadu/webhook_deliveries","kadu/webhooks","kadu/workspaces","read","write","delete","import","export","all"],"resource_documentation": "https://api.kadu.ia.br/v1/docs"}kadu/allqueda fuera a propósito: el comodín no se entrega a un cliente que se registró solo. -
Metadata del Authorization Server (RFC 8414), en
https://account.kobana.com.br/.well-known/oauth-authorization-server. -
Registro dinámico del cliente (RFC 7591). El cliente se registra solo.
-
Autorización con PKCE y
resource(RFC 8707). Inicias sesión en la Cuenta Kobana, eliges la organización (si tienes más de una), revisas lo que pide el cliente y autorizas. -
Token. El cliente cambia el código por un access token de vida corta y lo envía en cada solicitud, renovándolo cuando expira.
El token lleva quién eres (sub) y la organización que autorizaste (org). Es
el org el que delimita los datos — ninguna tool acepta organización como
argumento. Un token cuya organización no existe en Kadu recibe
403 unknown_organization.
Autorización: el rol de quien autorizó
Lo que ve un llamador es más estrecho que el catálogo: las tools se filtran
por el rol de la persona del token en la organización — los mismos permisos
agent.<recurso>.<acción> de la consola y de la API (mira
Permisos). La tool exige exactamente el permiso
de la ruta que refleja. El techo es siempre lo que esa persona puede hacer en
la consola.
| Situación | Resultado |
|---|---|
| La persona del token no es miembro de la organización | Niega todas las tools (not_a_member). Autoriza de nuevo con una cuenta que pertenezca a la organización. |
| Miembro | Los permisos del rol, aplicados tool por tool. Los comodines *, agent.* y agent.<recurso>.* valen como en la consola. |
| La consulta de permisos falla | Niega todas las tools. Falla cerrada. |
A diferencia de la API, el MCP no tiene token de integración sin persona: el
token tiene que llevar a una persona de la organización (por el sub o por el
correo del token); si no, ninguna tool corre.
Los permisos se releen al menos una vez por minuto, así que cambiar o quitar el rol de alguien vale en menos de un minuto, sin esperar a que el token expire.
Una tool que el llamador no puede ejecutar no se lista en tools/list.
Cuando tools/call la rechaza, el código dice quién lo resuelve:
| Código | Quién lo resuelve |
|---|---|
insufficient_scope | Tú, autorizando de nuevo con el acceso que faltó — o usando un token personal que alcance el recurso con el nivel pedido. |
insufficient_permission | Un administrador de la organización, concediendo el permiso (el error dice cuál). |
not_a_member | Autorizar de nuevo con una cuenta que sea miembro de la organización. |
:::tip Postura recomendada para agentes Autoriza el cliente con una cuenta que tenga solo los permisos que el agente necesita. Un cliente autorizado por alguien que solo lee expone solo las tools de lectura, haga lo que haga el modelo del otro lado. :::
Alcance del token
Además del rol, el MCP aplica el alcance que lleva el token, en el claim
scope, con la misma gramática de la API (mira
Autenticación de la API → Alcances).
Las dos verificaciones se suman: una tool solo aparece si las dos lo permiten.
| Tool | Exige |
|---|---|
Lectura (*_list, *_get, kadu_usage_get…) | El alcance del recurso (kadu/tasks) |
Eliminación (*_delete) | El alcance del recurso + delete |
| Cualquier otro cambio (crear, modificar, lanzar, cancelar, continuar, sincronizar, reenviar, rotar un secreto) | El alcance del recurso + write |
kadu_me_get | Cualquier token válido |
El recurso es el primer segmento de la ruta que refleja la tool:
kadu_executions_logs_list es del recurso executions, y kadu_tasks_run
exige kadu/tasks write. Las aprobaciones por enlace
(kadu_operation_approvals_*) se leen con kadu/system_events. Ningún nivel
reemplaza a otro: write no elimina. Ninguna tool exige import ni export.
| Alcance del token | Resultado |
|---|---|
kadu/executions kadu/todos read write | Lee y modifica ejecuciones y pendientes — cancela, continúa y resuelve — y nada más |
kadu/tasks kadu/executions write | Lee, lanza y sigue tareas, sin eliminar nada |
read (sin ningún kadu/…) | Lee todo, no escribe nada |
:::info Los clientes conectados por OAuth piden todos los niveles — tú eliges cuáles conceder
El documento /.well-known/oauth-protected-resource anuncia los recursos de
Kadu y los seis niveles de acceso, y eso es lo que el cliente pide en la
pantalla de consentimiento. Allí desmarcas lo que no quieres conceder: el token
emitido lleva solo los recursos y niveles que quedaron marcados, y el MCP lista
solo las tools que alcanzan. Una tool fuera de eso devuelve
insufficient_scope, y la salida es autorizar de nuevo concediendo el acceso
que faltó — delete para las tools de eliminación, por ejemplo.
:::
Revocación del token personal
Kadu verifica el token por su cuenta, por la firma, sin consultar a la Cuenta Kobana en cada llamada: revocar allí un token personal no corta el acceso al instante, y sigue aceptado hasta su validez. Por eso:
- elige la validez más corta que el uso permita;
- marca solo los recursos y accesos que el agente necesita;
- para cortar un agente de inmediato, quita el vínculo de la persona con la organización o cambia su rol — vale en menos de un minuto.
Modos de falla
| HTTP | code | Causa |
|---|---|---|
| 400 | -32022 (JSON-RPC) | Versión del protocolo no soportada, en la cabecera MCP-Protocol-Version o en el _meta; data.supported lista las soportadas. Mira Transporte. |
| 401 | invalid_token | Token ausente, mal formado, inválido, emitido para otro recurso (la API, por ejemplo) o con más de una audience en aud. |
| 401 | token_expired | El token expiró. El cliente lo renueva y lo intenta de nuevo. |
| 401 | token_revoked | El token fue revocado en Kadu. |
| 401 | invalid_request | Token en la query string — mira el aviso al principio de la página. |
| 403 | unknown_organization | El token es válido, pero la organización que nombra no existe en Kadu. |
| 403 | FORBIDDEN | Origin fuera de la lista permitida (origin_not_allowed). |
| 404 | NOT_FOUND | La solicitud llegó por un host que no sirve MCP. |
| 413 | -32600 (JSON-RPC) | Cuerpo de más de 1 MB. |
| 429 | TOO_MANY_REQUESTS | Límite de solicitudes alcanzado, con Retry-After y X-RateLimit-* — mira Límite de Peticiones. |
| 503 | SERVICE_UNAVAILABLE | No pudimos verificar el token contra el Authorization Server. Es de nuestro lado: inténtalo de nuevo después del Retry-After. |
Todo 401 lleva el desafío WWW-Authenticate de arriba, que devuelve al
cliente al flujo OAuth. El desafío dice siempre error="invalid_token" (o
invalid_request, para token en la URL), como manda la RFC 6750; es el code
del cuerpo el que distingue un token expirado (token_expired) de los demás.
Los intentos con token rechazado cuentan en un límite por IP, y pasado el
límite la respuesta pasa a ser 429.
Ninguna respuesta del MCP se puede cachear: todas, rechazos incluidos, llevan
Cache-Control: no-store y Content-Security-Policy: default-src 'none'. La
excepción es el documento público /.well-known/oauth-protected-resource, con
Cache-Control: public, max-age=300.
Las negativas de alcance y de permiso no son errores HTTP: vuelven como
resultado de tool marcado isError, para que el modelo lo lea y reaccione —
mira Errores.
Host y borde
El MCP solo responde por mcp.kadu.ia.br — nunca por el host de la consola,
nunca por el host de la API, que apuntan a la misma aplicación. Por cualquier
otro camino la respuesta es 404: el endpoint no anuncia que existe en otro
lugar.
El identificador de recurso OAuth — y, por lo tanto, la audience que el token
tiene que llevar — viene de la configuración del servidor, nunca del Host
de la solicitud, para que un Host falsificado no pueda apuntar a un cliente a
otro identificador.