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.
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
-
Node.js 20+ instalado — corré
node --version, debe imprimirv20.xo más. Con Node 18 nada de esto funciona. -
El repo
forms-dynamoclonado en tu máquina. Si no lo tenés:git clone git@github.com-dynamo:DynamoTechTVT/forms-dynamo.git. -
gh(GitHub CLI) autenticado con la cuenta de Dynamo. El paquete depende de@dynamotechtvt/mcp-core, que vive en GitHub Packages (privado) — sin token no instala. Verificá congh auth statusque aparezca la organizaciónDynamoTechTVT. - Un usuario real de Supabase Auth con la app forms habilitada en al menos un proyecto. Si no lo tenés, pedíselo a Daniel (o a un global-admin) antes de empezar.
-
El ambiente contra el que vas a apuntar. Este
manual usa QA (
bxogqotctuurtcgjakfz.supabase.co) porque es el ambiente seguro para probar — este MCP tiene tools de escritura, así que no repitas estos pasos contra PROD sin saber lo que hacés.
1. Instalar dependencias del paquete mcp/
Andá a la carpeta mcp/ del repo (NO la raíz, son
paquetes Node distintos):
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):
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é:
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:
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
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:
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)
{
"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:
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í.
{
"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)
| Tool | Qué hace |
|---|---|
forms_session_status | Quién sos, contra qué ambiente, si sos admin y si el gate de entitlement está activo (§6). |
forms_list_forms | Todos los formularios de un proyecto, vía la Edge Function admin-forms (respeta el acceso delegado por menú). |
forms_get_form | Un formulario por id. Pedí include_schema: false si solo querés metadata. |
forms_list_form_responses | Respuestas de un formulario, más recientes primero. Tope duro 500, default 50; paginá con offset y acotá con since. |
forms_list_tasks | Tareas, filtrables por proyecto, form, estado y asignado. Default 50, tope 200. |
forms_list_notifications | Notificaciones — es LA tool de "¿por qué no llegó el correo?". Con include_logs: true trae la respuesta real del proveedor. |
forms_list_shared_resources | El catálogo de datos maestros de un proyecto — nunca el contenido de las tablas. |
Escritura (5)
| Tool | Qué hace |
|---|---|
forms_create_form | Crea un formulario. Nace en draft; para publicarlo, forms_update_form con status: "active". |
forms_update_form | Modifica título, descripción, schema, visibilidad, estado, carpeta o favorito. Solo esa whitelist. |
forms_copy_form | Clona un formulario con sus plantillas de PDF y notificaciones, dentro del mismo proyecto. |
forms_set_form_analytics | Prende o apaga Analytics para un formulario (forms.has_analytics). |
forms_retry_notification | Reencola una notificación fallida para el próximo paso del worker. |
Tres cosas que conviene saber antes de escribir
-
forms_update_formreemplaza elschemaentero, no hace merge. En un formulario que ya tiene respuestas, eliminar o renombrar componentes deja esas respuestas apuntando a campos que ya no existen. Leé el schema actual conforms_get_formantes de reemplazarlo. -
Hay dos caminos separados para editar un formulario, y no
se mezclan.
has_analyticsno está en la whitelist deforms_update_form: va porforms_set_form_analytics. Si lo metés en el patch, la tool lo rechaza con un error explícito en vez de descartarlo en silencio. -
forms_retry_notificationno envía nada en el acto. Deja la notificación en cola; el worker la toma en su próxima corrida. Por defecto no resetea el contador de intentos: una notificación que ya los agotó consigue exactamente uno más. Si arreglaste la causa de fondo, pedíreset_attempts: true.
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
-
"La app forms no está habilitada para vos en este proyecto"— no es un bug. Es el gate de entitlement: tu usuario no tiene la appformshabilitada en ese proyecto. Se arregla en el dominio de auth: un global-admin habilita la app en el proyecto y después te da acceso. -
"No sos global-admin ni project-admin del proyecto…"— te faltan permisos. Pedí que te agreguen como admin o que te deleguen el menúforms-management. Conforms_session_status({ project_id })ves qué menús tenés delegados hoy. -
403 Forbiddenen elnpm ci— falta el grant de lectura del paquete@dynamotechtvt/mcp-core(§1). -
Tools desactualizadas después de un
git pull— faltónpm run build(§3).
Tabla completa de errores y causas: Troubleshooting.