# Servidor MCP

Usa Formstep desde Claude, Cursor y otras herramientas compatibles con MCP.

## Servidor MCP

El servidor del Model Context Protocol (MCP) permite a los agentes de IA leer y editar tus formularios de Formstep con herramientas enriquecidas basadas en esquemas.

<h2 id="what">Qué es</h2>
<p>
  MCP es un estándar abierto para que las herramientas de IA se conecten a servicios externos. Formstep expone un endpoint MCP alojado al
  que cualquier cliente compatible con MCP puede conectarse, incluyendo Claude Code, Claude desktop y Cursor.
</p>

<h2 id="connection">Conexión</h2>

```
URL:  https://api.formstep.io/api/mcp   (POST, streamable HTTP)
Auth: Bearer <token>
```

<p>Funcionan dos tipos de token Bearer:</p>
<ul>
  <li>
    <strong>Token de API</strong> (<code>fb_...</code>) — se crea desde <a href="/es/developers/api-tokens">tokens de API</a>. Ideal para
    uso personal y configuración rápida.
  </li>
  <li>
    <strong>Token de acceso OAuth</strong> (<code>fbo_...</code>) — emitido por el <a href="#oauth">flujo OAuth</a>. Ideal para aplicaciones
    de terceros que se conectan en nombre de un usuario.
  </li>
</ul>
<p>
  Ambos quedan vinculados a exactamente un espacio de trabajo y llegan a las mismas herramientas. Una llamada a una herramienta que nombre
  otro espacio de trabajo, o un formulario de otro, falla con <code>FORBIDDEN</code>. Los tokens OAuth también llevan ámbitos
  <code>mcp:read</code>, <code>mcp:write</code> y <code>offline_access</code>, pero hoy ninguna herramienta está restringida por ellos —
  trata cualquier token como acceso completo dentro de su espacio de trabajo.
</p>
<p>
  Las llamadas a herramientas están limitadas a 120 por minuto por token, compartidas con la <a href="/es/developers/rest-api">API</a>: solo{' '}
  <code>tools/call</code> gasta el presupuesto, mientras que <code>initialize</code>, <code>tools/list</code>, <code>prompts/*</code> y{' '}
  <code>resources/*</code> son gratis. Al superar el límite, la llamada igualmente devuelve HTTP 200 con un resultado de herramienta fallido
  que lleva <code>RATE_LIMITED</code> y un <code>retryAfterMs</code> — sondea con un temporizador, nunca en un bucle.
</p>

> 💡 **Dónde obtener un token**
> <p>
>     Abre <strong>OAuth y claves de API</strong> en la barra lateral de tu espacio de trabajo para crear tokens de API y ver las apps OAuth
>     conectadas. Consulta <a href="/es/developers/api-tokens">tokens de API</a> para una guía paso a paso.
>   </p>

<h2 id="core-tools">Herramientas principales</h2>
<p>
  Todas las herramientas se anuncian en <code>tools/list</code> cuando un cliente se conecta. Los clientes que cargan esquemas bajo demanda,
  como Claude Code, obtienen el esquema completo de una herramienta cuando una tarea lo necesita. La tabla a continuación cubre las
  herramientas principales con las que empiezan la mayoría de las tareas; <code>load_tools</code> (catálogos) y <code>load_skill</code>{' '}
  (guías de dominio) documentan el resto.
</p>

<p>
  Más variantes de inserción — hora, carga de archivos, firma, pago, matriz/cuadrícula, clasificación, selección de imagen, interruptor,
  tabla, lista, fila, campo calculado, campo oculto, variable inline, contenido incrustado (<code>editor_insertEmbedded</code> para YouTube,
  Google Maps o iframes) y un bloque de lógica condicional (<code>editor_insertLogic</code>) — también están en <code>tools/list</code>.{' '}
  Carga <code>load_skill("question-types")</code> para el conjunto completo, cada uno con su nombre de herramienta y campos. La lógica
  condicional se crea con <code>editor_setLogic</code> en el catálogo <a href="#tool-catalogs">editor-actions</a>.
</p>

<h2 id="tool-catalogs">Catálogos de herramientas</h2>
<p>
  Estas herramientas también están en <code>tools/list</code>. Ejecuta <code>load_tools</code> con un nombre de catálogo para obtener
  documentación enriquecida (introducción, esquemas completos, patrones de uso, casos límite) para las herramientas agrupadas, y luego
  llámalas directamente.
</p>

<p>
  El catálogo <code>request-lifecycle</code> lista las ocho herramientas de solicitudes porque el chat integrado anuncia un conjunto
  principal más reducido. En este endpoint las ocho ya están en <code>tools/list</code>, así que lo que el catálogo añade es documentación.
</p>

<h2 id="skills">Skills (conocimiento de dominio)</h2>
<p>
  Los skills son guías integradas que el agente puede cargar mediante <code>load_skill</code>. Ofrecen conocimiento de dominio que ayuda al
  agente a tomar mejores decisiones — no son esquemas de herramientas, sino consejos de diseño y semántica de campos.
</p>

<h2 id="requests">Solicitudes</h2>

<p>
  Una <a href="/es/requests/overview">solicitud</a> asigna un formulario publicado a una persona con nombre, con su propio enlace, sus
  propias respuestas precompletadas y su propio resultado. Así es como un agente pide algo a una persona real y descubre qué ha respondido.
</p>

<h3 id="requests-create">Crear una solicitud</h3>

<p>
  Empieza siempre con <code>fields_list(formId)</code>. Devuelve las claves direccionables de la versión publicada actual del formulario,
  cada una con una línea <code>usage</code> que indica en qué argumento va la clave — las preguntas visibles van en <code>prefill</code>,
  los campos ocultos en <code>context</code>. Nunca derives una clave a partir del título de una pregunta, y vuelve a leer después de{' '}
  <code>form_publish</code>.
</p>

```
{
  "formId": "j57...",
  "recipient": { "email": "ada@acme.com", "name": "Ada" },
  "prefill": { "company_name": "Acme", "plan": "pro" },
  "readonly": ["company_name"],
  "context": { "crm_id": "A-42" },
  "metadata": { "run_id": "exec_918" },
  "delivery": "email",
  "expiresAt": 1780000000000,
  "callbackUrl": "https://hooks.acme.com/formstep",
  "idempotencyKey": "po-42"
}
```

<p>El resultado incluye el enlace y el reloj:</p>

```
{
  "id": "kd7...",
  "status": "pending",
  "url": "https://form.formstep.io/r/rq_...",
  "deliveryStatus": "queued",
  "expiresAt": 1780000000000,
  "createdAt": 1747000000000,
  "deduplicated": false,
  "next": "..."
}
```

<p>
  <code>delivery</code> vale <code>"none"</code> por defecto, lo que te da <code>url</code> para que la entregues tú mismo;{' '}
  <code>"email"</code> envía la invitación y necesita <code>recipient.email</code> en un plan Pro o Business, o con una de las 10
  invitaciones gratis de una cuenta Free. <code>readonly</code> bloquea campos que el destinatario no puede editar, y toda clave bloqueada
  también debe estar precompletada. <code>context</code> solo acepta claves de campos ocultos, mientras que <code>metadata</code> es
  contabilidad opaca que se refleja en <code>request_get</code> y en el callback. <code>expiresAt</code> son milisegundos epoch, con 30 días
  por defecto y un máximo de 365 días. <code>idempotencyKey</code> tiene alcance de espacio de trabajo durante 30 días: la misma clave con
  el mismo cuerpo devuelve la solicitud original con <code>deduplicated: true</code>, y un cuerpo distinto es un conflicto. Cada respuesta
  también incluye una línea <code>next</code> que le dice al agente qué hacer a continuación.
</p>
<p>
  <code>deliveryStatus</code> es <code>not_requested</code> hasta que se pone en cola una invitación, luego <code>queued</code> →{' '}
  <code>sent</code> o <code>failed</code>, y <code>bounced</code> en cuanto el proveedor de correo reporta un rebote definitivo o una queja.
  Crear una solicitud gasta una unidad de la asignación mensual del espacio de trabajo, responda o no el destinatario; una vez agotada,{' '}
  <code>request_create</code> falla con <code>MONTHLY_ALLOWANCE_REACHED</code>.
</p>

<h3 id="requests-callbacks">Callbacks o sondeo</h3>

<p>
  Con una <code>callbackUrl</code>, Formstep envía un POST una vez por cada evento terminal — finalización, expiración, cancelación —
  firmado con el secreto de firma de solicitudes del espacio de trabajo. Consulta <a href="/es/requests/callbacks">Callbacks y firma</a>{' '}
  para conocer el payload y la receta de verificación.
</p>

> ℹ️ **Los agentes autónomos deberían sondear**
> <p>
>     El secreto de firma de solicitudes solo aparece en la página de Credenciales de tu espacio de trabajo — nunca se devuelve por MCP ni por
>     la API. Un agente que se ejecuta por su cuenta, sin ninguna persona que levante y configure un receptor, no puede por tanto verificar un
>     callback. Omite <code>callbackUrl</code> y en su lugar consulta <code>request_get(requestId)</code> periódicamente, del orden de minutos
>     y no de segundos, hasta que <code>status</code> deje de ser <code>"pending"</code>. <code>expiresAt</code> acota cuánto tiempo merece la
>     pena hacerlo.
>   </p>

<h3 id="requests-test-mode">Modo de prueba</h3>

<p>
  Pasa <code>test: true</code> para ensayar todo el circuito antes de una ejecución real. El enlace se sigue abriendo y se puede completar,
  y el callback se dispara con <code>"test": true</code> — pero no se envía nada por correo sea lo que sea lo que diga <code>delivery</code>{' '}
  y la solicitud queda oculta de la página de Solicitudes y del embudo de analíticas, y su envío no cuenta en ningún sitio: ni cuota, ni
  exportaciones, ni integraciones. Las solicitudes de prueba solo aparecen en <code>request_list</code> cuando pasas{' '}
  <code>includeTest: true</code>. El enlace se cierra en un plazo de 24 horas, y en Free un workspace puede crear 10 solicitudes de prueba
  al día.
</p>

<h3 id="requests-documents">Documentos por solicitud</h3>

<p>
  Para entregarle a un destinatario un archivo — un borrador de contrato, su propio presupuesto — el formulario necesita un{' '}
  <strong>bloque de Documentos</strong>, que un autor o un agente inserta con <code>editor_insertDocumentsBlock</code>.{' '}
  <code>fields_list</code> lo reporta como <code>type: "documents"</code>. Los bytes nunca pasan por una herramienta:
</p>

<ol>
  <li>
    Llama a <code>document_create</code> con <code>formId</code>, <code>name</code>, <code>contentType</code> y el <code>size</code> exacto
    en bytes. Recibes <code>{'{ id, name, contentType, size, uploadUrl, expiresAt }'}</code>. Solo PDF e imágenes (sin documentos de
    Office), 25 MB por archivo, y 100 MB de documentos por solicitud.
  </li>
  <li>
    Haz un <code>PUT</code> de los bytes en bruto a <code>uploadUrl</code> antes de que pase una hora, con <code>Content-Type</code>{' '}
    ajustado al tipo que declaraste.
  </li>
  <li>
    Referéncialo desde <code>request_create</code>: <code>documents: [{'{ documentId, field?, name? }'}]</code>. <code>field</code> es la
    clave de campo del bloque de Documentos, opcional solo cuando el formulario tiene exactamente un bloque de ese tipo. <code>name</code>{' '}
    sobrescribe el nombre visible para esta solicitud.
  </li>
</ol>

<p>
  Los documentos de autor del bloque permanecen intactos y los tuyos aparecen debajo, solo para este destinatario.{' '}
  <code>request_create</code> verifica la subida antes de que la solicitud exista, así que <code>DOCUMENT_NOT_UPLOADED</code> significa que
  el paso 2 se saltó. Una subida puede ser referenciada por cualquier número de solicitudes, y sus bytes cuentan contra el almacenamiento de
  tu espacio de trabajo.
</p>

<h3 id="requests-domains">Dominios personalizados</h3>

<p>
  Pasa <code>domainId</code> a <code>request_create</code> para generar el enlace en uno de los{' '}
  <a href="/es/branding-domains/custom-domains">dominios personalizados</a> del espacio de trabajo. Los ids vienen de{' '}
  <code>formShareLink_list</code>, que los devuelve como <code>availableCustomDomains</code>. Si lo omites, el enlace usa el dominio en el
  que el formulario ya está publicado.
</p>

<h3 id="requests-reading">Leer los resultados</h3>

<p>
  <code>request_get</code> devuelve la solicitud completa. Una vez completada, <code>answers</code> contiene los valores del destinatario
  indexados por clave de campo, <code>display</code> las mismas claves como texto legible, y <code>outcome</code> — aprobación, rechazo o
  cambios — es su veredicto cuando el formulario tiene una <a href="/es/requests/overview">pregunta de decisión</a>. Una marca de tiempo{' '}
  <code>callbackFailedAt</code> significa que la entrega se quedó sin reintentos y nada llegó a tu endpoint; arregla el receptor y luego
  llama a <code>request_replayCallback</code>, que reenvía el id del evento original para que tu receptor deduplique en lugar de volver a
  ejecutarse. Tras que la política de retención del formulario elimine una solicitud, se establece <code>dataPurgedAt</code> y las
  respuestas se pierden para siempre.
</p>

<p>
  <code>request_list</code> recorre muchas a la vez, filtradas por <code>status</code>, <code>outcome</code>, <code>externalId</code> e{' '}
  <code>includeTest</code>. Pagina con <code>nextCursor</code>: una página puede, en raras ocasiones, volver con <code>items</code> vacío y{' '}
  <code>hasMore: true</code>, lo cual no es el final de la lista — pasa el cursor de vuelta y sigue.
</p>

<h2 id="resources">Recursos y prompts</h2>
<p>
  Cada skill y catálogo de herramientas es también un recurso MCP en <code>skill://&lt;name&gt;</code> — <code>skill://requests</code>,{' '}
  <code>skill://editor-inserts</code>. Un cliente que admite <code>resources/list</code> puede explorarlos y leerlos sin necesidad de llamar
  a <code>load_skill</code> o <code>load_tools</code>. El servidor también sirve cuatro prompts en <code>prompts/list</code>:{' '}
  <code>identity</code>, <code>capabilities</code>, <code>data_tools</code> y <code>editor_tools</code>.
</p>

<h2 id="confirmation">Herramientas que piden confirmación</h2>
<p>
  Cada herramienta lleva las pistas MCP <code>readOnlyHint</code> y <code>destructiveHint</code>, derivadas de su verbo. Estas herramientas
  están marcadas como destructivas, porque deshacerlas exige otra llamada o no es posible: <code>form_delete</code>,{' '}
  <code>form_unpublish</code>, <code>workspaceFolder_delete</code>, <code>editor_deleteElement</code>,{' '}
  <code>translationLanguage_delete</code> y <code>request_cancel</code>. La mayoría de los clientes piden confirmación al usuario antes de
  ejecutarlas, pero ese aviso es decisión del cliente, así que revisa sus ajustes de aprobación si necesitas un bloqueo estricto.
</p>

<h2 id="limitations">Limitaciones</h2>
<ul>
  <li>
    <strong>Sin subidas binarias mediante una llamada a herramienta.</strong> Las imágenes se establecen por URL: portadas, logotipos y
    bloques de imagen aceptan URIs <code>http(s)://</code> o <code>data:image</code>. Un documento por solicitud es la excepción:{' '}
    <code>document_create</code> devuelve una URL de subida que un cliente con acceso HTTP puede subir con <code>PUT</code> (consulta{' '}
    <a href="#requests-documents">Documentos por solicitud</a>). Para convertir un PDF o una captura de pantalla en un formulario, usa el{' '}
    <a href="/es/ai/ai-form-generation#files">chat de IA integrado</a>.
  </li>
  <li>
    <strong>Sin AI Skills del espacio de trabajo.</strong> Las <a href="/es/ai/ai-skills">habilidades escritas en Formstep</a> solo están
    disponibles en el chat de IA integrado. Las skills propias del servidor (<code>load_skill</code>) sí están disponibles por MCP.
  </li>
</ul>

<h2 id="api-token-clients">Conectar con un token de API</h2>
<p>
  La mayoría de los clientes inician sesión con OAuth: pega la URL sin ninguna cabecera y sigue{' '}
  <a href="/es/guides/ai-agents/connect">Conecta un agente de IA</a>. Un cliente que no puede abrir un navegador, como un script, un job de
  CI o un agente sin interfaz, envía en su lugar un <a href="/es/developers/api-tokens">token de API</a> como cabecera.
</p>
<p>Claude Code, desde la línea de comandos:</p>

```
claude mcp add --transport http formstep https://api.formstep.io/api/mcp \
  --header "Authorization: Bearer fb_YOUR_TOKEN"
```

<p>
  O en el <code>.mcp.json</code> de un proyecto:
</p>

```
{
  "mcpServers": {
    "formstep": {
      "type": "http",
      "url": "https://api.formstep.io/api/mcp",
      "headers": {
        "Authorization": "Bearer fb_YOUR_TOKEN"
      }
    }
  }
}
```

<p>
  Cursor, en <code>.cursor/mcp.json</code> o <code>~/.cursor/mcp.json</code>:
</p>

```
{
  "mcpServers": {
    "formstep": {
      "url": "https://api.formstep.io/api/mcp",
      "headers": {
        "Authorization": "Bearer fb_YOUR_TOKEN"
      }
    }
  }
}
```

<p>
  Para iniciar sesión con OAuth en lugar de un token, usa{' '}
  <a href="https://cursor.com/install-mcp?name=formstep&config=eyJ1cmwiOiJodHRwczovL2FwaS5mb3Jtc3RlcC5pby9hcGkvbWNwIn0=">Añadir a Cursor</a>
  . Añade la URL del servidor sin cabecera, y Cursor te pide que inicies sesión en Formstep.
</p>
<p>Otros clientes usan la misma URL y cabecera; consulta su documentación para saber dónde.</p>

<h2 id="oauth">Usar OAuth en lugar de tokens de API</h2>
<p>
  Una aplicación de terceros que se conecta en nombre de un usuario debería usar OAuth en lugar de pedir un token pegado. Formstep es un
  servidor de autorización OAuth 2.1 con PKCE obligatorio (S256) y tokens opacos — sin JWT, sin implicit grant. Claude desktop, Claude Code
  y el conector web de Claude.ai descubren todo esto desde el endpoint MCP, así que pegar la URL sin ninguna cabecera es suficiente: el 401
  apunta a <code>/.well-known/oauth-protected-resource</code>, y el cliente se encarga del resto.
</p>
<p>El flujo, para un cliente que escribas tú mismo:</p>
<ol>
  <li>
    <code>GET /.well-known/oauth-protected-resource</code>, luego <code>GET /.well-known/oauth-authorization-server</code> para las URLs de
    los endpoints, los ámbitos y los métodos de autenticación admitidos.
  </li>
  <li>
    <code>POST /oauth/register</code> con tus <code>redirect_uris</code> (registro dinámico de cliente, sin necesidad de credenciales).
    Recibes un <code>client_id</code>, más un <code>client_secret</code> si pediste algo distinto de{' '}
    <code>token_endpoint_auth_method: "none"</code>. Las redirect URIs deben ser HTTPS, o HTTP en <code>localhost</code>. El registro está
    limitado a 20 por hora por IP.
  </li>
  <li>
    Envía al usuario a <code>/oauth/authorize</code> con <code>response_type=code</code>, tu <code>client_id</code>, el{' '}
    <code>redirect_uri</code> registrado, <code>scope=mcp:read mcp:write offline_access</code>, <code>state</code>, y un{' '}
    <code>code_challenge</code> con <code>code_challenge_method=S256</code>. Inicia sesión, elige un espacio de trabajo y autoriza.
  </li>
  <li>
    Intercambia el código en <code>POST /oauth/token</code> con <code>grant_type=authorization_code</code> y tu <code>code_verifier</code>,
    en un plazo de 60 segundos. Los códigos son de un solo uso.
  </li>
  <li>
    Llama al endpoint MCP con <code>Authorization: Bearer fbo_...</code>. Los tokens de acceso duran 1 hora; los tokens de actualización
    duran 30 días y rotan en cada uso. Reutilizar un token de actualización ya gastado quema toda la cadena, así que guarda el más reciente.
  </li>
</ol>
<p>
  <code>POST /oauth/revoke</code> (RFC 7009) revoca un token de acceso o de actualización. Un usuario también puede desconectar toda la
  aplicación desde <strong>Aplicaciones conectadas</strong> en la página OAuth y claves de API, lo que elimina todos los tokens que tiene
  para ese espacio de trabajo.
</p>

<p>Si ya tienes un token en la mano, va en el mismo lugar que una clave de API:</p>

```
{
  "mcpServers": {
    "formstep": {
      "type": "http",
      "url": "https://api.formstep.io/api/mcp",
      "headers": {
        "Authorization": "Bearer fbo_USER_OAUTH_TOKEN"
      }
    }
  }
}
```

<h3 id="connected-apps">Aplicaciones conectadas</h3>
<p>
  Cada conexión OAuth aparece listada en <strong>Aplicaciones conectadas</strong>, en la página <strong>OAuth y claves de API</strong>, con
  la fecha en que se conectó y su último uso. Las conexiones son personales: solo el usuario que la autorizó puede verla, y los
  administradores del espacio de trabajo no pueden ver ni revocar la de otro miembro. Desconectar surte efecto de inmediato. Un usuario que
  abandona el espacio de trabajo, o es eliminado de él, pierde todas sus conexiones a ese espacio.
</p>

<h2 id="next-steps">Próximos pasos</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Conectar un agente de IA](/es/guides/ai-agents/connect) — Configuración paso a paso en cualquier cliente MCP
  - [Referencia de webhooks](/es/developers/webhooks-reference) — Esquema del payload y firma
  - [API REST](/es/developers/rest-api) — Métodos de API para acceso programático
  - [Planes y precios](/es/subscription-billing/plans-pricing) — Compara el acceso a la API y los límites por plan
</div>
