MCP
Manual · MCP dynamo-auth

Levantar el MCP de Dynamo Auth

Este MCP corre local en tu máquina, transporte stdio — no hay deploy a Azure, no hay URL pública. Te da tools para consultar y administrar el sistema de acceso de Dynamo (proyectos, usuarios, miembros, apps habilitadas, quién tiene acceso a qué, invitaciones y bajas) actuando siempre como tu propio usuario — con tus permisos, nunca con una clave de servicio. Al final vas a poder llamar auth_session_status desde Claude Code y ver una respuesta real: quién sos y contra qué ambiente estás.

stdio local ejemplo: QA 6 lectura 6 escritura

Este manual usa comandos de Claude Code. Para Claude Desktop el flujo de instalar/loguear/ compilar (§1-3) es idéntico — solo cambia dónde pegás el bloque JSON final. Para Claude Mobile: no es posible, stdio exige un proceso local que el celular no puede correr. Detalle de los tres clientes: Conectar desde Claude.

La trampa más importante de este MCP en particular: varias tools de lectura no te dicen que no tenés permiso — te devuelven una lista vacía. El gate está escrito dentro del WHERE de la función de base de datos (AND is_global_admin(auth.uid())), así que un usuario sin permisos ve cero filas, exactamente igual que si el sistema estuviera vacío. Si algo te devuelve vacío y esperabas datos, lo primero que hay que mirar es auth_session_status, no la base. Cada tool afectada te lo recuerda en el campo note de su respuesta.

0. Qué necesitás antes de empezar

1. Instalar dependencias del paquete mcp/

Andá a la carpeta mcp/ del repo (NO la raíz, son paquetes Node distintos — mcp/ tiene su propio package-lock.json y no está en los workspaces del root):

bash
cd auth-dynamo/mcp
NODE_AUTH_TOKEN=$(gh auth token) npm ci

Qué tenés que ver: una línea final del estilo added 252 packages in 11s.

El NODE_AUTH_TOKEN=$(gh auth token) no es opcional y no se reemplaza pegando un token a mano: saca el token de tu sesión de gh ya autenticada y lo pasa solo para esa invocación (no queda en el historial ni en ningún archivo). En esta máquina la cuenta de gh se resuelve por carpeta — tenés que estar dentro de ~/dev/Dynamo/** para que agarre la cuenta de Dynamo.

401 Unauthorized = tu gh no está autenticado con la cuenta de Dynamo. 403 Forbidden = la cuenta está bien pero al repo le falta el permiso de lectura sobre el paquete; eso lo otorga Daniel en Package settings → Manage Actions access → auth-dynamo: Read.

2. Generar tu sesión (npm run login)

El MCP necesita un JWT de sesión guardado en tu disco (~/.dynamo/dynamo-qa-session.json). Exportá estas 4 variables en la misma terminal donde vas a correr el comando (no las guardes en ningún archivo commiteado):

bash
export SUPABASE_URL="https://bxogqotctuurtcgjakfz.supabase.co"
export ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJ4b2dxb3RjdHV1cnRjZ2pha2Z6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODIxODY5OTcsImV4cCI6MjA5Nzc2Mjk5N30.IL2_oF_UWFuOMZfvD6rJ4g4f87q4f9ioROJrxGss0qg"
export DYNAMO_LOGIN_EMAIL="tu-email@ejemplo.com"
export DYNAMO_LOGIN_PASSWORD="tu-password-de-supabase-auth"

La key de arriba es la anon de QA — es pública por diseño (la misma que va en el cliente web). En el Dashboard de Supabase, al lado, hay una service_role marcada como "secret": esa NO. El MCP no puede distinguirlas: si le pasás la service_role, el modelo de seguridad entero se cae y no te vas a enterar. Para DEV/PROD el ref y su anon key salen del Dashboard del proyecto (Settings → API Keys) — no están en este manual.

Corré:

bash
npm run login

Qué tenés que ver: primero una línea Ambiente: QA — la sesión se escribe en /Users/<vos>/.dynamo/dynamo-qa-session.json, y después la confirmación de que la sesión quedó guardada.

Si en cambio el login falla con credenciales inválidas, tu usuario probablemente no tiene password configurado (entrás siempre por magic-link). En ese caso usá el fallback OTP, que son dos corridas:

bash
unset DYNAMO_LOGIN_PASSWORD
export DYNAMO_LOGIN_OTP=1
npm run login
# Te llega un código de 6 dígitos al email. Corré de nuevo con el código:
export DYNAMO_LOGIN_OTP_CODE="123456"
npm run login

Verificación de este paso: corré ls -l ~/.dynamo/dynamo-qa-session.json — la línea tiene que empezar con -rw-------. Esos permisos (600) son a propósito: el archivo tiene tus tokens. Si el archivo no existe, no sigas hasta que exista.

Un login sirve para todos los MCPs de Dynamo del mismo ambiente. El archivo se llama dynamo-qa-session.json, no auth-…, porque auth, forms y analytics comparten el mismo proyecto Supabase por ambiente. Si ya hiciste login para Forms o Analytics contra QA, este paso ya está hecho.

3. Compilar el server

bash
npm run build

Qué tenés que ver: nada. Cero salida es éxito (tsc solo habla cuando hay errores). Confirmá que generó el ejecutable con ls dist/server.js.

Cada vez que hagas git pull y traigas cambios de mcp/, volvé a correr npm run build. Si no, Claude Code sigue ejecutando el dist/ viejo y vas a ver herramientas desactualizadas sin ningún aviso. auth_session_status reporta la versión del paquete y el mtime del archivo de sesión justamente para que puedas detectar un dist/ rancio.

4. Configurar Claude Code (transporte stdio)

Claude Code necesita la ruta absoluta al dist/server.js. Conseguila con pwd desde mcp/ y reemplazala abajo:

bash
claude mcp add dynamo-auth \
  --env SUPABASE_URL=https://bxogqotctuurtcgjakfz.supabase.co \
  --env ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJ4b2dxb3RjdHV1cnRjZ2pha2Z6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODIxODY5OTcsImV4cCI6MjA5Nzc2Mjk5N30.IL2_oF_UWFuOMZfvD6rJ4g4f87q4f9ioROJrxGss0qg" \
  -- node /RUTA/ABSOLUTA/A/auth-dynamo/mcp/dist/server.js

Qué tenés que ver: Added stdio MCP server dynamo-auth to local config. Verificá con claude mcp list — tiene que aparecer dynamo-auth con ✓ Connected a la derecha. El server no necesita tokens acá: lee la sesión directo del archivo del paso 2.

Equivalente en JSON (editar a mano)

json
{
  "mcpServers": {
    "dynamo-auth": {
      "command": "node",
      "args": ["/RUTA/ABSOLUTA/A/auth-dynamo/mcp/dist/server.js"],
      "env": {
        "SUPABASE_URL": "https://bxogqotctuurtcgjakfz.supabase.co",
        "ANON_KEY": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJ4b2dxb3RjdHV1cnRjZ2pha2Z6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODIxODY5OTcsImV4cCI6MjA5Nzc2Mjk5N30.IL2_oF_UWFuOMZfvD6rJ4g4f87q4f9ioROJrxGss0qg"
      }
    }
  }
}

Claude Desktop: el mismo bloque JSON, pegado en claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json). Reiniciá Claude Desktop después de guardar.

Si dice ✗ Failed to connect: casi siempre es una de tres — la ruta del server.js está mal, no corriste npm run build, o falta una de las dos variables de entorno. Arrancalo a mano para ver el error real:

bash
SUPABASE_URL="https://bxogqotctuurtcgjakfz.supabase.co" ANON_KEY="<ANON_KEY>" node dist/server.js

Tiene que quedar colgado esperando (es correcto: habla por stdin/stdout) y escribir [dynamo-auth-mcp] Listo — v0.1.0, 12 tools registradas, transport stdio.. Cortalo con Ctrl+C.

5. Verificar que responde

Reiniciá la sesión de Claude Code (o abrí una nueva) y pedile:

Usá auth_session_status para decirme contra qué ambiente estoy corriendo y quién soy.

Qué tenés que ver: algo así.

json
{
  "authenticated": true,
  "email": "tu.email@dynamotech.co",
  "user_id": "…",
  "env": "QA",
  "version": "0.1.0",
  "is_global_admin": true,
  "admin_project_ids": [],
  "multi_client": false
}

Si ves eso, terminaste. Si ves "authenticated": false, la sesión no se hidrató: repetí el paso 2 y reiniciá Claude Code — el MCP lee la sesión al arrancar, no en cada llamada.

6. auth_session_status — el diagnóstico antes de operar

Es la única tool sin requisitos, siempre corrible primero. Te dice quién sos (email/user_id), contra qué ambiente estás apuntando (DEV/QA/PROD, resuelto del SUPABASE_URL), si sos global-admin, de qué proyectos sos project-admin (admin_project_ids) y si tenés la flag multi_client. Nunca devuelve el JWT, solo su expiry.

PROD ⚠️. Cuando el ambiente resuelto es producción, auth_session_status lo marca explícitamente con ⚠️. Correla antes de cualquier escritura: acá hay 6 tools que escriben sobre el sistema de permisos, y nada te impide invitar, desactivar o deshabilitar apps de un cliente real si tenés permisos para hacerlo. Es el mismo mecanismo de anon-key/ref en los tres ambientes — solo cambia la URL, y es fácil creer que estás en QA cuando apuntás a PROD. Si no era tu intención, cambiá SUPABASE_URL a QA en la config del MCP y reiniciá Claude Code.

UNKNOWN ⚠️. Aparece cuando la SUPABASE_URL que configuraste no corresponde a ninguno de los tres ambientes conocidos. Se marca con ⚠️ a propósito: un ambiente que no reconocemos se trata como riesgo, no como "seguro por defecto". Revisá la URL.

7. Inventario de tools (12 total)

6 de lectura + 6 de escritura. Cada una valida su input y traduce cualquier error de la base a un mensaje legible en español — nunca se escapa una excepción cruda. No hace falta llamarlas por nombre: pedile a Claude lo que querés en castellano ("dale acceso a analytics a juan@cliente.com en el proyecto Acme") y elige la herramienta sola.

Lectura (6)

ToolQué hace
auth_session_statusQuién sos, contra qué ambiente, si sos global-admin y de qué proyectos sos project-admin (§6). Correla antes de cualquier cosa importante, y siempre que algo te devuelva vacío.
auth_list_projectsTodos los proyectos de la plataforma, con cuántos miembros y cuántas apps tiene cada uno. Es la que usás para pasar de un nombre de proyecto a su project_id. Global-admin only, gate por filtro.
auth_list_usersBusca usuarios por email o nombre (coincidencia parcial). Devuelve su rol, sus proyectos y si está desactivado.
auth_list_project_membersQuiénes son los miembros de un proyecto y cuáles de ellos son administradores de ese proyecto. Acepta global-admin o project-admin de ese proyecto.
auth_list_project_appsQué apps están habilitadas en un proyecto. Es la más estricta: global-admin y nadie más.
auth_list_project_app_accessQuién tiene acceso a qué app dentro de un proyecto. Acepta global-admin o project-admin de ese proyecto.

Escritura (6)

ToolQué hace
auth_invite_userInvita a alguien nuevo: lo crea, le manda el correo, lo mete al proyecto y le da las apps. Si el email existía desactivado, lo reactiva. Va por la Edge Function admin-invite-user. Global-admin only.
auth_add_project_memberMete a un usuario que ya existe en un proyecto, opcionalmente con apps, en una sola transacción. Idempotente.
auth_remove_project_memberLo saca de un proyecto (no lo borra, no lo toca en otros proyectos).
auth_set_app_accessDa o quita el acceso de una persona a una app en un proyecto. Idempotente en los dos sentidos.
auth_set_project_appHabilita o deshabilita una app en el proyecto. Solo global-admin. Deshabilitar exige confirm_cascade: true.
auth_set_user_deactivatedDa de baja (o vuelve a habilitar) a un usuario en toda la plataforma. Va por la Edge Function admin-deactivate-user. Global-admin only.

El modelo de acceso, en tres frases

Sin esto las respuestas no tienen sentido:

  1. Todo es por proyecto. No existe "tener acceso a forms" a secas: existe "tener acceso a forms en el proyecto X".
  2. Son dos capas, y el orden importa. Primero la app se habilita en el proyecto (auth_set_project_app / auth_list_project_apps), y recién después se le puede dar a un usuario (auth_set_app_access / auth_list_project_app_access). Dar acceso a una app que no está habilitada en el proyecto falla.
  3. Los global-admins no aparecen en la lista de accesos. Pueden usar todo y son miembros automáticos de todos los proyectos, pero por su rol — no por una fila de permiso. Que alguien no figure en auth_list_project_app_access no quiere decir que no pueda entrar.

Cuatro cosas que hay que saber antes de escribir

Las dos operaciones privilegiadas van por Edge Function

auth_invite_user y auth_set_user_deactivated necesitan la service_role key de Supabase — crear un usuario y banearlo no se pueden hacer con una sesión de usuario común. El MCP nunca ve esa clave. Esas dos operaciones viven dentro de Edge Functions (admin-invite-user y admin-deactivate-user): el MCP las invoca como vos, y la función vuelve a verificar tus permisos con tu propio JWT (construye un cliente con tu bearer y llama is_global_admin) antes de usar su clave privilegiada. Hay un test automatizado que falla el CI si alguien intenta meter esa clave en el MCP (mcp/src/__tests__/no-service-role.test.ts).

Lo que este MCP deliberadamente NO puede hacer

No hay tools para promover a alguien a global_admin, para cambiar por la vía directa quién es administrador de un proyecto, ni para borrar un proyecto. No es un olvido: las dos primeras son escalada de privilegios y la tercera arrastra en cascada a miembros y apps de forma irreversible. Se hacen desde el portal, con una persona mirando. Si Claude te dice que no puede promover a alguien a global-admin, está funcionando bien. Tampoco hay borrado de usuarios, porque el sistema no borra usuarios: los desactiva (auth_set_user_deactivated), que es reversible y queda auditado. Y no es un bypass de permisos: usa tu sesión — lo que no ves en el portal tampoco lo ves acá — ni toca infraestructura: un write del MCP no es un deploy.

8. Cuando el refresh token expira

Si después de mucho tiempo sin usarlo las tools empiezan a fallar con mensajes de sesión inválida/expirada, repetí el paso 2 (npm run login) y reiniciá Claude Code — no hace falta reconfigurar Claude Code de nuevo.

9. Troubleshooting rápido

Tabla completa de errores y causas: Troubleshooting.