Tu servidor MCP no debería obligar a pegar un token

oauth

El mes pasado pasé el servidor MCP de Kookee a tokens personales. Cada persona crea el suyo, el asistente actúa como esa persona, y cada cambio que hace dice quién lo hizo. En Claude Code y Cursor funciona bien: pegas tu token en un comando o en un archivo de configuración, y listo.

Ahora intenta explicárselo a alguien que escribe notas de versión en Claude desde el portátil, o que revisa los comentarios desde el teléfono. Las apps de chat de Claude y ChatGPT no tienen un campo para la cabecera Authorization. Se conectan a un servidor MCP remoto de una sola forma: les das una URL, te mandan a iniciar sesión y guardan la sesión. Eso es OAuth, y hasta esta semana Kookee no lo hablaba.

Ahora sí. Añades https://api.kookee.dev/api/mcp en Claude, ChatGPT o VS Code, inicias sesión en Kookee, eliges Lectura y escritura o Solo lectura, y esa es toda la configuración. Esto es lo que lancé y las decisiones que llevaron más tiempo que el código.

Cómo es conectarse ahora

La página del servidor MCP de cada proyecto tiene una tarjeta nueva con la URL y los pasos para cada app. Para Claude hay un botón Añadir a Claude. Abre el diálogo Add custom connector de Claude con el nombre y la URL ya rellenados, así que solo tienes que hacer clic en Add.

La tarjeta Conecta una aplicación de IA en la página del servidor MCP: la URL del conector con un botón para copiarla, pestañas para Claude, ChatGPT, VS Code y Claude Code, tres pasos y un botón Añadir a Claude

Después Claude abre una página de Kookee. Inicias sesión, o creas una cuenta, y decides qué puede hacer la app:

La página de consentimiento de Kookee: ¿Permitir que Claude use tu cuenta de Kookee?, con la opción entre Lectura y escritura, seleccionada, y Solo lectura, y los botones Permitir y Cancelar

Ningún token que crear, copiar, guardar o rotar. Después el conector aparece en Claude en la web, en el escritorio y en el teléfono.

Siete decisiones detrás del OAuth del MCP de Kookee

1. Una conexión por persona, no por proyecto

Mi primer plan copiaba el modelo de los tokens: un token pertenece a un proyecto, así que una conexión también. Claude tumbó ese plan. No te deja añadir un segundo conector personalizado con la misma URL, así que una conexión ligada a un proyecto habría limitado a cada usuario de Claude a un solo proyecto. Una URL distinta por proyecto lo evita, pero deja a alguien con cinco proyectos con cinco conectores que configurar y vigilar.

Así que una conexión pertenece a tu cuenta y llega a todos los proyectos de los que eres miembro, con tu rol en cada uno. Todas las herramientas excepto list_projects aceptan un argumento opcional project. Si estás en un solo proyecto, el asistente nunca lo necesita. Si estás en varios, dices «en el proyecto Lumen» y el asistente pasa el slug. list_projects cubre el caso en que tiene que preguntar.

Cuando el proyecto falta o es incorrecto, el servidor no responde con un error HTTP. Devuelve un resultado de herramienta normal que dice qué salió mal y lista tus proyectos:

JSON
{
  "error": "MCP_PROJECT_REQUIRED",
  "message": "This account belongs to several projects. Pass `project` with the slug of the one the user means.",
  "projects": [
    { "slug": "lumen", "name": "Lumen", "role": "OWNER" },
    { "slug": "lumen-docs", "name": "Lumen Docs", "role": "MEMBER" }
  ]
}

Un modelo lee eso y vuelve a intentarlo con el slug correcto. En cambio, un 400 del transporte le parece un servidor roto.

2. Solo lectura es una opción en la pantalla de consentimiento

Con los tokens, eliges el acceso al crear el token. Con OAuth no hay página de tokens, así que la elección pasó al momento en que apruebas la app.

Por debajo, las dos opciones son scopes de OAuth, read y write, y elegir Solo lectura quita write antes de que la app reciba su código. Como con los tokens, una conexión de solo lectura no se limita a rechazar escrituras: nunca muestra las herramientas de escritura. Un modelo que ve move_feedback planifica todo un triaje en torno a ella y falla en el último paso. Un modelo que no la ve te dice que la conexión es de solo lectura.

3. Desconectar tiene que desconectar de verdad

Esto fue lo que más tiempo llevó. Una app OAuth tiene dos tokens: un token de acceso que dura una hora y un token de refresco que cambia por nuevos tokens de acceso. De serie, el grant de refresco del servidor OAuth sobre el que construí no comprobaba si la app seguía autorizada. Si retirabas el consentimiento, la app podía seguir obteniendo tokens de acceso nuevos.

Por eso ahora pasan dos cosas. Cada solicitud MCP busca el token de acceso, el consentimiento que lo respalda y la cuenta, en una sola consulta, y rechaza la solicitud si falta alguno o la cuenta está desactivada. Y desconectar elimina el consentimiento, los tokens de refresco y los tokens de acceso juntos, en una sola transacción. Desconecta una app en Kookee y su siguiente solicitud se rechaza.

Cambiar o restablecer tu contraseña también desconecta todas las apps. Si restableces la contraseña porque algo salió mal, un asistente con una sesión activa en algún sitio es lo último que quieres. Las cuentas de invitado no pueden conectarse.

4. Tokens que puedes retirar, no tokens que hay que esperar a que caduquen

Una elección habitual para los tokens de acceso de OAuth son los JWT: firmados, autocontenidos, verificados sin consultar la base de datos. Y no se pueden retirar antes de que caduquen. Para un asistente que puede publicar en tu nombre, «deja de funcionar en menos de una hora» no basta.

Los de Kookee son cadenas aleatorias, guardadas solo como hashes SHA-256, con prefijos propios: mcpa_ para los tokens de acceso y mcpr_ para los de refresco. Un escáner de secretos, o una persona que lee un log, puede distinguirlos de un token personal (kkm_) o de una clave de API (kk_). El precio es una consulta por solicitud, y esa consulta ya se hacía.

5. Solo apps conocidas

Los clientes MCP se identifican con un documento de metadatos del client ID: el client ID es una URL, y el servidor la descarga para saber el nombre de la app y adónde puede devolverte. Es un diseño limpio. También significa que un servidor que acepta cualquier URL descargará lo que le indiquen, y guardará un cliente para ello, antes de que nadie haya iniciado sesión.

En lugar de eso, Kookee acepta una lista corta: Claude, Claude Code, ChatGPT y VS Code, cada uno reconocido por la dirección en la que está su documento de metadatos. Así la pantalla de consentimiento puede decir «¿Permitir que Claude use tu cuenta de Kookee?» con fundamento, y nadie puede alojar en otro sitio un documento que diga ser Claude.

Eso tiene un coste. Cursor usa el registro dinámico de clientes, que dejé desactivado, así que los usuarios de Cursor siguen usando tokens personales. Funcionan exactamente igual que antes.

6. La página de consentimiento no se puede cargar en un frame

Una página con un botón Permitir es el objetivo clásico del clickjacking: se carga en un frame invisible y se pone algo tentador debajo del botón. La de Kookee envía Content-Security-Policy: frame-ancestors 'none' y X-Frame-Options: DENY, así que ningún otro sitio puede cargarla en un frame. Son dos cabeceras, fáciles de olvidar hasta que alguien escribe para avisarte.

7. La misma atribución que antes

Iniciar sesión cambia cómo te conectas, no lo que se registra. Un cambio hecho desde Claude se guarda con tu nombre, igual que un cambio hecho con un token, y el log del servidor anota qué app lo hizo. En el tablero de comentarios, los cambios de columna, las reasignaciones y las respuestas que hace la app se marcan vía MCP, para que tus compañeros vean quién movió la publicación y que lo hizo un asistente.

Qué ves en Kookee

Todo lo que has conectado aparece en la configuración de tu cuenta, en Seguridad → Aplicaciones conectadas, con su acceso y el día en que lo conectaste. Desconectar es un clic y una confirmación.

Aplicaciones conectadas en Configuración → Seguridad: Claude Code y Claude con Lectura y escritura, VS Code con Solo lectura, cada una con un botón Desconectar

Los tokens personales no han desaparecido. Siguen siendo la herramienta adecuada para Cursor, para scripts y CI, y para un .mcp.json que todo el equipo sube al repositorio, y ahora están en una tarjeta de la página del servidor MCP, debajo de la nueva.

Si tienes un servidor MCP

  • Admite OAuth. La gente en Claude, ChatGPT y otras apps de chat no puede pegar una cabecera.

  • Haz la conexión por persona y recibe el espacio de trabajo como argumento de la herramienta. Claude no acepta la misma URL dos veces.

  • Pon la opción de solo lectura en la pantalla de consentimiento, y no muestres herramientas que el permiso concedido no puede usar.

  • Comprueba el consentimiento en cada solicitud, y elimina los tokens de refresco cuando alguien se desconecte. No cuentes con que el grant de refresco lo haga por ti.

  • Prefiere tokens opacos que puedas revocar a JWT que tengas que esperar a que caduquen.

  • Acepta una lista de URLs de metadatos de clientes conocidos en lugar de descargar lo que envíe cualquier cliente.

  • Mantén la página de consentimiento fuera de los frames.

  • Responde a «¿qué proyecto?» con un resultado de herramienta con el que el modelo pueda actuar, no con un error HTTP.


El servidor MCP de Kookee ya funciona con Claude, ChatGPT y VS Code iniciando sesión. Consulta Conectar Claude, ChatGPT o VS Code a Kookee para ver los pasos, o Gestionar tu contenido desde un asistente de IA (MCP) para saber qué puede hacer un asistente una vez conectado.