# MCP tools for forms and form fields

MCP form and field arguments, nested settings, response objects, index behavior, errors, and draft-only side effects.

Available on the Grow, Scale, and Pro plans.

Use these MCP tools to create forms and manage their ordered fields. Forms and offers share `ofr_` IDs. A stored item whose `triggeredAction.type` is `link` is an offer. Every other action type is a form. [Offer tools](https://rightmessage.com/help/mcp-tools-offers) cover link calls to action and performance reports for both forms and offers.

## MCP form access and draft changes

Every tool accepts an optional string `project_id` and follows the [shared MCP authorization and project-selection rules](https://rightmessage.com/help/mcp-authorization-reference). There is no additional form-specific plan requirement for these calls. Provider actions still require a connected, entitled provider when visitors use the published form.

Reads use the draft if present, otherwise the published configuration. Mutations save a draft and a snapshot of the preceding configuration. No tool here publishes, creates a subscriber, applies a provider tag, or submits a form. Visitor-side actions happen after publication and visitor interaction. See [publication tools](https://rightmessage.com/help/mcp-tools-publishing) to make the draft live.

Success examples show the JSON payload inside an MCP result. Errors are MCP error results, not REST status contracts. Shared errors include missing or invalid project selection, inaccessible projects, and unavailable MCP access. Mutations return `Validation error: ...` for configuration-schema failures and `Operation failed: ...` for other exceptions.

Examples use fictional WorkForge resources. They are illustrative and were not executed. Result excerpts are labeled where fields are omitted.

## Form data and nested settings

Create and update inputs use `is_enabled`, `parts.button_text`, and `parts.image_url`. Results use `isEnabled`, `parts.buttonText`, and `parts.imageUrl`.

`parts` supports optional string properties `title`, `description`, `button_text`, and `image_url`. Text supports Liquid phrase references such as `{{ phrases.phr_workdemo }}`. New forms default to title `Untitled Form`, button text `Submit`, and null description and image. The internal `name` does not automatically become the visible title.

`triggered_action` supports optional `type` and `settings`. Its type is one of `tag`, `list`, `automation`, `event`, `form`, or `contact`. Creation defaults to `form`. The registered schema also accepts deprecated `action`, with `type` and `settings`; `triggered_action` takes precedence when both are supplied.

| `settings` property | Input type                                     | Returned storage key and behavior                                                                                                                                                 |
| ------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `platform`          | String                                         | `partnerName`, the connected provider name.                                                                                                                                       |
| `tag_ids`           | Array of strings or numbers in the tool schema | `tagIds`. For a `tag` action, repair converts integer IDs to strings, splits comma/newline-separated entries, trims them, and removes blanks/duplicates. Use string provider IDs. |
| `list_id`           | String                                         | `listId`. See the list-action limitation below.                                                                                                                                   |
| `automation_id`     | String                                         | `automationId`. See the automation-action limitation below.                                                                                                                       |
| `event_name`        | Nonempty string                                | Trimmed and stored as the one-element `eventIds` array. Used for `event`.                                                                                                         |
| `form_ids`          | Array of strings or numbers in the tool schema | `formIds`. For a `form` action, repair normalizes IDs like `tagIds` above; omission becomes `[]`.                                                                                 |
| `show_labels`       | Boolean                                        | `formSettings.showLabels`. New `form` actions default to `true`.                                                                                                                  |
| `fields`            | Array of field objects                         | Replaces the input field list. Save-time repair adds an email field and submit field whenever either is missing, including for `[]`.                                              |
| `thanks_message`    | String                                         | Stored as `thanksForConvertingText`. When no nonempty redirect tree exists, repair creates a message node using this text, or `Thank you!` when blank/omitted.                    |
| `redirect_url`      | String                                         | Builds `redirectFunnel`, a root with one redirect node, generated UUIDs, the supplied `url`, and `open: false`.                                                                   |

The current MCP mapping stores singular `listId` and `automationId`, while the runtime configuration uses `listIds` and `automationIds`. A successful save with those arguments alone is not proof that a list subscription or automation will run. This reference does not offer a working list or automation example for that mismatch.

A form result contains `id`, `name`, `isEnabled`, `parts`, and `triggeredAction: {type, settings}`. It can also contain existing configuration properties retained by an update. Stored fields use these names:

| Field property         | Type and meaning                                                                                                                         |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `displayType`          | String. `text`, `email`, `textarea`, `select`, `hidden`, `submit`, or `sg`.                                                              |
| `label`, `placeholder` | Optional strings.                                                                                                                        |
| `width`                | Number. Column span in the visitor form's six-column grid. `3` is half width and `6` is full width. Save-time repair caps widths at `6`. |
| `espField`             | Optional string containing the provider field mapping.                                                                                   |
| `required`             | Optional boolean. Linked question fields explicitly default to `false`; ordinary fields can omit this stored key.                        |
| `options`              | For select input, an array of objects with string `label` and `value`. Seeded email and submit fields instead contain an empty object.   |
| `value`                | Optional string, principally for hidden fields.                                                                                          |
| `segmentGroupId`       | For `sg`, an existing question `raq_` ID or its `dim_` ID.                                                                               |
| `questionDisplay`      | Optional `dropdown` or `radio`, with dropdown as the linked-choice display default.                                                      |

The field subresource tools translate `display_type`, `esp_field`, `question_id`, and `question_display` to those stored names. Within a whole form's `settings.fields`, only `display_type`, `question_id`, and `question_display` receive that translation. Use `espField`, not `esp_field`, in that nested array.

The field tools describe a 12-column grid and `create-form-field` initially defaults to `12`, but save-time repair caps the stored width at `6`. Pass `width: 6` explicitly for a full-width field. Repair also fills missing `options` with `{}`, `placeholder` with `""`, and missing labels from the placeholder. The field mutation response can describe the pre-repair field and counts; use `get-form` to inspect the saved result.

## `create-form`

Creates one draft form. Required argument `name` is a string. Optional arguments are `project_id`, boolean `is_enabled` defaulting to `true`, and the `parts`, `triggered_action`, and deprecated `action` objects above.

Returns the complete new form. It does not attach the form to a flow or widget. If fields are omitted for a non-link action, the service seeds an email field and a submit field, each width `6`. Repair also restores either missing field in a supplied list. Blank `event_name` fails with `Operation failed: event_name must be a non-empty string.` Invalid action or configuration values can return a validation error.

Illustrative request:

```json
{"name":"create-form","arguments":{"name":"WorkForge guide","parts":{"title":"Get the WorkForge guide","button_text":"Send the guide"}}}
```

Illustrative result:

```json
{"id":"ofr_workdemo","name":"WorkForge guide","isEnabled":true,"parts":{"title":"Get the WorkForge guide","description":null,"buttonText":"Send the guide","imageUrl":null},"triggeredAction":{"type":"form","settings":{"formSettings":{"showLabels":true},"formIds":[],"fields":[{"label":"Email address","placeholder":"Your email address","displayType":"email","width":6,"options":{}},{"displayType":"submit","width":6,"options":{},"placeholder":"","label":""}],"redirectFunnel":{"type":"root","child":[{"type":"message","content":"Thank you!"}]}}}}
```

This is a stored form definition, not evidence of a configured provider destination.

## `list-forms`

Lists the complete stored form objects, not compact summaries. The only argument is optional string `project_id`. It returns an array, or `[]` if no non-link items exist. No pagination or filtering arguments are registered.

This read has no draft or provider side effect. Shared access errors apply.

Illustrative request and result for an empty project:

```json
{"name":"list-forms","arguments":{}}
```

```json
[]
```

## `get-form`

Returns one complete form, with resource-name annotations where a stored reference resolves. Arguments are optional string `project_id` and required string `form_id`.

Missing `form_id` returns `form_id is required.` A missing resource returns `Form not found: ID`. A link item returns an error directing the caller to `get-offer`. The read changes nothing.

Illustrative request and result excerpt:

```json
{"name":"get-form","arguments":{"form_id":"ofr_workdemo"}}
```

```json
{"id":"ofr_workdemo","id_name":"WorkForge guide","name":"WorkForge guide","isEnabled":true}
```

The full result also contains `parts` and `triggeredAction`, including all current fields.

## `update-form`

Changes the existing draft item. Required string `form_id` selects the item. Optional arguments are `project_id`, `name`, `is_enabled`, `parts`, `triggered_action`, and deprecated `action`. At least one non-null update value is required. Top-level nulls do not clear values.

The tool returns the complete updated item. Omitted part properties and omitted action-setting keys stay unchanged. Supplied settings replace their matching keys, so `fields` replaces the entire field array and `show_labels` replaces the stored `formSettings` object. Changing action type does not clear the old settings automatically.

Changing `thanks_message` alone does not replace an existing nonempty `redirectFunnel`; repair only uses that message to populate an empty tree. Use `redirect_url` when the intended post-submit action is a redirect. After changing or removing fields, read the saved form because repair restores a missing email or submit field.

Missing ID returns `form_id is required.` No changes returns `No fields provided to update.` An unknown item returns `Operation failed: Form not found: ID`. This update handler does not enforce the non-link type check used by `get-form`; use the correct ID rather than relying on the tool name to protect an offer.

Illustrative request and result excerpt:

```json
{"name":"update-form","arguments":{"form_id":"ofr_workdemo","parts":{"button_text":"Email my guide"}}}
```

```json
{"id":"ofr_workdemo","name":"WorkForge guide","isEnabled":true,"parts":{"title":"Get the WorkForge guide","description":null,"buttonText":"Email my guide","imageUrl":null}}
```

The full result retains `triggeredAction`. No visitor sees this edit until publication.

## `duplicate-form`

Copies a form into the draft with a new `ofr_` ID. Arguments are optional string `project_id`, required string `form_id`, and optional string `new_name`. The name defaults to the original name plus ` (Copy)`.

Returns the complete copied form. Existing field definitions and provider destinations are copied. If an older form has no `fields` property, the copy receives the default email and submit fields. This tool neither duplicates a flow nor connects the copy to one.

Errors cover missing `form_id`, missing source, and a source with link action type. The latter directs the caller to `duplicate-offer`.

Illustrative request and result excerpt:

```json
{"name":"duplicate-form","arguments":{"form_id":"ofr_workdemo","new_name":"WorkForge guide follow-up"}}
```

```json
{"id":"ofr_copydemo","name":"WorkForge guide follow-up","isEnabled":true}
```

The full result includes the copied `parts` and `triggeredAction`.

## `delete-form`

Deletes an item from the draft and clears `offerId` on referencing flow nodes. Arguments are optional string `project_id` and required string `form_id`. Returns `deleted: true` and the requested `form_id`.

A missing ID returns `form_id is required.` An unknown item returns `Operation failed: Form not found: ID`. This tool doesn't reject offer IDs. Supplying an offer ID can therefore delete that offer and clear its flow references.

Illustrative request and result:

```json
{"name":"delete-form","arguments":{"form_id":"ofr_copydemo"}}
```

```json
{"deleted":true,"form_id":"ofr_copydemo"}
```

This does not delete provider contacts or publish the removal.

## `create-form-field`

Inserts a field before the first submit field. If there is no submit field, it appends the new field.

| Argument                            | Required | Type and default                                                                       |
| ----------------------------------- | -------- | -------------------------------------------------------------------------------------- |
| `project_id`                        | No       | String. Shared project selection.                                                      |
| `form_id`                           | Yes      | String. Must identify a non-link item.                                                 |
| `label`                             | Yes      | String.                                                                                |
| `display_type`                      | Yes      | String. One of the field types described above.                                        |
| `question_id`                       | For `sg` | String. Existing question or dimension ID.                                             |
| `question_display`                  | No       | `dropdown` or `radio`. Applies only to `sg`.                                           |
| `width`                             | No       | Number. Defaults to `12`.                                                              |
| `placeholder`, `esp_field`, `value` | No       | Strings. No stored default.                                                            |
| `required`                          | No       | Boolean. Defaults to false behavior; explicitly stored as false for `sg` when omitted. |
| `options`                           | No       | Array of `{label, value}` objects for select fields.                                   |

Returns `form_id`, zero-based `field_index`, the new `field` in stored naming, and `total_fields`. Missing form, link item, and invalid linked-question configuration return errors. `question_id` and `question_display` on a non-`sg` field are rejected. An unknown linked question returns `Question not found: ID`.

Illustrative request and result for a form with the two default fields:

```json
{"name":"create-form-field","arguments":{"form_id":"ofr_workdemo","label":"Company","display_type":"text","width":6,"esp_field":"company","required":false}}
```

```json
{"form_id":"ofr_workdemo","field_index":1,"field":{"label":"Company","displayType":"text","width":6,"espField":"company","required":false},"total_fields":3}
```

## `update-form-field`

Merges provided properties into one field. Required arguments are string `form_id` and numeric `field_index`. Optional `project_id` and all field properties from `create-form-field` are accepted. None of those optional properties receives a creation default during a normal update.

The index is zero-based and converted to an integer. The tool returns `form_id`, `field_index`, and the complete updated `field`. Updating `options` replaces that value. Omitted and null field properties stay unchanged. Changing `display_type` away from `sg` removes old question references unless they were explicitly supplied, in which case validation rejects the incompatible combination.

Errors include missing ID or index, a missing form, a link item, an out-of-range index, invalid question settings, and `No field properties provided to update.` Index errors distinguish an empty form from a form with a valid index range.

Illustrative request and result:

```json
{"name":"update-form-field","arguments":{"form_id":"ofr_workdemo","field_index":1,"label":"Work email domain"}}
```

```json
{"form_id":"ofr_workdemo","field_index":1,"field":{"label":"Work email domain","displayType":"text","width":6,"espField":"company","required":false,"options":{},"placeholder":""}}
```

## `delete-form-field`

Removes a field and shifts later indexes down. Required arguments are string `form_id` and numeric `field_index`; `project_id` is optional. There is no stable field ID argument. The index is converted to an integer.

Returns `form_id`, the removed object as `deleted_field`, and the pre-repair `remaining_fields` count. Errors match the form lookup, type, and index errors described above. Save-time repair adds an email or submit field back if the deletion removed the last field of that type; the response count can therefore differ from `get-form`.

Illustrative request and result:

```json
{"name":"delete-form-field","arguments":{"form_id":"ofr_workdemo","field_index":1}}
```

```json
{"form_id":"ofr_workdemo","deleted_field":{"label":"Work email domain","displayType":"text","width":6,"espField":"company","required":false,"options":{},"placeholder":""},"remaining_fields":2}
```

All field mutations remain draft-only. Later calls must use the new indexes after an insertion or deletion.
