# MCP tools for split tests

Create, inspect, update, start, complete, and delete split tests, including variant payloads and the publication boundary.

Available on the Grow, Scale, and Pro plans.

Split-test tools manage test records for existing questions, offers, and flows. They do not create duplicate base resources. Starting a test changes its stored status immediately; publication deploys the active test to the website. Ending a test records the winner and can replace original content in the draft.

## MCP split-test access and lifecycle

Every operation accepts an optional string `project_id` and follows the [shared MCP authorization and project-selection rules](https://rightmessage.com/help/mcp-authorization-reference). Invalid or inaccessible projects return errors.

`split_test_pid` is the public test ID returned as `pid`, not a resource ID or a made-up `st_` identifier. Missing input, invalid IDs, and cross-project or unknown tests return errors.

The state sequence is `draft` → `active` → `completed`. Only draft tests can be edited or started. Only active tests can be ended. Completed tests cannot be restarted. Deleting an active test is prohibited.

Examples are illustrative and were not executed. Requests are `tools/call` parameters; responses are decoded payloads. Synthetic resource IDs assume that the corresponding resources already exist in WorkForge.

## Variant payloads

`variants` is an array of variant objects. Control is implicit and should not be included. Common fields are:

| Field    | Type   | Meaning                                                                                       |
| -------- | ------ | --------------------------------------------------------------------------------------------- |
| `id`     | string | Nonempty variant identifier, distinct from `control`.                                         |
| `name`   | string | Nonempty display name.                                                                        |
| `weight` | number | Creation requires `0`–`100`; non-control weights must total at least `1` and less than `100`. |

The remaining weight is reserved for control. A supplied control object is removed during creation, and the remaining weights are redistributed. Don't copy the tool description's two-variant example with weights `50` and `50`. Creation rejects that sum.

### Question overrides

Question tests support one to three non-control variants, with unique IDs. Only one draft or active test may exist for a question.

- `text`: optional nullable string overriding question text. Blank wording inherits control.
- `description`: optional nullable string with the same inheritance behavior.
- `answers`: optional nullable object keyed by an existing answer's segment ID. A value can be a label string or an object with `label` and `imageUrl`. Omitted, null, or empty-string values inherit control; promotion applies only nonempty label/image strings. Unknown segment keys do not create options.

Choice questions can vary question and answer wording. Input questions use text and description overrides. These payloads do not create new answer segments or change the underlying input type. `buttonText` is not a promoted question override.

### Offer overrides

Offer variants can contain `parts`, an object with `title`, `description`, `buttonText`, and `imageUrl` string overrides. Omit a part to inherit it. Use intentional, nonempty values rather than null or empty patches.

A link-action offer can also contain `link`, a sparse object with only:

- `url`: optional nonempty destination string.
- `open`: optional boolean controlling whether it opens in a new tab.

Omitting `link` inherits the complete link. Within a supplied link, omitted fields inherit. Unknown link keys, null/empty link objects, malformed values, or link overrides on non-link offers are rejected. The create handler does not impose an offer-specific variant-count maximum.

### Flow overrides

Flow creation accepts up to four variants. Every variant must include a complete `funnelDefinition` object with `type: "root"`. It is a replacement tree, not a patch.

The tool advertises this as an open object rather than a separate recursively enumerated JSON schema. Preserve the complete valid flow node structure, including each node's type-specific fields and ordered `child` or branch arrays. The creation handler checks that the supplied root is a nonempty object/array with root type; that is not a guarantee that every nested node is runnable. Read the existing flow and use its validated node definitions rather than inferring a tree from these minimal root checks.

## `create-split-test`

Creates a test record in draft status.

| Argument        | Type             | Required | Meaning                                       |
| --------------- | ---------------- | -------- | --------------------------------------------- |
| `project_id`    | string           | No       | Public project ID.                            |
| `resource_type` | string           | Yes      | `question`, `offer`, or `flow`.               |
| `resource_id`   | string           | Yes      | Existing resource ID.                         |
| `variants`      | array of objects | Yes      | Nonempty array using the payload rules above. |

```json
{"name":"create-split-test","arguments":{"project_id":"123456789","resource_type":"question","resource_id":"raq_example","variants":[{"id":"variant_b","name":"Direct question","weight":50,"text":"What would help your team most?"}]}}
```

```json
{"pid":"987654321","resource_type":"question","resource_id":"raq_example","status":"draft","variants":[{"id":"variant_b","name":"Direct question","weight":50,"text":"What would help your team most?"}],"message":"Split test created in draft status. Use start-split-test to activate it."}
```

Creation immediately stores the test record but does not start or publish it. Missing fields, unsupported resource types, invalid weights, excessive question/flow variants, duplicate open question tests, and invalid variant definitions return errors. Shared mutation failures use `Validation error: ...` or `Operation failed: ...`.

## `list-split-tests`

Lists stored tests, optionally filtered.

**Arguments:** optional `project_id` string; optional `resource_type`, `resource_id`, and `status` strings. Supported resource types are `question`, `offer`, and `flow`; normal status values are `draft`, `active`, and `completed`. For clarity, pair a resource ID with its type. The handler applies supplied filters independently.

```json
{"name":"list-split-tests","arguments":{"project_id":"123456789","resource_type":"question","status":"draft"}}
```

```json
[{"pid":"987654321","resource_type":"question","resource_id":"raq_example","status":"draft","variants":[{"id":"variant_b","name":"Direct question","weight":50,"text":"What would help your team most?"}],"started_at":null,"ended_at":null,"winner_id":null,"resource_name":"Business needs"}]
```

Each item contains `pid`, `resource_type`, `resource_id`, `status`, `variants`, `started_at`, `ended_at`, and `winner_id`; resolved resources can add `resource_name`. There are no pagination arguments. No matches returns `[]`. This does not compute metrics or alter a test.

## `get-split-test`

Reads one test, its original content, and optionally its metrics.

**Arguments:** optional `project_id` string; required `split_test_pid` string; optional `include_metrics` boolean, default `true`.

```json
{"name":"get-split-test","arguments":{"project_id":"123456789","split_test_pid":"987654321","include_metrics":false}}
```

Illustrative response excerpt:

```json
{"pid":"987654321","resource_type":"question","resource_id":"raq_example","status":"draft","snapshot":null,"original_content":{"text":"What does your team need?","description":"","buttonText":"Continue","options":[]}}
```

The full response includes test metadata and variants described under listing, plus `snapshot`. `original_content` for a question contains `text`, `description`, `buttonText`, and `options`, whose items contain `segmentId`, `label`, and `imageUrl`. Offer original content contains `title`, `description`, `buttonText`, and `imageUrl`; link-action offers also include `url` and `openInNewTab`. A flow does not receive the same top-level original-content expansion.

Original content uses draft-first configuration for draft tests and published configuration for active/completed tests. A completion snapshot, when present, contains `created_at`, `original_content`, and `metrics`; question snapshots also preserve related segment information.

Metrics are attached only for active/completed tests when requested. A metrics exception can leave the test readable with `metrics: null` and `metrics_error`. This operation changes neither test state nor configuration.

## `get-split-test-metrics`

Retrieves performance for an active or completed test.

**Arguments:** optional `project_id` string and required `split_test_pid` string. There is no date-range or winner-selection argument.

```json
{"name":"get-split-test-metrics","arguments":{"project_id":"123456789","split_test_pid":"987654321"}}
```

Illustrative offer-test response:

```json
{"splitTestPid":"987654321","metrics":{"control":{"variantId":"control","variantName":"Control (original)","exposures":100,"conversions":12,"conversionRate":12},"variant_b":{"variantId":"variant_b","variantName":"Trial CTA","exposures":100,"conversions":15,"conversionRate":15}}}
```

The envelope contains `splitTestPid` and `metrics`, keyed by variant ID. Entries include `variantId`, resolved `variantName`, `exposures`, `conversions`, and percentage `conversionRate`.

Question entries also include `confidenceInputs: {visitors, answered}` and `answerDistribution: {coverage, respondents, answers}`. `answers` is keyed by segment ID with `{count, share}`. Coverage contains `status` (`available` or `unavailable`), nullable `reason`, and nullable `from` timestamp. `respondents` can be null when answer capture is unavailable. Do not substitute empty distributions for proven zero answers.

Flow responses add `primaryMetric`, either `offerConversionRate` when an arm includes offers or `completionRate` otherwise. Flow entries add `completions`, `completionRate`, `offerConversions`, `offerConversionRate`, `avgDurationMs`, `dropoffs`, and `dropoffRate`. Flow and question queries reject incomplete metric results. The older offer metric path can return zero control metrics after a backend exception, so zero values alone are not proof of a functioning data pipeline.

Draft tests return an error. The tool does not declare statistical significance, choose a winner, or change state. Inspect the sample size and coverage before making a decision.

## `update-split-test`

Replaces the stored variants of a draft test. It is not a per-variant merge.

**Arguments:** optional `project_id` string; required `split_test_pid` string; `variants` array of objects. Although the advertised schema does not mark `variants` required, the handler rejects a request without it because there are no other editable fields.

```json
{"name":"update-split-test","arguments":{"project_id":"123456789","split_test_pid":"987654321","variants":[{"id":"variant_b","name":"Short question","weight":50,"text":"What do you need most?"}]}}
```

```json
{"pid":"987654321","resource_type":"question","resource_id":"raq_example","status":"draft","variants":[{"id":"variant_b","name":"Short question","weight":50,"text":"What do you need most?"}]}
```

Include every variant you want to retain. This overwrites stored test data immediately, but does not publish it. Active/completed tests return a state error; absent update data returns `No update fields provided. Pass variants to update.` Model validation still applies to question and offer variants. The update handler does not repeat all of creation's weight and flow-root checks, so clients should retain the valid creation contract rather than relying on weaker update validation.

## `start-split-test`

Changes a draft test to active and records `started_at`.

**Arguments:** optional `project_id` string and required `split_test_pid` string.

```json
{"name":"start-split-test","arguments":{"project_id":"123456789","split_test_pid":"987654321"}}
```

```json
{"pid":"987654321","resource_type":"question","resource_id":"raq_example","status":"active","started_at":"2026-10-13T12:00:00Z","message":"Split test is now active. Visitors will be randomly assigned to variants."}
```

This is a consequential activation, not a private preview. Variants become locked, and a subsequent configuration publication can include the active test. The handler itself does not compile or publish the snippet. The response's active message describes stored test state, not proof that new code reached visitors.

An active question test also locks changes to the question's wording, answers, display, behavior, linked segment values, and non-sync Auto-Segment rules. This applies to other mutation tools and configuration restores too. Sync mappings, internal names, and flow membership can still change. End the test before attempting a locked edit.

Only draft tests can start. Confirm the intended resource and variants, then use the [publication tools](https://rightmessage.com/help/mcp-tools-publishing) when ready to deploy. Publication can include other pending configuration changes.

## `end-split-test`

Completes an active test and declares control or a stored variant the winner.

**Arguments:** optional `project_id` string; required `split_test_pid` string and `winner_id` string. `winner_id` must be `control` or an existing variant ID.

```json
{"name":"end-split-test","arguments":{"project_id":"123456789","split_test_pid":"987654321","winner_id":"control"}}
```

```json
{"pid":"987654321","resource_type":"question","resource_id":"raq_example","status":"completed","winner_id":"control","ended_at":"2026-10-13T13:00:00Z","message":"Test ended. Control (original) wins — no changes made."}
```

Ending a test saves a completion snapshot and attempts to capture metrics. A metrics error leaves `snapshot.metrics: null` but does not by itself prevent completion. Test completion and promotion of a non-control winner either succeed together or fail together. A non-control winner overwrites supported question copy and answer wording, offer content and links, or the flow tree in the draft. Promoting a question's answer label can also change the corresponding segment name. Choosing control leaves original content unchanged but still completes the test record.

A winning variant replaces the prior editable content. The tool saves a configuration snapshot before promotion and notifies editors. It does not publish. Publish to deploy the winner and remove the running experiment from the served configuration. A completed test record alone does not prove that a previously published test has stopped running in browsers.

Wrong state, unknown winner, invalid ID, or invalid promotion returns an error. Review metrics and obtain a deliberate winner decision before calling it; the completion snapshot is not a guaranteed substitute for that review.

## `delete-split-test`

Permanently deletes a draft or completed test record, including its stored completion snapshot.

**Arguments:** optional `project_id` string and required `split_test_pid` string.

```json
{"name":"delete-split-test","arguments":{"project_id":"123456789","split_test_pid":"987654321"}}
```

```json
{"deleted":true,"pid":"987654321"}
```

Active tests must be ended first. Deletion is immediate account-record removal, not a draft operation. Configuration undo cannot restore the test record, and deleting a completed test does not revert content already promoted or published. Missing/invalid IDs, unknown tests, and active-state deletion return errors.
