# Serveur MCP

Utilisez Formstep depuis Claude, Cursor et d'autres outils compatibles MCP.

## Serveur MCP

Le serveur Model Context Protocol (MCP) permet aux agents IA de lire et de modifier vos formulaires Formstep grâce à des outils riches et pilotés par schéma.

<h2 id="what">Qu’est-ce que c’est</h2>
<p>
  MCP est un standard ouvert permettant aux outils IA de se connecter à des services externes. Formstep expose un point de terminaison MCP
  hébergé auquel tout client compatible MCP peut se connecter, notamment Claude Code, Claude desktop et Cursor.
</p>

<h2 id="connection">Connexion</h2>

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

<p>Deux types de token porteur (bearer) fonctionnent :</p>
<ul>
  <li>
    <strong>Token API</strong> (<code>fb_...</code>) — créé depuis <a href="/fr/developers/api-tokens">les tokens API</a>. Idéal pour un
    usage personnel et une configuration rapide.
  </li>
  <li>
    <strong>Token d’accès OAuth</strong> (<code>fbo_...</code>) — émis par le <a href="#oauth">flux OAuth</a>. Idéal pour les applications
    tierces qui se connectent au nom d’un utilisateur.
  </li>
</ul>
<p>
  Les deux sont liés à exactement un espace de travail et atteignent les mêmes outils. Un appel d’outil qui nomme un autre espace de
  travail, ou un formulaire qui s’y trouve, échoue avec <code>FORBIDDEN</code>. Les tokens OAuth portent aussi des portées
  <code>mcp:read</code>, <code>mcp:write</code> et <code>offline_access</code>, mais aucun outil n’est aujourd’hui conditionné par elles —
  traitez tout token comme un accès complet à l’intérieur de son espace de travail.
</p>
<p>
  Les appels d’outils sont limités à 120 par minute et par token, en commun avec <a href="/fr/developers/rest-api">l’API</a> : seul{' '}
  <code>tools/call</code> consomme ce budget, tandis que <code>initialize</code>, <code>tools/list</code>, <code>prompts/*</code> et{' '}
  <code>resources/*</code> sont gratuits. Au-delà du budget, l’appel renvoie tout de même HTTP 200 avec un résultat d’outil en échec portant{' '}
  <code>RATE_LIMITED</code> et un <code>retryAfterMs</code> — sondez sur une minuterie, jamais en boucle.
</p>

> 💡 **Où obtenir un token**
> <p>
>     Ouvrez <strong>OAuth et clés API</strong> dans la barre latérale de votre espace de travail pour créer des tokens API et consulter les
>     applications OAuth connectées. Consultez <a href="/fr/developers/api-tokens">les tokens API</a> pour un guide pas à pas.
>   </p>

<h2 id="core-tools">Outils principaux</h2>
<p>
  Chaque outil est annoncé sur <code>tools/list</code> lorsqu’un client se connecte. Les clients qui chargent les schémas à la demande,
  comme Claude Code, récupèrent le schéma complet d’un outil quand une tâche en a besoin. Le tableau ci-dessous couvre les outils principaux
  par lesquels la plupart des tâches commencent ; <code>load_tools</code> (catalogues) et <code>load_skill</code> (guides métier)
  documentent le reste.
</p>

<p>
  D’autres variantes d’insertion — heure, téléchargement de fichier, signature, paiement, matrice/grille, classement, choix d’image, bouton
  bascule, tableau, liste, ligne, champ calculé, champ masqué, variable en ligne, contenu intégré (<code>editor_insertEmbedded</code> pour
  YouTube, Google Maps ou des intégrations iframe) et un bloc de logique conditionnelle (<code>editor_insertLogic</code>) — sont aussi sur{' '}
  <code>tools/list</code>. Chargez <code>load_skill("question-types")</code> pour l’ensemble complet, avec le nom de l’outil et les champs
  de chacun. La logique conditionnelle s’écrit avec <code>editor_setLogic</code> dans le catalogue{' '}
  <a href="#tool-catalogs">editor-actions</a>.
</p>

<h2 id="tool-catalogs">Catalogues d’outils</h2>
<p>
  Ces outils sont aussi sur <code>tools/list</code>. Exécutez <code>load_tools</code> avec un nom de catalogue pour obtenir une
  documentation enrichie (introduction, schémas complets, patterns d’utilisation, cas limites) pour les outils groupés, puis appelez-les
  directement.
</p>

<p>
  Le catalogue <code>request-lifecycle</code> liste les huit outils de demandes car le chat intégré annonce un ensemble principal plus
  restreint. Sur ce point de terminaison, les huit sont déjà sur <code>tools/list</code>, donc ce que le catalogue ajoute, c’est la
  documentation.
</p>

<h2 id="skills">Compétences (connaissances métier)</h2>
<p>
  Les compétences sont des guides intégrés que l’agent peut charger via <code>load_skill</code>. Elles fournissent des connaissances métier
  qui aident l’agent à prendre de meilleures décisions — pas des schémas d’outils, mais des conseils de conception et la sémantique des
  champs.
</p>

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

<p>
  Une <a href="/fr/requests/overview">demande</a> assigne un formulaire publié à un destinataire nommé, avec son propre lien, ses propres
  réponses préremplies, et son propre résultat. C’est ainsi qu’un agent demande quelque chose à une personne réelle et découvre ce qu’elle a
  répondu.
</p>

<h3 id="requests-create">Créer une demande</h3>

<p>
  Commencez toujours par <code>fields_list(formId)</code>. Cela renvoie les clés adressables de la version actuellement publiée du
  formulaire, chacune avec une ligne <code>usage</code> indiquant dans quel argument la clé doit aller — les questions visibles vont dans{' '}
  <code>prefill</code>, les champs cachés dans <code>context</code>. Ne dérivez jamais une clé à partir du titre d’une question, et relisez
  après <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>Le résultat contient le lien et l’horloge :</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> vaut <code>"none"</code> par défaut, ce qui vous donne <code>url</code> à livrer vous-même ; <code>"email"</code>{' '}
  envoie l’invitation et nécessite <code>recipient.email</code> sur un plan Pro ou Business, ou sur l’une des 10 invitations gratuites d’un
  compte Free. <code>readonly</code> verrouille des champs que le destinataire ne peut pas modifier, et toute clé verrouillée doit aussi
  être préremplie. <code>context</code> n’accepte que des clés de champs cachés, tandis que <code>metadata</code> est un suivi opaque
  renvoyé dans <code>request_get</code> et dans le callback. <code>expiresAt</code> est en millisecondes epoch, par défaut 30 jours et
  plafonné à 365 jours. <code>idempotencyKey</code> est valable à l’échelle de l’espace de travail pendant 30 jours : la même clé avec le
  même corps renvoie la demande d’origine avec <code>deduplicated: true</code>, et un corps différent est un conflit. Chaque réponse porte
  aussi une ligne <code>next</code> qui indique à l’agent quoi faire ensuite.
</p>
<p>
  <code>deliveryStatus</code> vaut <code>not_requested</code> jusqu’à ce qu’une invitation soit mise en file, puis <code>queued</code> →{' '}
  <code>sent</code> ou <code>failed</code>, et <code>bounced</code> une fois que le fournisseur d’e-mail signale un rejet définitif ou une
  plainte. Créer une demande dépense une unité de l’allocation mensuelle de l’espace de travail, que le destinataire réponde ou non ; une
  fois épuisée, <code>request_create</code> échoue avec <code>MONTHLY_ALLOWANCE_REACHED</code>.
</p>

<h3 id="requests-callbacks">Callbacks ou sondage</h3>

<p>
  Avec une <code>callbackUrl</code>, Formstep envoie un POST une fois par événement terminal — complétion, expiration, annulation — signé
  avec le secret de signature des demandes de l’espace de travail. Voir <a href="/fr/requests/callbacks">Callbacks et signature</a> pour le
  payload et la recette de vérification.
</p>

> ℹ️ **Les agents autonomes devraient sonder**
> <p>
>     Le secret de signature des demandes n’apparaît que sur la page Identifiants de votre espace de travail — il n’est jamais renvoyé via MCP
>     ou l’API. Un agent qui s’exécute seul, sans humain pour mettre en place et configurer un récepteur, ne peut donc pas vérifier un
>     callback. Omettez <code>callbackUrl</code> et sondez plutôt <code>request_get(requestId)</code>, à l’échelle de quelques minutes plutôt
>     que de quelques secondes, jusqu’à ce que <code>status</code> quitte <code>"pending"</code>. <code>expiresAt</code> borne combien de
>     temps cela vaut la peine.
>   </p>

<h3 id="requests-test-mode">Mode test</h3>

<p>
  Passez <code>test: true</code> pour répéter tout le circuit avant une exécution réelle. Le lien s’ouvre toujours et peut être complété, et
  le callback se déclenche avec <code>"test": true</code> — mais rien n’est envoyé par e-mail quoi que dise <code>delivery</code>, la
  demande reste masquée de la page Demandes et de l’entonnoir analytique, et sa soumission ne compte nulle part : pas de quota, pas
  d’exports, pas d’intégrations. Les demandes de test n’apparaissent dans <code>request_list</code> que si vous passez{' '}
  <code>includeTest: true</code>. Le lien se ferme dans les 24 heures, et sur Free un workspace peut créer 10 demandes de test par jour.
</p>

<h3 id="requests-documents">Documents par demande</h3>

<p>
  Pour remettre un fichier à un destinataire — un projet de contrat, son propre devis — le formulaire a besoin d’un{' '}
  <strong>bloc Documents</strong>, qu’un auteur ou un agent insère avec <code>editor_insertDocumentsBlock</code>. <code>fields_list</code>{' '}
  le signale comme <code>type: "documents"</code>. Les octets ne transitent jamais par un outil :
</p>

<ol>
  <li>
    Appelez <code>document_create</code> avec <code>formId</code>, <code>name</code>, <code>contentType</code>, et le <code>size</code>{' '}
    exact en octets. Vous récupérez <code>{'{ id, name, contentType, size, uploadUrl, expiresAt }'}</code>. PDF et images uniquement (pas de
    documents Office), 25 Mo par fichier, et 100 Mo de documents par demande.
  </li>
  <li>
    Faites un <code>PUT</code> des octets bruts vers <code>uploadUrl</code> dans l’heure, avec <code>Content-Type</code> réglé sur le type
    déclaré.
  </li>
  <li>
    Référencez-le depuis <code>request_create</code> : <code>documents: [{'{ documentId, field?, name? }'}]</code>. <code>field</code> est
    la clé de champ du bloc Documents, optionnelle seulement quand le formulaire a exactement un bloc de ce type. <code>name</code> remplace
    le nom d’affichage pour cette demande.
  </li>
</ol>

<p>
  Les documents de l’auteur du bloc restent en place et les vôtres apparaissent en dessous, pour ce seul destinataire.{' '}
  <code>request_create</code> vérifie le téléversement avant que la demande n’existe, donc <code>DOCUMENT_NOT_UPLOADED</code> signifie que
  l’étape 2 a été sautée. Un téléversement peut être référencé par un nombre quelconque de demandes, et ses octets comptent dans le stockage
  de votre espace de travail.
</p>

<h3 id="requests-domains">Domaines personnalisés</h3>

<p>
  Passez <code>domainId</code> à <code>request_create</code> pour créer le lien sur l’un des{' '}
  <a href="/fr/branding-domains/custom-domains">domaines personnalisés</a> de l’espace de travail. Les ids viennent de{' '}
  <code>formShareLink_list</code>, qui les renvoie sous <code>availableCustomDomains</code>. Si vous l’omettez, le lien prend le domaine sur
  lequel le formulaire est déjà publié.
</p>

<h3 id="requests-reading">Lire les résultats</h3>

<p>
  <code>request_get</code> renvoie la demande entière. Une fois terminée, <code>answers</code> contient les valeurs du destinataire indexées
  par clé de champ, <code>display</code> les mêmes clés en texte lisible, et <code>outcome</code> — approbation, refus ou modifications —
  est son verdict quand le formulaire a une <a href="/fr/requests/overview">question de décision</a>. Un horodatage{' '}
  <code>callbackFailedAt</code> signifie que la livraison a épuisé ses tentatives et que rien n’a atteint votre endpoint ; réparez le
  récepteur, puis appelez <code>request_replayCallback</code>, qui renvoie l’id de l’événement d’origine pour que votre récepteur déduplique
  au lieu de relancer. Une fois que la politique de rétention du formulaire a supprimé une demande, <code>dataPurgedAt</code> est défini et
  les réponses sont perdues pour de bon.
</p>

<p>
  <code>request_list</code> balaie plusieurs demandes à la fois, filtrées par <code>status</code>, <code>outcome</code>,{' '}
  <code>externalId</code>, et <code>includeTest</code>. Paginez avec <code>nextCursor</code> : une page peut rarement revenir avec des{' '}
  <code>items</code> vides et <code>hasMore: true</code>, ce qui ne marque pas la fin de la liste — renvoyez le curseur et continuez.
</p>

<h2 id="resources">Ressources et prompts</h2>
<p>
  Chaque compétence et catalogue d’outils est aussi une ressource MCP à <code>skill://&lt;nom&gt;</code> — <code>skill://requests</code>,{' '}
  <code>skill://editor-inserts</code>. Un client qui prend en charge <code>resources/list</code> peut les parcourir et les lire sans appeler{' '}
  <code>load_skill</code> ou <code>load_tools</code>. Le serveur sert aussi quatre prompts sur <code>prompts/list</code> :{' '}
  <code>identity</code>, <code>capabilities</code>, <code>data_tools</code>, et <code>editor_tools</code>.
</p>

<h2 id="confirmation">Les outils qui demandent d’abord confirmation</h2>
<p>
  Chaque outil porte les indices MCP <code>readOnlyHint</code> et <code>destructiveHint</code>, dérivés de son verbe. Ces outils sont
  marqués comme destructifs, car les annuler nécessite un autre appel ou n’est pas possible : <code>form_delete</code>,{' '}
  <code>form_unpublish</code>, <code>workspaceFolder_delete</code>, <code>editor_deleteElement</code>,{' '}
  <code>translationLanguage_delete</code>, et <code>request_cancel</code>. La plupart des clients demandent une confirmation à l’utilisateur
  avant de les exécuter, mais l’invite reste la décision du client — vérifiez ses paramètres d’approbation si vous avez besoin d’un blocage
  strict.
</p>

<h2 id="limitations">Limites</h2>
<ul>
  <li>
    <strong>Aucun téléversement binaire via un appel d’outil.</strong> Les images sont définies par URL : couvertures, logos et blocs image
    acceptent des URI <code>http(s)://</code> ou <code>data:image</code>. Un document par demande fait exception :{' '}
    <code>document_create</code> renvoie une URL de téléversement qu’un client avec accès HTTP peut cibler avec <code>PUT</code> (voir{' '}
    <a href="#requests-documents">Documents par demande</a>). Pour transformer un PDF ou une capture d’écran en formulaire, utilisez le{' '}
    <a href="/fr/ai/ai-form-generation#files">chat IA intégré</a>.
  </li>
  <li>
    <strong>Aucune compétence IA d’espace de travail.</strong> Les <a href="/fr/ai/ai-skills">compétences écrites dans Formstep</a> ne sont
    disponibles que dans le chat IA intégré. Les compétences propres au serveur (<code>load_skill</code>) sont disponibles via MCP.
  </li>
</ul>

<h2 id="api-token-clients">Se connecter avec un token API</h2>
<p>
  La plupart des clients se connectent avec OAuth : ajoutez l’URL sans en-tête et suivez{' '}
  <a href="/fr/guides/ai-agents/connect">Connecter un agent IA</a>. Un client qui ne peut pas ouvrir de navigateur, comme un script, un job
  CI ou un agent headless, envoie plutôt un <a href="/fr/developers/api-tokens">token API</a> en en-tête.
</p>
<p>Claude Code, en ligne de commande :</p>

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

<p>
  Ou dans le <code>.mcp.json</code> d’un projet :
</p>

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

<p>
  Cursor, dans <code>.cursor/mcp.json</code> ou <code>~/.cursor/mcp.json</code> :
</p>

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

<p>
  Pour vous connecter avec OAuth plutôt qu’avec un token, utilisez{' '}
  <a href="https://cursor.com/install-mcp?name=formstep&config=eyJ1cmwiOiJodHRwczovL2FwaS5mb3Jtc3RlcC5pby9hcGkvbWNwIn0=">
    Ajouter à Cursor
  </a>
  . Cela ajoute l’URL du serveur sans en-tête, et Cursor vous demande de vous connecter à Formstep.
</p>
<p>Les autres clients prennent la même URL et le même en-tête ; consultez leur documentation pour savoir où les placer.</p>

<h2 id="oauth">Utiliser OAuth plutôt que des tokens API</h2>
<p>
  Une application tierce qui se connecte au nom d’un utilisateur devrait utiliser OAuth plutôt que de demander un token collé à la main.
  Formstep est un serveur d’autorisation OAuth 2.1 avec PKCE obligatoire (S256) et des tokens opaques — pas de JWT, pas de grant implicite.
  Claude desktop, Claude Code et le connecteur web Claude.ai découvrent tout cela depuis le point de terminaison MCP, donc coller l’URL sans
  en-tête suffit : le 401 pointe vers <code>/.well-known/oauth-protected-resource</code>, et le client prend le relais à partir de là.
</p>
<p>Le flux, pour un client que vous écrivez vous-même :</p>
<ol>
  <li>
    <code>GET /.well-known/oauth-protected-resource</code>, puis <code>GET /.well-known/oauth-authorization-server</code> pour les URLs des
    points de terminaison, les portées et les méthodes d’authentification prises en charge.
  </li>
  <li>
    <code>POST /oauth/register</code> avec vos <code>redirect_uris</code> (enregistrement de client dynamique, aucun identifiant requis).
    Vous obtenez un <code>client_id</code>, plus un <code>client_secret</code> si vous avez demandé autre chose que{' '}
    <code>token_endpoint_auth_method: "none"</code>. Les URIs de redirection doivent être en HTTPS, ou en HTTP sur <code>localhost</code>.
    L’enregistrement est plafonné à 20 par heure et par IP.
  </li>
  <li>
    Envoyez l’utilisateur vers <code>/oauth/authorize</code> avec <code>response_type=code</code>, votre <code>client_id</code>, le{' '}
    <code>redirect_uri</code> enregistré, <code>scope=mcp:read mcp:write offline_access</code>, <code>state</code>, et un{' '}
    <code>code_challenge</code> avec <code>code_challenge_method=S256</code>. Il se connecte, choisit un espace de travail, et autorise.
  </li>
  <li>
    Échangez le code sur <code>POST /oauth/token</code> avec <code>grant_type=authorization_code</code> et votre <code>code_verifier</code>,
    dans les 60 secondes. Les codes sont à usage unique.
  </li>
  <li>
    Appelez le point de terminaison MCP avec <code>Authorization: Bearer fbo_...</code>. Les tokens d’accès durent 1 heure ; les tokens de
    rafraîchissement durent 30 jours et tournent à chaque utilisation. Réutiliser un token de rafraîchissement déjà consommé brûle toute la
    chaîne, donc conservez le plus récent.
  </li>
</ol>
<p>
  <code>POST /oauth/revoke</code> (RFC 7009) révoque un token d’accès ou de rafraîchissement. Un utilisateur peut aussi déconnecter toute
  l’application depuis <strong>Applications connectées</strong> sur la page OAuth et clés API, ce qui tue tous les tokens qu’elle détient
  pour cet espace de travail.
</p>

<p>Si vous avez déjà un token en main, il va au même endroit qu’une clé API :</p>

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

<h3 id="connected-apps">Applications connectées</h3>
<p>
  Chaque connexion OAuth est listée sous <strong>Applications connectées</strong> sur la page <strong>OAuth et clés API</strong>, avec la
  date de connexion et de dernière utilisation. Les connexions sont personnelles : seul l’utilisateur qui a autorisé une connexion peut la
  voir, et les administrateurs de l’espace de travail ne peuvent ni la consulter ni la révoquer pour un autre membre. La déconnexion prend
  effet immédiatement. Un utilisateur qui quitte l’espace de travail ou en est retiré perd toutes ses connexions à celui-ci.
</p>

<h2 id="next-steps">Prochaines étapes</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Connecter un agent IA](/fr/guides/ai-agents/connect) — Configuration étape par étape dans n’importe quel client MCP
  - [Référence webhooks](/fr/developers/webhooks-reference) — Schéma de payload et signature
  - [API REST](/fr/developers/rest-api) — Méthodes d'API pour un accès programmatique
  - [Plans et tarifs](/fr/subscription-billing/plans-pricing) — Comparer l’accès API et les limites par plan
</div>
