MCP form write tools

Use these tools to create forms, change form drafts and settings, and publish forms. Some changes save directly; draft proposals must be applied separately.

create_form

Creates a draft from a complete form config. It never publishes. It also creates a default owner email notification. Call get_form_config_schema before generating a config.

Input

NameTypeRequiredDescription
namestringYesNon-empty form name.
preview_configobjectYesComplete form config. If mode is omitted, it is set to page. If end is missing, a default ending is added.
workspace_idstringNoWorkspace ID from get_workspace.

Returns

Returns id, name, status: "draft", preview_config, created_at, updated_at, team_id, workspace_id, and edit_url. A personal key gets workspace_id: null.

Example call

{ "name": "November registration", "preview_config": { "mode": "page", "pages": [ { "type": "form", "id": "p1", "ui": [ { "type": "short_text_input", "name": "full_name", "label": "What is your name?", "required": true }, { "type": "email_input", "name": "email", "label": "What is your email?", "required": true } ] } ] } }

create_form_from_prompt

Creates a draft from a natural-language prompt. It never publishes.

Input

NameTypeRequiredDescription
promptstringYesDescription of the form to create.
workspace_idstringNoWorkspace ID.

Returns

Returns form_id, name, form_config, saved: true, team_id, workspace_id, message, and edit_url.

Example call

{ "prompt": "Create a registration form that asks for a person's name and email address." }

update_form

Renames a form, replaces its draft config, or attaches or clears one managed image. A name change can affect the title of an already published page. This is an immediate direct write and never publishes. Replacing the config or changing an image requires expected_updated_at to match the value returned by get_form.

Input

NameTypeRequiredDescription
form_idstringYesForm to update.
namestringNoNew form name.
preview_configobjectNoReplacement config; requires expected_updated_at.
expected_updated_atISO timestamp or nullWhen replacing preview_configMust match get_form.updated_at. Replacing the config without it returns the error name precondition_required, and data.current_updated_at gives the current value. A value that no longer matches returns conflict.
image_assetobjectNo{target, asset_id | null}. Use a separate call with expected_updated_at; asset_id is never a URL. Supported targets are open_graph_image, favicon, logo, cover_image, and qr_logo.

MCP cannot upload image files. Upload an image through REST POST https://api.makeform.ai/forms/{id}/image-assets, then attach it with update_form. The upload uses API-key-only authentication and a multipart/form-data body with exactly two fields: file and target. The open_graph_image, favicon, and qr_logo targets require a paid form.

Returns

Returns id, name, status, and updated_at. With image_asset, the result also includes image_asset with target, asset_id, binding_url, changed, and publishes_form: false.

Example call

Use the exact updated_at value returned by get_form.

{ "form_id": "FORM_ID", "name": "November registration", "expected_updated_at": "2026-11-11T18:56:04.351198+00:00" }

update_form_settings

Deep-merges partial settings into a form. Settings changes apply to a published form immediately, without a publish step. CAPTCHA is a form setting, configured with captcha: {enabled, provider: "turnstile"}. It works on hosted forms in page, tally, and typeform modes. You cannot enable it for a form served on a custom domain, and a form with CAPTCHA enabled does not accept Headless submissions.

Input

NameTypeRequiredDescription
form_idstringYesForm to update.
settingsobjectYesPartial settings using writable keys.
expected_updated_atISO timestampNoRejects a stale write.

Writable settings keys:

GroupKeys
Language and completionform_language, redirect_on_completion, redirect_url (valid HTTP or HTTPS URL)
Timertimer { enabled, show_clock, duration_seconds, warning_time_seconds, warning_message, trigger_page_id, expire_end_page_id }
Closingclose_form, close_form_message { title, description }, close_on_scheduled_date, close_date, close_date_timezone, total_submissions_limit
Behaviourprogress_bar, partial_submissions, duplicate_submissions { enabled, field_name, field_type }, auto_jump_to_next_page, save_answers_for_later
Anti-botanti_bot_lv1, captcha { enabled, provider: "turnstile" }
Display and brandingreduce_motion, hide_loading_screen, loading_screen_text (at most 100 characters), hide_powered_by_badge, hide_end_screen_branding, hide_email_branding
Hidden fieldshidden_fields[], each { id?, name, required? }
Trackinggoogle_analytics_id (G- plus 10 characters), meta_pixel_id
Sharingshare { site_name, title, description }
Embeddingallowed_embed_origins (at most 10)
Email verificationemail_verification_sender { display_name }

Unknown settings keys are rejected. A key omitted from the request keeps its stored value. Plain values replace stored values; objects merge key by key. Arrays replace the whole stored array. duplicate_submissions replaces its whole object, and null replaces the stored value, including objects and arrays.

For enabled timers, provide duration_seconds and warning_time_seconds. A scheduled close needs a future close_date timestamp and a close_date_timezone. timer.expire_end_page_id must refer to an ending in the published config.

Hidden-field names must start with a lowercase letter and contain only lowercase letters, digits, and underscores. They must be unique and must not match a visible field name, ignoring case. Keep each returned hidden-field id when sending the array back; to rename a hidden field, keep its id and change its name.

Embedding origins must use HTTPS, except that HTTP is accepted for localhost. They cannot contain a path, query, or wildcard, and each can be at most 300 characters. Sending an empty array removes the origin restriction.

Plan eligibility follows the form owner’s plan, or the team owner’s plan for a team form. Pro or Business is required to turn on partial_submissions, hide_powered_by_badge, hide_end_screen_branding, hide_loading_screen, or hide_email_branding; change loading_screen_text; set a new google_analytics_id or meta_pixel_id; or set a new share.site_name, share.title, or share.description. email_verification_sender requires Business. Turning paid values off, clearing them, or resending unchanged values is allowed. Plan errors are named requires_pro or requires_business.

Headless forms accept only close_form, close_on_scheduled_date, close_date, close_date_timezone, total_submissions_limit, hidden_fields, and hide_email_branding.

Returns

Returns form_id, settings, and reconciliation with status: "complete" and settings_may_be_stale: false.

Example call

{ "form_id": "FORM_ID", "settings": { "captcha": { "enabled": true, "provider": "turnstile" } } }

To reopen a form after it has closed because a scheduled date passed or the submission limit was reached, move or disable the scheduled close, or increase or remove the reached limit, along with setting close_form to false.

duplicate_form

Copies a form into a new draft.

Input

NameTypeRequiredDescription
form_idstringYesForm to copy.
new_namestringNoName for the new draft.

Returns

Returns id, name, status: "draft", source_form_id, message, and edit_url.

Example call

{ "form_id": "FORM_ID", "new_name": "November registration copy" }

translate_form

Translates a draft into respondent languages. This tool requires Pro; otherwise it returns the requires_pro error. Translations are saved to the draft and are published with publish_form. Strings edited by the owner are kept, and the source text does not change.

Input

NameTypeRequiredDescription
form_idstringYesForm to translate.
localesstring[]YesUnique supported locales, at least one, excluding the source language.
refreshbooleanNoRedo earlier AI-translated strings.
expected_updated_atISO timestampNoRejects a stale write.

Returns

Returns form_id, source_locale, enabled_locales, locales with each locale’s translated, kept, and still_missing values, updated_at, published: false, and message.

propose_form_draft

Validates a complete form config and returns a proposal without saving it. Call get_form_config_schema before generating a config. Use the returned draft_token and exact proposed_config with apply_form_draft. The token is bound to the action, form, user, calling credential, and proposed config. A changed draft or proposal context is rejected with data.name: "conflict".

Input

NameTypeRequiredDescription
form_idstringYesForm to update.
proposed_configobjectYesComplete form config.

Returns

Returns id, name, changed, apply_required, summary, delta, risk, publish_readiness, and message. When an apply is available, it also returns draft_token, expires_at, and proposed_config.

Example call

{ "form_id": "FORM_ID", "proposed_config": { "mode": "page", "start": { "id": "start", "type": "start", "ui": [], "button_label": "Start" }, "pages": [ { "type": "form", "id": "p1", "ui": [ { "type": "short_text_input", "name": "full_name", "label": "What is your name?", "required": true }, { "type": "email_input", "name": "email", "label": "What is your email?", "required": true } ] } ], "end": [ { "id": "e0", "type": "end", "ui": [] } ] } }

apply_form_draft

Saves a proposed config to the draft. The draft must still be current, and proposed_config must match the proposal token exactly. This tool never publishes. The token is bound to the action, form, user, calling credential, and proposed config. A changed draft or proposal context is rejected with data.name: "conflict".

Input

NameTypeRequiredDescription
form_idstringYesForm to update.
draft_tokenstringYesToken returned by propose_form_draft.
proposed_configobjectYesExact config returned by the proposal.

Returns

Returns id, name, status, updated_at, field_count, delta, and message.

Example call

{ "form_id": "FORM_ID", "draft_token": "mf_mcp_act_DRAFT_TOKEN", "proposed_config": { "mode": "page", "start": { "id": "start", "type": "start", "ui": [], "button_label": "Start" }, "pages": [ { "type": "form", "id": "p1", "ui": [ { "type": "short_text_input", "name": "full_name", "label": "What is your name?", "required": true }, { "type": "email_input", "name": "email", "label": "What is your email?", "required": true } ] } ], "end": [ { "id": "e0", "type": "end", "ui": [] } ] } }

propose_form_edit

Sends the current draft and an instruction to an AI model, then returns an edited config without saving it. Use the returned edit_token and proposed_config with apply_form_edit. The token is bound to the action, form, user, calling credential, and proposed config. A changed draft or proposal context is rejected with data.name: "conflict".

Input

NameTypeRequiredDescription
form_idstringYesForm to update.
instructionstringYesRequested draft change.

Returns

Returns id, name, changed, apply_required, summary, delta, risk, publish_readiness, and message. When an apply is available, it also returns edit_token, expires_at, and proposed_config.

Example call

{ "form_id": "FORM_ID", "instruction": "Add a required phone number question after the email question." }

apply_form_edit

Saves a proposed AI edit to the draft. It rejects stale drafts and never publishes. The token is bound to the action, form, user, calling credential, and proposed config. A changed draft or proposal context is rejected with data.name: "conflict".

Input

NameTypeRequiredDescription
form_idstringYesForm to update.
edit_tokenstringYesToken returned by propose_form_edit.
proposed_configobjectYesConfig returned by the proposal.

Returns

Returns id, name, status, updated_at, preview_config_hash, delta, message, and, when present, warnings.

Example call

{ "form_id": "FORM_ID", "edit_token": "mf_mcp_act_EDIT_TOKEN", "proposed_config": { "mode": "page", "pages": [ { "type": "form", "id": "p1", "ui": [ { "type": "short_text_input", "name": "full_name", "label": "What is your name?", "required": true }, { "type": "email_input", "name": "email", "label": "What is your email?", "required": true }, { "type": "phone_input", "name": "phone", "label": "What is your phone number?", "required": true } ] } ], "end": [ { "id": "e0", "type": "end", "ui": [] } ] } }

publish_form

Previews publication first. To publish, call it again with confirm_publish: true and the publish_token from the preview. Confirmation also publishes draft translations. The token is bound to the action, form, user, calling credential, and proposed publication state. A changed publication context is rejected with data.name: "conflict".

Input

NameTypeRequiredDescription
form_idstringYesForm to publish.
confirm_publishbooleanNoSet to true to confirm publication. Omit for a preview.
publish_tokenstringFor confirmationToken returned by the preview.
labelstringNoLabel up to 120 characters.

Returns

The preview returns published: false, would_publish, confirmation_required, publish_token, expires_at, proposed_config_hash, and preview with the delta and risk. Confirmation returns published: true, id, name, published_at, version, url, field_count, and publish_config_hash.

Example call

Preview:

{ "form_id": "FORM_ID", "label": "November registration" }

Confirm with the preview’s token:

{ "form_id": "FORM_ID", "confirm_publish": true, "publish_token": "mf_mcp_act_PUBLISH_TOKEN", "label": "November registration" }

Action tokens expire after 10 minutes. If a token expires, run the preview or proposal again.

Tool errors

Tool errors return HTTP 200 with result.isError: true. The text in content[0].text is a JSON object wrapped in jsonrpc_error. Validation errors use code -32602, business-rule errors use -32600, and unexpected errors use -32603.

{ "jsonrpc_error": { "code": -32600, "message": "Replacing preview_config requires expected_updated_at. Call get_form and pass its updated_at so a newer edit is never overwritten.", "data": { "name": "precondition_required", "current_updated_at": "2026-11-11T18:56:04.351Z" } } }

Input schema failures start with MCP error -32602: Input validation error. Deleting forms and managing custom domains are not available through MCP.