MCP
Manual · MCP dynamo-forms

Levantar el MCP de DynamoForms

Este MCP corre local en tu máquina, transporte stdio — no hay deploy a Azure, no hay URL pública. Actúa siempre como tu propio usuario (tu JWT de sesión de Supabase Auth); nunca usa una clave de servicio. Al final vas a poder llamar forms_session_status desde Claude Code y ver una respuesta real: quién sos y contra qué ambiente estás.

stdio local ejemplo: QA 7 lectura 5 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.

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):

bash
cd forms-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 → forms-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 forms-…, porque forms y auth comparten el mismo proyecto Supabase por ambiente. Si ya hiciste login para otro MCP de Dynamo 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.

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-forms \
  --env SUPABASE_URL=https://bxogqotctuurtcgjakfz.supabase.co \
  --env ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJ4b2dxb3RjdHV1cnRjZ2pha2Z6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODIxODY5OTcsImV4cCI6MjA5Nzc2Mjk5N30.IL2_oF_UWFuOMZfvD6rJ4g4f87q4f9ioROJrxGss0qg" \
  -- node /RUTA/ABSOLUTA/A/forms-dynamo/mcp/dist/server.js

Qué tenés que ver: Added stdio MCP server dynamo-forms to local config. Verificá con claude mcp list — tiene que aparecer dynamo-forms 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-forms": {
      "command": "node",
      "args": ["/RUTA/ABSOLUTA/A/forms-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-forms-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á forms_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,
  "entitlement_enforcement": true,
  "entitlement_warning": null
}

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. forms_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, y — si le pasás project_id — si sos project-admin de ese proyecto y qué menús tenés delegados. Nunca devuelve el JWT, solo su expiry.

PROD ⚠️. Cuando el ambiente resuelto es producción (o uno no reconocido), forms_session_status lo marca explícitamente con ⚠️. Correla antes de cualquier escritura: acá hay 5 tools que escriben, y nada te impide crear o modificar un formulario real de un cliente si tenés permisos. 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.

⚠️ KILL-SWITCH APAGADO. Aparece cuando entitlement_enforcement está en false en ese ambiente: el chequeo de "¿esta app está habilitada para este usuario en este proyecto?" está desactivado y el único control que queda es RLS. No es algo que arregles vos — es un estado de configuración de la base. Se reporta para que no creas que el sistema valida más de lo que valida.

7. Inventario de tools (12 total)

7 de lectura + 5 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 ("mostrame las respuestas del formulario de inspección de esta semana") y elige la herramienta sola.

Lectura (7)

ToolQué hace
forms_session_statusQuién sos, contra qué ambiente, si sos admin y si el gate de entitlement está activo (§6).
forms_list_formsTodos los formularios de un proyecto, vía la Edge Function admin-forms (respeta el acceso delegado por menú).
forms_get_formUn formulario por id. Pedí include_schema: false si solo querés metadata.
forms_list_form_responsesRespuestas de un formulario, más recientes primero. Tope duro 500, default 50; paginá con offset y acotá con since.
forms_list_tasksTareas, filtrables por proyecto, form, estado y asignado. Default 50, tope 200.
forms_list_notificationsNotificaciones — es LA tool de "¿por qué no llegó el correo?". Con include_logs: true trae la respuesta real del proveedor.
forms_list_shared_resourcesEl catálogo de datos maestros de un proyecto — nunca el contenido de las tablas.

Escritura (5)

ToolQué hace
forms_create_formCrea un formulario. Nace en draft; para publicarlo, forms_update_form con status: "active".
forms_update_formModifica título, descripción, schema, visibilidad, estado, carpeta o favorito. Solo esa whitelist.
forms_copy_formClona un formulario con sus plantillas de PDF y notificaciones, dentro del mismo proyecto.
forms_set_form_analyticsPrende o apaga Analytics para un formulario (forms.has_analytics).
forms_retry_notificationReencola una notificación fallida para el próximo paso del worker.

Tres cosas que conviene saber antes de escribir

Lo que este MCP deliberadamente NO puede hacer

No hay tools para borrar formularios ni respuestas, ni para cambiar roles de usuario. No es un olvido: son operaciones irreversibles sobre datos reales de cliente, y se hacen desde la interfaz, con una persona mirando. Si Claude te dice que no puede borrar un formulario, está funcionando bien. Tampoco es un bypass de permisos (usa tu sesión: lo que no ves en el portal tampoco lo ves acá), no ve nunca la service_role — hay un test que falla el CI si alguien la mete (no-service-role.test.ts) — y no 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.