# MCP tools for offers

Exact MCP offer arguments, content and action settings, returned objects, performance metrics, and draft-only changes.

Available on the Grow, Scale, and Pro plans.

Offer tools manage calls to action that direct visitors to a URL. Offers and forms share `ofr_` IDs, but only items with `triggeredAction.type: "link"` are classified as offers. Other action types belong to [form tools](https://rightmessage.com/help/mcp-tools-forms). The performance tool on this page reports both.

## MCP offer 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 offer-specific feature check.

Configuration reads prefer the draft over published configuration. Create, update, duplicate, and delete save a draft and a snapshot of the previous configuration. They do not publish, open a URL, or trigger a visitor conversion. [Publishing](https://rightmessage.com/help/mcp-tools-publishing) is separate.

Results below are the JSON payload inside an MCP response. Shared errors include missing, invalid, or inaccessible project IDs and unavailable MCP access. Mutations use `Validation error: ...` for configuration-schema failures and `Operation failed: ...` for other exceptions. All examples are illustrative, not executed, and use fictional WorkForge resources.

## Content and action objects

Create and update accept these optional nested values:

| Argument                         | Type and behavior                                                                                                                                                                                           |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `parts.title`                    | String. Visible headline. New offers default to `Untitled Offer`.                                                                                                                                           |
| `parts.description`              | String. New offers default to null.                                                                                                                                                                         |
| `parts.button_text`              | String. New offers default to `Learn More`. Returned as `buttonText`.                                                                                                                                       |
| `parts.image_url`                | String. New offers default to null. Returned as `imageUrl`.                                                                                                                                                 |
| `action.type`                    | String. Defaults to `link` on creation. The tool description also admits `tag`, `event`, `list`, `automation`, and `contact`, but those create form-classified items. `redirect` is not an action type.     |
| `action.settings.url`            | String. Destination for a useful `link` action. Omission does not supply a destination.                                                                                                                     |
| `action.settings.platform`       | String. Provider name for a tag action. Stored as `partnerName`.                                                                                                                                            |
| `action.settings.tag_ids`        | Array of strings or numbers in the tool schema. Stored as `tagIds`. For a tag action, save-time repair converts integers to strings, splits comma/newline-separated IDs, and removes blanks and duplicates. |
| `action.settings.thanks_message` | String. Stored as `thanksForConvertingText`.                                                                                                                                                                |

The text parts support `{{ phrases.phr_workdemo }}` references. The internal offer name does not automatically become the headline. For non-link actions, the more complete input schema is on the form reference.

Non-link actions also receive a post-submit `redirectFunnel` during save-time repair when no nonempty tree exists: it contains a message with `thanks_message`, or `Thank you!` by default. Changing the message setting later does not overwrite an existing nonempty tree. Use the form tools for field management and inspect the saved definition after changing action type.

The stored offer object contains `id`, `name`, `isEnabled`, `parts`, and `triggeredAction: {type, settings}`. Input `is_enabled` becomes `isEnabled`. There is no `offer_id` property in a returned offer object; its ID is `id`.

## `create-offer`

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

Returns the complete new object with a generated `ofr_` ID. It does not create a widget or attach the offer to a flow. Invalid stored values can fail configuration validation. A supplied non-link action changes the item's classification; use form reads for that result.

Illustrative request:

```json
{"name":"create-offer","arguments":{"name":"WorkForge demo","parts":{"title":"See WorkForge in action","button_text":"Book a demo"},"action":{"type":"link","settings":{"url":"https://example.com/demo"}}}}
```

Illustrative result:

```json
{"id":"ofr_workdemo","name":"WorkForge demo","isEnabled":true,"parts":{"title":"See WorkForge in action","description":null,"buttonText":"Book a demo","imageUrl":null},"triggeredAction":{"type":"link","settings":{"url":"https://example.com/demo"}}}
```

## `list-offers`

Lists link-type items only. Optional string `project_id` is the only argument. Returns an array of compact summaries with `id`, `name`, `isEnabled`, `type: "cta"`, and `triggeredActionType`. An empty project returns `[]`. There is no pagination or search argument.

This read makes no configuration change. Shared authorization errors apply.

Illustrative request and result:

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

```json
[{"id":"ofr_workdemo","name":"WorkForge demo","isEnabled":true,"type":"cta","triggeredActionType":"link"}]
```

## `get-offer`

Returns one offer with display annotations. Arguments are optional string `project_id` and required string `offer_id`.

The result includes the stored offer fields, `triggeredActionSummary`, and `containingFlows`, an array of `{id, name}` objects. IDs can gain resolved-name properties such as `id_name`. Text parts are display-oriented: HTML is stripped and phrase references receive annotations. They are not a lossless replacement payload for rich content.

Missing `offer_id` returns `offer_id is required.` Missing items return `Offer not found: ID`. A non-link item returns an error naming its action type and directing the caller to `get-form`. No draft or provider mutation occurs.

Illustrative request and result:

```json
{"name":"get-offer","arguments":{"offer_id":"ofr_workdemo"}}
```

```json
{"id":"ofr_workdemo","id_name":"WorkForge demo","name":"WorkForge demo","isEnabled":true,"parts":{"title":"See WorkForge in action","description":null,"buttonText":"Book a demo","imageUrl":null},"triggeredAction":{"type":"link","settings":{"url":"https://example.com/demo"}},"triggeredActionSummary":"Link: https://example.com/demo","containingFlows":[]}
```

## `update-offer`

Changes an existing draft item. Required `offer_id` is a string. Optional arguments are `project_id`, string `name`, boolean `is_enabled`, and the shared `parts` and `action` objects. At least one non-null update value is required.

Returns the complete updated stored object, without the additional display summaries from `get-offer`. Omitted top-level fields, part properties, and action-setting keys remain unchanged. Supplied settings replace matching keys rather than recursively merging arrays. Changing the action type retains unrelated old settings and can turn the item into a form.

Missing ID returns `offer_id is required.` An empty update returns `No fields provided to update.` A missing item returns `Operation failed: Offer not found: ID`. The update handler does not reject a non-link item by type. The caller must select the intended resource.

Illustrative request:

```json
{"name":"update-offer","arguments":{"offer_id":"ofr_workdemo","action":{"settings":{"url":"https://example.com/book-demo"}}}}
```

Illustrative result:

```json
{"id":"ofr_workdemo","name":"WorkForge demo","isEnabled":true,"parts":{"title":"See WorkForge in action","description":null,"buttonText":"Book a demo","imageUrl":null},"triggeredAction":{"type":"link","settings":{"url":"https://example.com/book-demo"}}}
```

## `duplicate-offer`

Copies an offer into the draft with a new `ofr_` ID. Arguments are optional string `project_id`, required string `offer_id`, and optional string `new_name`. The default name is the original name plus ` (Copy)`.

Returns the complete copied offer, retaining its content, enabled state, and destination. It does not duplicate or change containing flows. Missing ID and missing source return errors. A form source returns `Item ID is a form, not an offer. Use duplicate-form instead.`

Illustrative request and result excerpt:

```json
{"name":"duplicate-offer","arguments":{"offer_id":"ofr_workdemo","new_name":"WorkForge demo alternative"}}
```

```json
{"id":"ofr_copydemo","name":"WorkForge demo alternative","isEnabled":true}
```

The full result also contains the copied `parts` and `triggeredAction`.

## `delete-offer`

Removes an item from the draft and clears `offerId` on flow nodes that reference it. Arguments are optional string `project_id` and required string `offer_id`.

Returns `{"deleted":true,"offer_id":"ID"}`. Missing ID returns `offer_id is required.` A missing item returns `Operation failed: Offer not found: ID`. This tool doesn't reject form IDs. Supplying a form ID can therefore delete that form and clear its flow references.

Illustrative request and result:

```json
{"name":"delete-offer","arguments":{"offer_id":"ofr_copydemo"}}
```

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

The deletion does not publish or remove previously recorded conversions.

## `get-offer-performance`

Reports recorded exposure and conversion activity for all forms and offers currently present in the draft-first configuration. It does not accept an individual offer filter.

| Argument     | Required | Type and boundary                                                                                    |
| ------------ | -------- | ---------------------------------------------------------------------------------------------------- |
| `project_id` | No       | String. Shared selection.                                                                            |
| `date_from`  | Yes      | ISO 8601 timestamp or `YYYY-MM-DD`. Inclusive start. A date means local midnight.                    |
| `date_to`    | Yes      | ISO 8601 timestamp or `YYYY-MM-DD`. A timestamp is exclusive. A date includes that entire local day. |
| `timezone`   | No       | String. Defaults to `UTC`. Controls date-only boundaries and daily buckets.                          |

Returns an array of per-resource rows. The overall row is excluded. A configured item without matching activity need not have a row. The fields are:

- `for`: an array identifying the offer or form bucket.
- `numerator`: unique converting visitors in the range.
- `denominator`: unique exposed visitors for that same item in the range.
- `data`: daily entries containing `x`, `numerator`, and `denominator`.
- `change`: relative conversion-rate change against the preceding period, or null when no comparison is available.
- `previousPeriod`: `numerator`, `denominator`, and `rate`.

The current rate is computed from the returned numerator and denominator; there is no top-level `rate` property. The service divides by `1` when the denominator is zero for its internal comparison calculation. `change` is null when the previous denominator is zero. If the previous rate is zero and the current rate is positive, `change` is `1`, the service's convention for a 100% increase. Daily unique counts must not be added to reconstruct range-wide unique counts.

Date-only ranges compare the same number of preceding local calendar days. Timestamp ranges use the preceding equal-duration period. This is observational performance, not a split-test verdict.

A project with no configured offers or forms returns `No offers or forms are configured for project ID.` Invalid date-only values or timezone values return `Date-only inputs must be valid calendar dates and timezone must be valid.` Reporting failures can return an MCP failure rather than empty activity. No configuration is changed.

Illustrative request and result for a resource with activity:

```json
{"name":"get-offer-performance","arguments":{"date_from":"2026-10-01","date_to":"2026-10-01","timezone":"UTC"}}
```

```json
[{"for":["ofr_workdemo"],"numerator":10,"denominator":100,"data":[{"x":"2026-10-01T00:00:00.000Z","numerator":10,"denominator":100}],"change":1,"previousPeriod":{"numerator":5,"denominator":100,"rate":0.05}}]
```
