Using the Deftform MCP server
The Deftform MCP server connects AI assistants such as Claude Code, Claude Desktop, Cursor or GitHub Copilot in VS Code to your Deftform workspace. It uses the Model Context Protocol. Once it's connected, your assistant can:
browse your forms and their fields
build new forms and add, edit, reorder or delete fields
change form settings, for example close a form or change the completion message
read responses, download response PDFs and analyze statistics
submit responses to your forms
Requirements
A Deftform account with access to the workspace settings
An API token for the workspace
An MCP client that supports remote servers over Streamable HTTP and lets you send an
Authorizationheader, for example Claude Code, Claude Desktop, Cursor or VS Code
Note: ChatGPT and the claude.ai website aren't supported yet. Their connectors require signing in via OAuth and can't use an API token. Claude Desktop works with the mcp-remote bridge described below.
1. Create an API token
The MCP server uses the same tokens as the Deftform API. Each token belongs to one workspace.
Go to Settings → API + MCP.
Click Create new token and enter a name, for example
Claude Code.Choose the access level:
Read-only access: your assistant can view forms, fields and responses. Use this for reporting and analysis.
Read & write access: your assistant can also create and change forms, fields and settings, and submit responses.
Copy the token right away. It's shown only once.
Every request needs this header:
Authorization: Bearer YOUR_API_TOKEN
Tip: Create a separate token for each tool or device. If you stop using one, or a token leaks, you can delete just that token on the API settings page. Start with a read-only token if you only want to ask questions about your data.
Tokens created before access levels were introduced have full read & write access.
2. Connect your MCP client
Server URL: https://deftform.com/mcp
Transport: Streamable HTTP
Replace YOUR_API_TOKEN with your token in the examples below.
Generic configuration
Most clients that use an mcpServers config file accept this format:
{ "mcpServers": { "deftform": { "type": "http", "url": "https://deftform.com/mcp", "headers": { "Authorization": "Bearer YOUR_API_TOKEN" } } }}
Claude Code
claude mcp add --transport http deftform https://deftform.com/mcp \ --header "Authorization: Bearer YOUR_API_TOKEN"
Claude Desktop
Claude Desktop's config file can't send custom headers to remote servers directly. Use the mcp-remote bridge instead. You need Node.js installed.
Open Settings → Developer → Edit Config and add:
{ "mcpServers": { "deftform": { "command": "npx", "args": [ "mcp-remote", "https://deftform.com/mcp", "--header", "Authorization:${DEFTFORM_AUTH}" ], "env": { "DEFTFORM_AUTH": "Bearer YOUR_API_TOKEN" } } }}
Restart Claude Desktop after you save the file.
VS Code (GitHub Copilot)
Create .vscode/mcp.json in your project, or run MCP: Open User Configuration to set it up for all projects:
{ "inputs": [ { "type": "promptString", "id": "deftform-token", "description": "Deftform API token", "password": true } ], "servers": { "deftform": { "type": "http", "url": "https://deftform.com/mcp", "headers": { "Authorization": "Bearer ${input:deftform-token}" } } }}
VS Code asks for the token once and stores it securely.
Cursor
Add this to ~/.cursor/mcp.json, or to .cursor/mcp.json in your project:
{ "mcpServers": { "deftform": { "url": "https://deftform.com/mcp", "headers": { "Authorization": "Bearer YOUR_API_TOKEN" } } }}
Check the connection
Ask your assistant:
"List my Deftform forms."
If you see your forms, the connection works.
3. How your forms are organized
Workspace└── Forms (identified by a short id, e.g. "aB3dE9") ├── Fields (identified by a uuid) └── Responses (identified by a uuid)
Tools refer to items by ID, not by name. A typical flow is:
list-formsto get theform_idget-formto get the fields and their uuidsadd-fields,update-field,list-responses,submit-response, and so on
You don't have to do this yourself. Your assistant looks up the IDs automatically.
4. Available tools
Tool | Type | Description |
|---|---|---|
| Read | Gets details about the workspace, its owner and members |
| Read | Lists all forms with their response counts |
| Read | Gets a form's settings and all of its fields |
| Read | Lists the responses of a form |
| Read | Gets a single response with all answers |
| Read | Gets totals, trends, conversion rate and answer distributions |
| Read | Creates a PDF of a response and returns a download link |
| Write | Creates a new form |
| Write | Updates the settings of a form |
| Write | Adds one or more fields to a form |
| Write | Updates the settings of a field |
| Write | Deletes a field |
| Write | Changes the order of all fields of a form |
| Write | Submits a new response to a form |
With a read-only token, write tools aren't offered to your assistant.
Read tools
get-workspace
Returns details about the workspace the token belongs to.
Parameters: none
Example response
{ "name": "Acme Inc.", "timezone": "Europe/Berlin", "locale": "en", "max_upload_size_mb": 50, "forms_count": 12, "owner": { "name": "Jane Doe", "email": "jane@acme.com" }, "members": [ { "name": "John Smith", "email": "john@acme.com" } ], "created_at": "2025-03-02T09:12:44+00:00"}
list-forms
Lists all forms in the workspace, newest first.
Parameter | Type | Required | Description |
|---|---|---|---|
search | string | no | Only return forms whose name contains this text |
Example response
{ "forms": [ { "id": "aB3dE9", "name": "Contact us", "slug": "contact-us-aB3dE9", "is_closed": false, "responses_count": 214, "incomplete_count": 18, "created_at": "2026-02-11T14:03:21+00:00" } ]}
get-form
Returns a form's settings and all of its fields, in the order they appear on the form.
Parameter | Type | Required | Description |
|---|---|---|---|
form_id | string | yes | ID of the form |
Example response (shortened)
{ "id": "aB3dE9", "name": "Contact us", "is_closed": false, "cta_label": "Send message", "after_message": "Thanks, we'll get back to you soon.", "fields": [ { "uuid": "6f1c0a8e-...", "label": "Email", "type": "input", "element_type": "email", "required": true, "options": null, "sorting": 1 }, { "uuid": "a2d94b17-...", "label": "Topic", "type": "select", "options": ["Sales", "Support", "Other"], "required": false, "sorting": 2 } ]}
list-responses
Lists the responses of a form, newest first.
Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
form_id | string | yes | ID of the form | |
incomplete | boolean | no | both |
|
from | string | no | Only responses created on or after this date ( | |
to | string | no | Only responses created on or before this date ( | |
page | integer | no | 1 | Page number |
limit | integer | no | 25 | Responses per page, max. 100 |
Dates use your workspace timezone.
Example response (shortened)
{ "data": [ { "uuid": "0d6e2f4a-...", "number": 214, "number_formatted": "214", "responses": [ { "key": "Email", "value": "alex@example.com" }, { "key": "Topic", "value": "Support" } ], "is_incomplete": false, "referrer": null, "created_at": "2026-11-12T16:40:09+00:00" } ], "meta": { "page": 1, "limit": 25, "total": 214, "has_more": true }}
get-response
Returns a single response with all answers, in the same format as one item of list-responses.
Parameter | Type | Required | Description |
|---|---|---|---|
uuid | string | yes | uuid of the response |
get-response-stats
Returns aggregated statistics for a form within a date range:
totals: responses, incomplete responses, visits and conversion rate
a timeline
per-field answer counts and option distributions
averages for rating and slider fields
the latest saved AI summary
Field statistics only count completed responses.
Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
form_id | string | yes | ID of the form | |
from | string | no | 30 days before | Start date ( |
to | string | no | today | End date ( |
group_by | string | no |
| Timeline grouping: |
The date range can be up to 366 days.
Example response (shortened)
{ "form": { "id": "aB3dE9", "name": "Contact us" }, "range": { "from": "2026-10-14", "to": "2026-11-13", "timezone": "Europe/Berlin", "group_by": "day" }, "totals": { "responses": 48, "incomplete": 6, "visits": 520, "conversion_rate": 9.23 }, "timeline": [ { "period": "2026-10-14", "responses": 2, "incomplete": 0, "visits": 17 } ], "fields": [ { "uuid": "a2d94b17-...", "label": "Topic", "type": "select", "answered": 45, "options": [ { "value": "Support", "count": 29 }, { "value": "Sales", "count": 12 } ], "other": [] } ], "ai_summary": null}
get-response-pdf
Creates a PDF of a single response and returns a download link.
Parameter | Type | Required | Description |
|---|---|---|---|
uuid | string | yes | uuid of the response |
Example response
{ "pdf_url": "https://..." }
Write tools
Write tools need a token with read & write access. Changes take effect immediately, also on forms that are already live.
Tip: When you build a new form, ask your assistant to create it closed (is_closed: true). Open it once you've reviewed it in the form builder.
create-form
Creates a new form. Add fields afterwards with add-fields.
Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Form name (max. 255 characters) |
description | string | no | Description shown below the form title |
cta_label | string | no | Label of the submit button |
after_message | string | no | Message shown after submission |
after_redirect_url | string | no | URL to redirect to after submission |
after_redirect_delay | integer | no | Seconds to wait before redirecting (0–60) |
is_closed | boolean | no | Create the form closed for responses |
closed_message | string | no | Message shown while the form is closed |
show_formtitle | boolean | no | Show the form title (default: |
include_response_in_email | boolean | no | Include the response in the notification email |
capture_location | boolean | no | Capture the respondent's location |
captcha | string | no |
|
seo_title | string | no | SEO title |
seo_description | string | no | SEO description |
seo_allow_bots | boolean | no | Allow search engines to index the form (default: |
Example response
{ "id": "Xy7pQ2", "uuid": "3c5e9a1d-...", "name": "Event registration", "slug": "event-registration-Xy7pQ2", "created_at": "2026-11-13T10:15:00+00:00"}
update-form-settings
Updates the settings of a form. Only the settings you send are changed. Besides form_id, you can send any of these:
General:
name,description,is_closed,closed_message,responses_limitButtons and labels:
cta_label,cta_label_continue,submit_aria_label,progress_label,show_formtitle,show_multipage_progressAfter submission:
completion_heading,after_message,after_redirect_url,after_redirect_delayEmails:
after_message_email_subject,after_message_email,send_pdf_to_respondent,admin_email_subject,admin_email_note,admin_email_attach_pdf,include_response_in_emailResponse numbers:
number_prefix,number_suffixProtection and data:
captcha(altcha,turnstile,recaptchaornone),capture_location,ai_summary_enabledIntegrations:
slack_enabled,discord_enabled,hubspot_enabled,google_sheets_enabledSEO:
seo_title,seo_description,seo_allow_bots
Example response
{ "id": "aB3dE9", "name": "Contact us", "updated": ["is_closed", "closed_message"], "updated_at": "2026-11-13T10:20:31+00:00"}
add-fields
Adds up to 50 fields to a form in one call. Fields are added at the end, or at a specific position.
Parameter | Type | Required | Description |
|---|---|---|---|
form_id | string | yes | ID of the form |
fields | array | yes | The fields to add, in order (1–50) |
position | integer | no | 0-based position to insert the fields at. If you leave it out, fields are added at the end. |
Each field needs a type. These types are available:
Type | Element |
|---|---|
| Short answer. Set |
| Long answer |
| Dropdown |
| Single choice |
| Multiple choice |
| Ranking |
| Country dropdown |
| Rating (1–5 or 1–10) |
| Slider |
| Time picker |
| Address |
| eSignature |
| File upload |
| Opt-in checkbox, for example for consent |
| Hidden field |
| Paragraph / text |
| Divider |
| Spacer |
| Page break for multi-page forms |
Settings available for all types:
Setting | Type | Description |
|---|---|---|
label | string | Plain-text label. Required, except for |
required | boolean | Whether an answer is required |
placeholder | string | Placeholder text |
text_help | string | Help text below the field |
subtext | string | Text below the label. Basic formatting allowed (see below). |
hide_label | boolean | Visually hide the label |
display_inline | boolean | Display choices inline |
readonly | boolean | Make the field read-only |
include_in_pdf_export | boolean | Include paragraph content in PDF exports |
aria_label | string | Accessible label for screen readers |
aria_description | string | Accessible description for screen readers |
custom_key | string | Key used instead of the field uuid in submissions and webhooks. Letters, numbers, dashes and underscores; must be unique within the form. |
Settings for specific types:
Type | Settings |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Settings that don't apply to a field's type are ignored.
Example request
{ "form_id": "Xy7pQ2", "fields": [ { "type": "paragraph", "label": "Intro", "text": "<p>Register for our <strong>Product Day</strong>.</p>" }, { "type": "input", "label": "Full name", "required": true }, { "type": "input", "element_type": "email", "label": "Email", "required": true }, { "type": "radio", "label": "Which session?", "options": ["Morning", "Afternoon"], "required": true }, { "type": "optincheckbox", "label": "Privacy", "text": "I agree to the <a href=\"https://acme.com/privacy\">privacy policy</a>.", "required": true } ]}
The response contains the new fields, in the same format as in get-form.
update-field
Updates the settings of a field. Only the settings you send are changed. You can send the same settings as in add-fields, except type. A field's type can't be changed.
Parameter | Type | Required | Description |
|---|---|---|---|
form_id | string | yes | ID of the form |
field_uuid | string | yes | uuid of the field |
settings | no | Any setting from |
Returns the updated field.
Note: If other fields have conditional logic based on this field, changing its options doesn't update those conditions. Check them in the form builder afterwards.
delete-field
Deletes a field from a form, including any conditional logic that refers to it. Existing responses keep their answers, but the field is no longer shown on the form or included in exports.
Parameter | Type | Required | Description |
|---|---|---|---|
form_id | string | yes | ID of the form |
field_uuid | string | yes | uuid of the field |
Example response
{ "uuid": "a2d94b17-...", "deleted": true }
Deleting is marked as a destructive action, so most MCP clients ask you to confirm it first.
reorder-fields
Sets the order of all fields of a form.
Parameter | Type | Required | Description |
|---|---|---|---|
form_id | string | yes | ID of the form |
field_uuids | array | yes | Every field uuid of the form, exactly once, in the new order |
To insert new fields at a specific place, use the position parameter of add-fields instead.
submit-response
Submits a new response to a form, for example to import data or test a form.
Parameter | Type | Required | Description |
|---|---|---|---|
form_id | string | yes | ID of the form |
data | object | yes | Answers keyed by field uuid (or custom key) |
referrer | string | no | Source of the submission, e.g. |
Checkbox answers are arrays, address answers are objects. Required fields must be filled in.
Example response
{ "uuid": "9b2a7c3e-...", "number": 215, "created_at": "2026-11-13T10:31:12+00:00"}
5. Example prompts
You don't need to call tools yourself. Describe what you want, and your assistant picks the right tools.
Build forms
"Create a closed form called 'Event registration' with fields for full name, email, company, a single-choice question for the session (Morning or Afternoon) and a required privacy consent checkbox."
"Turn this list of questions into a feedback survey with a 1–10 rating at the end: …"
"Add a page break after the contact details in my 'Job application' form, and move the file upload for the CV to the second page."
Edit forms
"Make the phone field in 'Contact us' optional and limit it to Germany, Austria and Switzerland."
"Add 'Partnership' as an option to the topic dropdown in 'Contact us'."
"Remove the 'Fax' field from all my forms."
"Close the 'Summer raffle' form and show 'The raffle has ended. Thanks for taking part!' instead."
Analyze responses
"How many responses did 'Contact us' get last month, and what's the conversion rate?"
"Which topics are selected most often in 'Contact us' this year? Group the trend by month."
"Summarize the comments from the last 50 responses of our customer feedback survey."
"Download the PDF of the latest response to 'Job application'."
6. Limits and good to know
Rate limit: 60 requests per minute per workspace. If you exceed it, the server responds with
429 Too Many Requests. Wait a moment and try again.Access: A token only gives access to the workspace it was created in.
Field limits: Up to 50 fields per
add-fieldscall, 500 fields per form and 250 options per field.Formatting: Labels, options, placeholders and help texts are plain text. Any HTML is removed.
Paragraphs allow
h1–h3,p,br,b,strong,em,i,u,ul,ol,lianda.Opt-in texts allow
a,strong,b,iandem.Subtexts and file upload texts allow
p,br,b,strong,em,i,uanda.Links must start with
https://,http://ormailto:.Ask your assistant to write HTML, not Markdown.
Builder only: HTML/code, image and embed elements, conditional logic and field validation rules can only be added in the form builder.
Live changes: Changes to a form that's already published are visible to respondents immediately. Adding a required field affects everyone who fills out the form from then on.
Deleting forms: You can't delete forms through MCP. Do this in Deftform.
7. Troubleshooting
Problem | Cause and fix |
|---|---|
| The token is missing, wrong or deleted. Check the |
| You've hit the rate limit. Wait a minute and try again. |
ChatGPT or claude.ai can't connect, or ask you to sign in | These connectors require OAuth, which isn't supported yet. Use Claude Code, Claude Desktop, Cursor or VS Code with an API token instead. |
"Tool [create-form] not found." or write tools are missing | Your token has read-only access. Create a token with read & write access. If you've just changed the token, reconnect the server or restart your client, because some clients cache the tool list. |
"Form not found." / "Field not found." / "Response not found." | The ID is wrong, the item was deleted, or it belongs to another workspace. Ask your assistant to list the items again. |
"Fields of type html can only be edited in the form builder." | HTML/code, image and embed elements can't be changed through MCP. Edit them in the form builder. |
"The fields.0.options field is required." | Dropdown, single choice, multiple choice and ranking fields need at least one option. |
"The fields.0.custom_key has already been taken." | Another field in the form already uses this custom key. Choose a different one. |
"A form can have at most 500 fields." | The form has reached the maximum number of fields. |
"Provide the uuids of all fields of the form exactly once." |
|
"This form is closed and not accepting responses." | Reopen the form with |
"The date range must not exceed 366 days." | Use a shorter range in |
Still stuck? Contact us with your MCP client, the prompt you used and the error message.