# MCP-Server

Nutze Formstep aus Claude, Cursor und anderen MCP-kompatiblen Tools.

## MCP-Server

Der Model Context Protocol (MCP)-Server ermöglicht es KI-Agenten, deine Formstep-Formulare mit umfangreichen, schemagesteuerten Tools zu lesen und zu bearbeiten.

<h2 id="what">Was es ist</h2>
<p>
  MCP ist ein offener Standard, über den KI-Tools eine Verbindung zu externen Diensten herstellen. Formstep stellt einen gehosteten
  MCP-Endpunkt bereit, mit dem sich jeder MCP-kompatible Client verbinden kann – darunter Claude Code, Claude Desktop und Cursor.
</p>

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

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

<p>Zwei Arten von Bearer-Token funktionieren:</p>
<ul>
  <li>
    <strong>API-Token</strong> (<code>fb_...</code>) — erstellt über <a href="/de/developers/api-tokens">API-Tokens</a>. Ideal für den
    persönlichen Gebrauch und eine schnelle Einrichtung.
  </li>
  <li>
    <strong>OAuth-Zugriffstoken</strong> (<code>fbo_...</code>) — wird vom <a href="#oauth">OAuth-Flow</a> ausgestellt. Ideal für
    Drittanbieter-Apps, die im Auftrag eines Nutzers eine Verbindung herstellen.
  </li>
</ul>
<p>
  Beide sind an genau einen Arbeitsbereich gebunden und erreichen dieselben Tools. Ein Tool-Aufruf, der einen anderen Arbeitsbereich oder
  ein Formular darin nennt, schlägt mit <code>FORBIDDEN</code> fehl. OAuth-Token tragen außerdem Scopes (<code>mcp:read</code>,{' '}
  <code>mcp:write</code>, <code>offline_access</code>), aber heute ist kein Tool davon abhängig — behandle jedes Token als vollen Zugriff
  innerhalb seines Arbeitsbereichs.
</p>
<p>
  Tool-Aufrufe sind auf 120 pro Minute pro Token begrenzt, gemeinsam genutzt mit der <a href="/de/developers/rest-api">API</a>: Nur{' '}
  <code>tools/call</code> verbraucht das Budget, während <code>initialize</code>, <code>tools/list</code>, <code>prompts/*</code> und{' '}
  <code>resources/*</code> kostenlos sind. Bei Überschreitung liefert der Aufruf trotzdem HTTP 200 mit einem fehlgeschlagenen Tool-Ergebnis,
  das <code>RATE_LIMITED</code> und ein <code>retryAfterMs</code> trägt — polle mit einem Timer, nie in einer Schleife.
</p>

> 💡 **Wo du ein Token bekommst**
> <p>
>     Öffne <strong>OAuth und API-Schlüssel</strong> in der Seitenleiste deines Arbeitsbereichs, um API-Tokens zu erstellen und verbundene
>     OAuth-Apps anzuzeigen. Weitere Informationen findest du unter <a href="/de/developers/api-tokens">API-Tokens</a>.
>   </p>

<h2 id="core-tools">Kern-Tools</h2>
<p>
  Jedes Tool wird über <code>tools/list</code> angezeigt, wenn sich ein Client verbindet. Clients, die Schemas bei Bedarf laden, etwa Claude
  Code, rufen das vollständige Schema eines Tools ab, sobald eine Aufgabe es braucht. Die folgende Tabelle deckt die Kern-Tools ab, mit
  denen die meisten Aufgaben beginnen; <code>load_tools</code> (Kataloge) und <code>load_skill</code> (Domänenleitfäden) dokumentieren den
  Rest.
</p>

<p>
  Weitere Insert-Varianten — Uhrzeit, Dateiupload, Unterschrift, Zahlung, Matrix/Raster, Rangordnung, Bildauswahl, Umschalter, Tabelle,
  Liste, Zeile, berechnetes Feld, verstecktes Feld, Inline-Variable, eingebettete Inhalte (<code>editor_insertEmbedded</code> für YouTube,
  Google Maps oder iframe-Einbettungen) und ein Block für bedingte Logik (<code>editor_insertLogic</code>) — stehen ebenfalls auf{' '}
  <code>tools/list</code>. Lade <code>load_skill("question-types")</code> für den vollständigen Satz, jeweils mit Tool-Name und Feldern.
  Bedingte Logik wird mit <code>editor_setLogic</code> im <a href="#tool-catalogs">editor-actions</a>-Katalog erstellt.
</p>

<h2 id="tool-catalogs">Tool-Kataloge</h2>
<p>
  Auch diese Tools stehen auf <code>tools/list</code>. Führe <code>load_tools</code> mit einem Katalognamen aus, um erweiterte Dokumentation
  (Einführung, vollständige Schemas, Verwendungsmuster, Grenzfälle) für die gruppierten Tools zu erhalten, und rufe sie dann direkt auf.
</p>

<p>
  Der Katalog <code>request-lifecycle</code> listet alle acht Request-Tools auf, weil der In-App-Chat ein kleineres Kern-Set anzeigt. Über
  diesen Endpunkt sind bereits alle acht über <code>tools/list</code> verfügbar, sodass der Katalog nur Dokumentation hinzufügt.
</p>

<h2 id="skills">Skills (Domänenwissen)</h2>
<p>
  Skills sind integrierte Leitfäden, die der Agent über <code>load_skill</code> laden kann. Sie liefern Domänenwissen, das dem Agenten
  hilft, bessere Entscheidungen zu treffen – keine Tool-Schemas, sondern Designhinweise und Feldsemantik.
</p>

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

<p>
  Ein <a href="/de/requests/overview">Request</a> weist ein veröffentlichtes Formular einer namentlich bekannten empfangenden Person zu, mit
  eigenem Link, eigenen vorausgefüllten Antworten und eigenem Ergebnis. So bittet ein Agent eine echte Person um etwas und erfährt, was sie
  geantwortet hat.
</p>

<h3 id="requests-create">Einen Request erstellen</h3>

<p>
  Starte immer mit <code>fields_list(formId)</code>. Es liefert die ansprechbaren Schlüssel der aktuell veröffentlichten Version des
  Formulars, jeweils mit einer <code>usage</code>-Zeile, die sagt, in welches Argument der Schlüssel gehört — sichtbare Fragen gehören in{' '}
  <code>prefill</code>, versteckte Felder in <code>context</code>. Leite einen Schlüssel niemals aus einem Fragetitel ab, und lies nach{' '}
  <code>form_publish</code> erneut ein.
</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>Das Ergebnis liefert den Link und die Uhr:</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> ist standardmäßig <code>"none"</code>, wodurch du <code>url</code> zur eigenen Zustellung erhältst;{' '}
  <code>"email"</code> versendet die Einladung und braucht <code>recipient.email</code> auf einem Pro- oder Business-Plan, oder eine der 10
  kostenlosen Einladungen eines Free-Kontos. <code>readonly</code> sperrt Felder, die die empfangende Person nicht bearbeiten darf, und
  jeder gesperrte Schlüssel muss auch vorausgefüllt sein. <code>context</code> nimmt nur Schlüssel versteckter Felder an, während{' '}
  <code>metadata</code> undurchsichtige Buchhaltung ist, die bei <code>request_get</code> und im Callback zurückgespiegelt wird.{' '}
  <code>expiresAt</code> sind Epoch-Millisekunden, standardmäßig 30 Tage im Voraus und maximal 365 Tage. <code>idempotencyKey</code> ist 30
  Tage lang arbeitsbereichsweit gültig: derselbe Schlüssel mit demselben Body liefert den ursprünglichen Request mit{' '}
  <code>deduplicated: true</code> zurück, ein abweichender Body ist ein Konflikt. Jede Antwort trägt außerdem eine <code>next</code>-Zeile,
  die dem Agenten sagt, was von hier aus zu tun ist.
</p>
<p>
  <code>deliveryStatus</code> ist <code>not_requested</code>, bis eine Einladung eingereiht wird, dann <code>queued</code> →{' '}
  <code>sent</code> oder <code>failed</code>, und <code>bounced</code>, sobald der Mail-Anbieter einen Hard Bounce oder eine Beschwerde
  meldet. Einen Request zu erstellen verbraucht eine Einheit des monatlichen Kontingents des Arbeitsbereichs, egal ob die empfangende Person
  je antwortet; ist es aufgebraucht, schlägt <code>request_create</code> mit <code>MONTHLY_ALLOWANCE_REACHED</code> fehl.
</p>

<h3 id="requests-callbacks">Callbacks oder Polling</h3>

<p>
  Mit einer <code>callbackUrl</code> sendet Formstep einmal pro Endzustand — Abschluss, Ablauf, Stornierung — einen POST, signiert mit dem
  Request-Signaturgeheimnis des Arbeitsbereichs. Siehe <a href="/de/requests/callbacks">Callbacks und Signierung</a> für die Payload und das
  Verifizierungsrezept.
</p>

> ℹ️ **Autonome Agenten sollten pollen**
> <p>
>     Das Request-Signaturgeheimnis erscheint ausschließlich auf der Credentials-Seite deines Arbeitsbereichs — es wird niemals über MCP oder
>     die API zurückgegeben. Ein Agent, der eigenständig läuft, ohne dass ein Mensch einen Empfänger aufsetzt und konfiguriert, kann einen
>     Callback daher nicht verifizieren. Lass <code>callbackUrl</code> weg und polle stattdessen <code>request_get(requestId)</code>, eher im
>     Minuten- als im Sekundentakt, bis <code>status</code> <code>"pending"</code> verlässt. <code>expiresAt</code> begrenzt, wie lange sich
>     das lohnt.
>   </p>

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

<p>
  Übergib <code>test: true</code>, um die gesamte Verkabelung vor einem echten Lauf durchzuspielen. Der Link öffnet sich weiterhin und lässt
  sich abschließen, und der Callback feuert mit <code>"test": true</code> — aber es wird nichts per E-Mail versendet, egal was{' '}
  <code>delivery</code> sagt, der Request bleibt auf der Requests-Seite und im Analytics-Funnel verborgen, und seine Einreichung zählt
  nirgends: kein Kontingent, keine Exporte, keine Integrationen. Testanfragen erscheinen in <code>request_list</code> nur, wenn du{' '}
  <code>includeTest: true</code> übergibst. Der Link schließt sich innerhalb von 24 Stunden, und auf Free darf ein Workspace 10 Testanfragen
  pro Tag erstellen.
</p>

<h3 id="requests-documents">Dokumente pro Request</h3>

<p>
  Um einer empfangenden Person eine Datei zu übergeben — einen Vertragsentwurf, ihr eigenes Angebot — braucht das Formular einen{' '}
  <strong>Dokumente-Block</strong>, den ein Autor oder ein Agent mit <code>editor_insertDocumentsBlock</code> einfügt.{' '}
  <code>fields_list</code> meldet ihn als <code>type: "documents"</code>. Die Bytes wandern nie durch ein Tool:
</p>

<ol>
  <li>
    Rufe <code>document_create</code> auf mit <code>formId</code>, <code>name</code>, <code>contentType</code> und der exakten{' '}
    <code>size</code> in Bytes. Du bekommst <code>{'{ id, name, contentType, size, uploadUrl, expiresAt }'}</code> zurück. Nur PDF und
    Bilder (keine Office-Dokumente), 25 MB pro Datei und 100 MB Dokumente pro Request.
  </li>
  <li>
    <code>PUT</code>e die rohen Bytes innerhalb einer Stunde an <code>uploadUrl</code>, mit <code>Content-Type</code> auf den deklarierten
    Typ gesetzt.
  </li>
  <li>
    Referenziere sie von <code>request_create</code> aus: <code>documents: [{'{ documentId, field?, name? }'}]</code>. <code>field</code>{' '}
    ist der Feldschlüssel des Dokumente-Blocks, nur optional, wenn das Formular genau einen solchen Block hat. <code>name</code>{' '}
    überschreibt den Anzeigenamen für diesen Request.
  </li>
</ol>

<p>
  Die vom Autor hinterlegten Dokumente bleiben unverändert, und deine erscheinen darunter, nur für diese empfangende Person.{' '}
  <code>request_create</code> prüft den Upload, bevor der Request existiert, sodass <code>DOCUMENT_NOT_UPLOADED</code> bedeutet, dass
  Schritt 2 übersprungen wurde. Ein Upload kann von beliebig vielen Requests referenziert werden, und seine Bytes zählen gegen deinen
  Workspace-Speicher.
</p>

<h3 id="requests-domains">Benutzerdefinierte Domains</h3>

<p>
  Übergib <code>domainId</code> an <code>request_create</code>, um den Link auf einer der{' '}
  <a href="/de/branding-domains/custom-domains">benutzerdefinierten Domains</a> des Arbeitsbereichs zu erstellen. Die IDs kommen von{' '}
  <code>formShareLink_list</code>, das sie als <code>availableCustomDomains</code> zurückgibt. Lässt du sie weg, nimmt der Link die Domain,
  unter der das Formular bereits veröffentlicht ist.
</p>

<h3 id="requests-reading">Ergebnisse lesen</h3>

<p>
  <code>request_get</code> liefert den gesamten Request. Sobald er abgeschlossen ist, enthält <code>answers</code> die Werte der
  empfangenden Person, indiziert nach Feldschlüssel, <code>display</code> dieselben Schlüssel als lesbarer Text, und <code>outcome</code> —
  Zustimmung, Ablehnung oder Änderungswunsch — ist ihr Urteil, wenn das Formular eine <a href="/de/requests/overview">Entscheidungsfrage</a>{' '}
  hat. Ein <code>callbackFailedAt</code>-Zeitstempel bedeutet, dass die Zustellung keine Versuche mehr übrig hatte und nichts bei deinem
  Endpunkt ankam; behebe das Problem beim Empfänger und rufe dann <code>request_replayCallback</code> auf, das die ursprüngliche Event-ID
  erneut sendet, sodass dein Empfänger dedupliziert, statt erneut zu laufen. Nachdem die Aufbewahrungsrichtlinie des Formulars einen Request
  entfernt hat, ist <code>dataPurgedAt</code> gesetzt und die Antworten sind endgültig verloren.
</p>

<p>
  <code>request_list</code> fasst viele auf einmal zusammen, gefiltert nach <code>status</code>, <code>outcome</code>,{' '}
  <code>externalId</code> und <code>includeTest</code>. Blättere mit <code>nextCursor</code>: eine Seite kann selten mit leeren{' '}
  <code>items</code> und <code>hasMore: true</code> zurückkommen — das ist nicht das Ende der Liste, gib den Cursor zurück und mach weiter.
</p>

<h2 id="resources">Ressourcen und Prompts</h2>
<p>
  Jeder Skill und Tool-Katalog ist außerdem eine MCP-Ressource unter <code>skill://&lt;name&gt;</code> — <code>skill://requests</code>,{' '}
  <code>skill://editor-inserts</code>. Ein Client, der <code>resources/list</code> unterstützt, kann sie durchsuchen und lesen, ohne{' '}
  <code>load_skill</code> oder <code>load_tools</code> aufzurufen. Der Server liefert außerdem vier Prompts über <code>prompts/list</code>:{' '}
  <code>identity</code>, <code>capabilities</code>, <code>data_tools</code> und <code>editor_tools</code>.
</p>

<h2 id="confirmation">Tools, die erst nachfragen</h2>
<p>
  Jedes Tool trägt die MCP-Hinweise <code>readOnlyHint</code> und <code>destructiveHint</code>, abgeleitet von seinem Verb. Diese Tools sind
  als destruktiv markiert, weil sie sich nur mit einem weiteren Aufruf rückgängig machen lassen oder gar nicht: <code>form_delete</code>,{' '}
  <code>form_unpublish</code>, <code>workspaceFolder_delete</code>, <code>editor_deleteElement</code>,{' '}
  <code>translationLanguage_delete</code> und <code>request_cancel</code>. Die meisten Clients fragen die Person, bevor sie diese ausführen,
  aber die Nachfrage ist die Entscheidung des Clients — prüfe also dessen Genehmigungseinstellungen, wenn du einen harten Stopp brauchst.
</p>

<h2 id="limitations">Einschränkungen</h2>
<ul>
  <li>
    <strong>Keine Binär-Uploads über einen Tool-Aufruf.</strong> Bilder werden per URL gesetzt: Cover, Logos und Bild-Blöcke akzeptieren{' '}
    <code>http(s)://</code>- oder <code>data:image</code>-URIs. Ein Dokument pro Request ist die Ausnahme: <code>document_create</code>{' '}
    liefert eine Upload-URL zurück, an die ein Client mit HTTP-Zugriff die Datei per <code>PUT</code> senden kann (siehe{' '}
    <a href="#requests-documents">Dokumente pro Request</a>). Um ein PDF oder einen Screenshot in ein Formular zu verwandeln, nutze den{' '}
    <a href="/de/ai/ai-form-generation#files">integrierten KI-Chat</a>.
  </li>
  <li>
    <strong>Keine Workspace-KI-Skills.</strong> <a href="/de/ai/ai-skills">In Formstep geschriebene Skills</a> sind nur im integrierten
    KI-Chat verfügbar. Die eigenen Skills des Servers (<code>load_skill</code>) sind über MCP verfügbar.
  </li>
</ul>

<h2 id="api-token-clients">Mit einem API-Token verbinden</h2>
<p>
  Die meisten Clients melden sich per OAuth an: Füge die URL ohne Header hinzu und folge{' '}
  <a href="/de/guides/ai-agents/connect">Einen KI-Agenten verbinden</a>. Ein Client, der keinen Browser öffnen kann — etwa ein Skript, ein
  CI-Job oder ein headless Agent — sendet stattdessen ein <a href="/de/developers/api-tokens">API-Token</a> als Header.
</p>
<p>Claude Code, über die Kommandozeile:</p>

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

<p>
  Oder in der <code>.mcp.json</code> eines Projekts:
</p>

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

<p>
  Cursor, in <code>.cursor/mcp.json</code> oder <code>~/.cursor/mcp.json</code>:
</p>

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

<p>
  Um dich statt mit einem Token per OAuth anzumelden, nutze{' '}
  <a href="https://cursor.com/install-mcp?name=formstep&config=eyJ1cmwiOiJodHRwczovL2FwaS5mb3Jtc3RlcC5pby9hcGkvbWNwIn0=">
    Zu Cursor hinzufügen
  </a>
  . Es fügt die Server-URL ohne Header hinzu, und Cursor fordert dich auf, dich bei Formstep anzumelden.
</p>
<p>Andere Clients nehmen dieselbe URL und denselben Header; ihre eigene Dokumentation zeigt, wo.</p>

<h2 id="oauth">OAuth statt API-Tokens verwenden</h2>
<p>
  Eine Drittanbieter-App, die im Auftrag einer Person eine Verbindung herstellt, sollte OAuth verwenden, statt nach einem eingefügten Token
  zu fragen. Formstep ist ein OAuth-2.1-Autorisierungsserver mit verpflichtendem PKCE (S256) und undurchsichtigen Token — keine JWTs, kein
  Implicit Grant. Claude Desktop, Claude Code und der Claude.ai-Web-Connector entdecken das alles selbst über den MCP-Endpunkt, sodass es
  reicht, nur die URL ohne Header einzufügen: Der 401 verweist auf <code>/.well-known/oauth-protected-resource</code>, und der Client
  übernimmt von dort.
</p>
<p>Der Ablauf, für einen Client, den du selbst schreibst:</p>
<ol>
  <li>
    <code>GET /.well-known/oauth-protected-resource</code>, dann <code>GET /.well-known/oauth-authorization-server</code> für die
    Endpunkt-URLs, Scopes und unterstützten Auth-Methoden.
  </li>
  <li>
    <code>POST /oauth/register</code> mit deinen <code>redirect_uris</code> (dynamische Client-Registrierung, keine Zugangsdaten nötig). Du
    bekommst eine <code>client_id</code>, plus ein <code>client_secret</code>, falls du etwas anderes als{' '}
    <code>token_endpoint_auth_method: "none"</code> angefordert hast. Redirect-URIs müssen HTTPS sein, oder HTTP auf <code>localhost</code>.
    Die Registrierung ist auf 20 pro Stunde pro IP begrenzt.
  </li>
  <li>
    Schicke die Person zu <code>/oauth/authorize</code> mit <code>response_type=code</code>, deiner <code>client_id</code>, der
    registrierten <code>redirect_uri</code>, <code>scope=mcp:read mcp:write offline_access</code>, <code>state</code> und einem{' '}
    <code>code_challenge</code> mit <code>code_challenge_method=S256</code>. Sie meldet sich an, wählt einen Arbeitsbereich und autorisiert.
  </li>
  <li>
    Tausche den Code bei <code>POST /oauth/token</code> mit <code>grant_type=authorization_code</code> und deinem <code>code_verifier</code>{' '}
    ein, innerhalb von 60 Sekunden. Codes sind nur einmal verwendbar.
  </li>
  <li>
    Rufe den MCP-Endpunkt mit <code>Authorization: Bearer fbo_...</code> auf. Zugriffstoken sind 1 Stunde gültig; Refresh-Token sind 30 Tage
    gültig und rotieren bei jeder Verwendung. Einen bereits verwendeten Refresh-Token erneut zu nutzen, verbrennt die gesamte Kette —
    speichere also immer den neuesten.
  </li>
</ol>
<p>
  <code>POST /oauth/revoke</code> (RFC 7009) widerruft ein Zugriffs- oder Refresh-Token. Eine Person kann außerdem die ganze App unter{' '}
  <strong>Verbundene Apps</strong> auf der Seite OAuth und API-Schlüssel trennen, was jedes Token beendet, das sie für diesen Arbeitsbereich
  hält.
</p>

<p>Falls du bereits ein Token in der Hand hast, gehört es an denselben Platz wie ein API-Schlüssel:</p>

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

<h3 id="connected-apps">Verbundene Apps</h3>
<p>
  Jede OAuth-Verbindung wird unter <strong>Verbundene Apps</strong> auf der Seite <strong>OAuth und API-Schlüssel</strong> aufgelistet, mit
  Angabe, wann sie verbunden und zuletzt verwendet wurde. Verbindungen sind persönlich: Nur die Person, die eine Verbindung autorisiert hat,
  sieht sie, und Workspace-Admins können die Verbindung eines anderen Mitglieds weder einsehen noch widerrufen. Das Trennen wirkt sofort.
  Eine Person, die den Arbeitsbereich verlässt oder daraus entfernt wird, verliert alle ihre Verbindungen zu ihm.
</p>

<h2 id="next-steps">Nächste Schritte</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Einen KI-Agenten verbinden](/de/guides/ai-agents/connect) — Schritt-für-Schritt-Einrichtung in jedem MCP-Client
  - [Webhooks-Referenz](/de/developers/webhooks-reference) — Payload-Schema und Signierung
  - [REST-API](/de/developers/rest-api) — API-Methoden für programmatischen Zugriff
  - [Pläne & Preise](/de/subscription-billing/plans-pricing) — Plan-API-Zugang und Limits vergleichen
</div>
