---
title: "MCP endpoint"
description: "Connect an MCP client, discover Tale tools, develop automations, and handle access checks and execution results."
---
Connect an MCP client when an agent needs to discover Tale's tools, retrieve knowledge, or build and run automations. The connection uses the same API key and organization scope as [REST](https://docs.tale.dev/develop/api-reference). Tale is the server in this connection: your external client calls Tale.

Start with `initialize`, inspect `tools/list`, then call `get_docs` before writing an automation. The deployment supplies its own supported grammar, so the client does not need to invent node types or configuration fields.

## Connect a client

### Prepare the connection

Create an [API key](https://docs.tale.dev/platform/admin/api-keys) and keep it in your client's secret configuration. The app's **Settings > API > MCP** page shows the endpoint, organization slug, and a copyable discovery request.

| Setting | Value |
| --- | --- |
| Endpoint | `https://your-host.example.com/api/v1/mcp` |
| Transport | HTTPS POST with JSON-RPC; plain JSON responses |
| Authorization | `Authorization: Bearer <api-key>` |
| Organization | `X-Organization-Slug: <slug>` |
| Protocol revisions | `2026-07-28`, carried by every request; or `2025-11-25`, `2025-06-18` or `2025-03-26`, opened with `initialize` ([Protocol revisions](https://docs.tale.dev/develop/mcp-endpoint#protocol-revisions)) |

Use a client that supports a remote HTTP endpoint with custom headers. There is no SSE event stream, session deletion, or OAuth authorization flow. OAuth discovery URLs return JSON `404`; a client requiring that flow needs a different authentication configuration. A client that only launches local stdio servers cannot use this URL directly. [Use Tale from your editor or a script](https://docs.tale.dev/develop/use-tale-from-your-editor) has ready configurations for opencode and Claude Code.

Always send the organization header in reusable integrations. It is optional only when the key holder has one organization. With several memberships, omitting it returns `400 ORG_SLUG_REQUIRED`; an unknown slug returns `404 ORG_SLUG_INVALID`, and a non-member organization returns `403 ORG_FORBIDDEN`. Each of these refusals lists the slugs you can send in `data.organizations`.

### Initialize and retrieve the authoring reference

The examples assume `TALE_URL`, `TALE_API_KEY`, and `TALE_ORG_SLUG` are already set in your environment. `TALE_URL` is the application origin, without `/api/v1`.

```bash
curl --fail-with-body "$TALE_URL/api/v1/mcp" \
  --header "Authorization: Bearer $TALE_API_KEY" \
  --header "X-Organization-Slug: $TALE_ORG_SLUG" \
  --header 'Content-Type: application/json' \
  --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"docs-client","version":"1.0.0"}}}'
```

The response identifies the server as `tale-platform` and reports the API contract version as `serverInfo.version`. Its `instructions` are a short guide to working with Tale that your client can hand to its model. Read `result.protocolVersion` and send that negotiated value as `MCP-Protocol-Version` on later calls. The next example uses `2025-11-25`; replace it if your initialization negotiated an older revision. A revision the endpoint does not speak returns `400` with JSON-RPC `-32022`, and `data.supported` lists the ones it does.

```bash
curl --fail-with-body "$TALE_URL/api/v1/mcp" \
  --header "Authorization: Bearer $TALE_API_KEY" \
  --header "X-Organization-Slug: $TALE_ORG_SLUG" \
  --header 'MCP-Protocol-Version: 2025-11-25' \
  --header 'Content-Type: application/json' \
  --data '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_docs","arguments":{}}}'
```

A successful `get_docs` result contains the automation reference as text and has no error flag set. Add `"arguments":{"topic":"triggers"}` for the triggers reference (each trigger kind's fields, the input its runs start with, and the events), `"validation"` for how to read a validation result and every issue code, `"settings"` for every kind of setting with its fields ([Settings over MCP](https://docs.tale.dev/develop/mcp-settings)), or `"skill"` for the [Tale skill](https://docs.tale.dev/develop/use-tale-from-your-editor#tale-skill). To inspect tool schemas instead, send `method: "tools/list"`. Keep the JSON-RPC `id` so a client can match a result to its request.

### Transport and batches

| Request | Response behavior |
| --- | --- |
| One JSON-RPC message | One JSON-RPC result or error |
| Batch of up to 20 messages (2025 revisions only) | An array of responses; notifications have no response entry |
| Notifications only | HTTP `202` |
| `OPTIONS` | HTTP `204`, `Allow: POST, OPTIONS`; no key required |
| Any other HTTP method | HTTP `405`, `Allow: POST, OPTIONS` |

Every additional tool call, resource read or listing, or prompt in a batch consumes the same request budget as a separate call. If a batch exhausts its budget, the refused entry is JSON-RPC `-32000` with `data.retryAfterMs`; the enclosing HTTP response remains `200` and has no `Retry-After`. A single request rejected at the HTTP boundary gets REST `429`. Handle both cases using the [rate-limit guidance](https://docs.tale.dev/develop/rate-limits).

The endpoint supplies no CORS headers for browser key use. Keep the API key on a trusted server or in the MCP client's credential store. A request whose `Origin` header names a site the deployment does not accept is logged, and refused with `403` `ORIGIN_FORBIDDEN` where the operator enforces that check ([environment reference](https://docs.tale.dev/self-hosted/configuration/environment-reference#mcp-endpoint)). Coding agents in a terminal send no `Origin`.

### Protocol revisions {#protocol-revisions}

The endpoint serves two generations of the protocol on the same URL and key, and decides for each request which one it is. A client that speaks `2026-07-28` sends no `initialize`: every request carries its revision and the client's capabilities, so nothing is kept between requests.

| | `2025-11-25`, `2025-06-18`, `2025-03-26` | `2026-07-28` |
| --- | --- | --- |
| Start | `initialize`, then the negotiated revision in `MCP-Protocol-Version` | No handshake; `server/discover` returns what the server speaks |
| Every request | The JSON-RPC message | `params._meta` with `io.modelcontextprotocol/protocolVersion` and `io.modelcontextprotocol/clientCapabilities`; the headers `MCP-Protocol-Version` and `Mcp-Method`, plus `Mcp-Name` for `tools/call`, `resources/read` and `prompts/get` |
| Batches | Up to 20 messages | One message per request |
| Results | As described on this page | Also `resultType: "complete"` and the server under `_meta["io.modelcontextprotocol/serverInfo"]`; `server/discover`, the lists and `resources/read` add `ttlMs` and `cacheScope: "private"` |
| `initialize`, `ping` | Answered | HTTP `404` with JSON-RPC `-32601` |
| An address that reads nothing | `-32002` | `-32602` |

A request is served as `2026-07-28` when its `_meta` names a revision or its `MCP-Protocol-Version` header names `2026-07-28`. Its headers must repeat what the body says. A missing header, or one that names another revision, method, tool, prompt or address, is refused with HTTP `400` and JSON-RPC `-32020` before anything runs. Send an `Mcp-Name` value that is not plain ASCII as `=?base64?<Base64 of the UTF-8 text>?=`. A missing or malformed `_meta` returns `-32602` with HTTP `400` and names the keys under `data.missing` or `data.malformed`.

```bash
curl --fail-with-body "$TALE_URL/api/v1/mcp" \
  --header "Authorization: Bearer $TALE_API_KEY" \
  --header "X-Organization-Slug: $TALE_ORG_SLUG" \
  --header 'MCP-Protocol-Version: 2026-07-28' \
  --header 'Mcp-Method: server/discover' \
  --header 'Content-Type: application/json' \
  --data '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{},"io.modelcontextprotocol/clientInfo":{"name":"docs-client","version":"1.0.0"}}}}'
```

The answer lists every revision under `supportedVersions` and carries the same capabilities and `instructions` as `initialize`, with the server under `_meta`. `ttlMs` says how long your client may reuse an answer: an hour for `server/discover`, the tool, prompt and template lists and the references; a minute for `resources/list` and the connector catalog; `0` for an automation or a run, because it can change with your agent's next save. Every answer belongs to the key that asked, so a cache must never share it with another key. The name in `io.modelcontextprotocol/clientInfo` is recorded with each call and with the changes the call makes, so a version an agent saves names the client that saved it.

## The tools

`tools/list` is the source for each tool's input schema, and a read tool also states its answer in `outputSchema`. Arguments are checked before execution: missing, mistyped, blank, or unexpected ones are refused with one tool result marked `isError`, whose `code` is `INVALID_ARGUMENTS` and whose `data.issues` lists every problem with its `path`, `code`, and `message`. The refusal never repeats an argument's value. Every result carries its answer as compact JSON text, and a read tool's successful answer also comes as `structuredContent`, the same object. The document passed to `validate_automation`, `run_automation`, `test_automation`, or `save_automation` is intentionally an open envelope: `get_docs` explains its grammar, and the engine validates its contents.

Tools also expose `readOnlyHint`, `destructiveHint`, `idempotentHint`, and `openWorldHint`. Hosts can use these annotations to explain a call, but they do not grant permission or guarantee safety. Reads are marked read-only; saving writes a version; deployment, deletion, installation, trigger and settings changes can replace existing state; live runs and some settings changes can contact real services. Deploying, deleting, setting a trigger, installing an automation in projects, answering a run's question, and changing settings also carry `_meta["anthropic/requiresUserInteraction"]`, so a client that honors it asks the person before every such call.

### Authoring {#authoring}

| Tool                  | What it does                                               |
| --------------------- | ---------------------------------------------------------- |
| `get_docs`            | The automation authoring reference — grammar, node kinds, capability nodes and the method table in this endpoint's own `tools/call` dialect — as text. |
| `get_catalog`         | Every node type this deployment can execute, with each capability's input schema, output signature, and `outputSchema`; `kind` narrows to one node kind and `compact: true` drops the schemas. |
| `search_catalog`      | Search the node-type catalog by keyword.                   |
| `validate_automation` | Validate an automation document without saving it: its errors and warnings, each with its location, plus the flow analysis and the inferred types. |
| `run_automation`      | Run an automation document directly against the deterministic mocks. Simulated outputs and failures (`mocks`, `failures`) stand in for nodes, and `upTo`, `only` and `item` run part of it: see [Try a draft and run tests](https://docs.tale.dev/develop/mcp-endpoint#testing). |
| `test_automation`     | Run an automation's own acceptance tests — all of them or those named in `tests` — of a draft (`automation`), or of a saved version (`name`, `version`), whose whole-suite verdict is then recorded on the version. `detail` adds what each run returned, did and went through: see [Try a draft and run tests](https://docs.tale.dev/develop/mcp-endpoint#testing). |
| `save_automation`     | Save an automation document as a new immutable version; the answer lists its warnings. See [Save without losing work](https://docs.tale.dev/develop/mcp-endpoint#save); with `name` and `restoreVersion` instead of a document it brings an older version back, see [Compare, restore and roll back](https://docs.tale.dev/develop/mcp-endpoint#versions). |
| `get_automation`      | Read one saved version — the latest when unversioned, `version: "deployed"` for the live one (`AUTOMATION_VERSION_UNKNOWN` while nothing is deployed): the document under `automation`, its `settings`, `taskContract` and `presentation`, `latestVersion`, `deployedVersion`, who saved it (`createdBy`) through which door (`createdVia`: `app`, `upload`, `mcp`, `managed` or `system`; `null` for a version saved before it was recorded) and with which client (`clientName`), its `projectIds` and its `trigger`. With `against`, the answer also carries a `diff` of what the version changed against another one: see [Compare, restore and roll back](https://docs.tale.dev/develop/mcp-endpoint#versions). |
| `list_automations`    | The organization's automations with their latest and deployed versions and the projects each is installed in (`projectIds`). |
| `deploy_automation`   | Promote one saved version to be the live version; an older one rolls back. `expectedDeployedVersion` (the version you read as live, `null` for none) refuses the deploy with `AUTOMATION_DEPLOYMENT_STALE` when another went live meanwhile; the answer names the `previousVersion` and its `kind`: `rollback` for an older version, `unchanged` when that version was live already, `deploy` for any other. |
| `delete_automation`   | Delete an automation — every version, its trigger and its installations; its runs stay. `expectedLatestVersion` must be the latest version you read (`AUTOMATION_VERSION_STALE` otherwise); a run still going refuses the delete (`AUTOMATION_HAS_ACTIVE_RUNS`). |

Use the authoring loop in this order: read the grammar and catalog, validate the document, run it against mocks, run its acceptance tests, save a version, then deploy that version. A successful mock run verifies the simulated path; it does not prove vendor credentials, network access, or real effects.

#### Save without losing work {#save}

A version carries, beside the document, the `settings` form a task shows, the `taskContract`, and the `presentation` on the automations list. A save that leaves one of them out keeps the latest version's; `null` stores none (for `presentation`, the list then keeps showing the newest earlier one); a value is checked against the schema the app reads it with, and refused as `INVALID_ARGUMENTS` with every problem when it does not fit. The answer's `carried` names what was kept.

Pass `baseVersion`, the version your edit started from. When someone saved a newer version meanwhile, the save is refused with `AUTOMATION_VERSION_STALE` and `data.latestVersion`; read that version, merge your change and save again. Without `baseVersion` the save appends, and `baseVersionChecked: false` says the check was skipped. Save a new automation with `create: true`: a name that already exists is refused (`AUTOMATION_NAME_TAKEN`), so a new automation never becomes a version of another one. A save is refused the same way, `create` or not, when the name belongs to an automation you cannot see. `projectId` installs a new automation in a project you can edit with its first version; a save of an automation that exists ignores it.

#### Compare, restore and roll back {#versions}

Every save adds a version whose document and package data stay unchanged while it is retained. Automation deletion or a governance retention policy can remove versions; only a retained version can be read, compared and brought back. `list_versions` answers all retained versions, newest first, when `limit` is left out. With `limit` it answers a page, and `nextBefore` is the `before` of the next one. Each row says who saved the version through which door and client, which version it was based on and restored from, what it changed, how its tests went and whether it is live.

Look at what a version changes before you deploy it. `get_automation` with `against` answers the version and, beside it, a `diff` against another one: `"previous"` (the version saved before it, the default), `"deployed"` (the live version), `"latest"` or a number. The diff lists what changed node by node, in the input schema, the output, the description and the tests; `diffFormat: "unified"` answers a patch over the two versions' YAML instead, and `"both"` answers both.

To go back, either way keeps the history intact. `save_automation` with `name` and `restoreVersion` saves an older version's content as the newest version, after the checks and tests of any save, and the live version stays live; when the latest version already holds that content, nothing is saved and the answer says `unchanged: true`. `deploy_automation` of an older version rolls the live version back: the answer carries `kind: "rollback"` and the `previousVersion` to deploy again. Pass `expectedDeployedVersion` so a deploy never undoes a colleague's.

#### Try a draft and run tests {#testing}

`run_automation` runs a document you have not saved, on the deterministic mocks: nothing leaves Tale and nothing is stored. Besides `input` it takes what the editor's [tests and step tests](https://docs.tale.dev/platform/automations/testing) use:

- `mocks` maps a node id to a simulated output that takes the place of its call — a connector's, a model's, an agent's, a called automation's or the node's own code. The node's input is still worked out and its effect recorded, so the run shows what the node would have sent. For a node that runs once per item (`forEach`), give a list with one output per item.
- `failures` maps a node id to a message, and that node fails with it, so you can see what the rest of the automation does about it.
- `upTo` runs a node and everything it needs, and leaves the rest out.
- `only` runs one node alone, on the data you pin in `mocks` for every node it reads, and `item` picks one item of a `forEach` node run that way, counting from 0.

The answer says where the run stood in (`focus`) and which simulated outputs it never used (`unusedMocks`) because their node was skipped, left out or never reached. Simulated outputs or a scope the document can't take are refused as `BENCH_INVALID` — a node the document doesn't have (the refusal names the closest one), or scopes that can't be combined, such as `item` without `only` — with the engine's own explanation under `data.issue`. A simulated output larger than 64 KB, or simulated outputs larger than 512 KB together, are refused as `INVALID_ARGUMENTS`. A run is stopped before its next step once it has run for 20 seconds, and answers `stoppedBy: "time_limit"`.

`test_automation` runs the tests in the document's `tests:` block, the ones [the editor runs](https://docs.tale.dev/platform/automations/testing). Leave `tests` out to run all of them, or list names to run only those, by the name the document gives each. A name no test bears is refused with `INVALID_PARAMS` and the names there are; so is a name that several tests bear, with the places they stand at. Each result says whether its test passed and, for one that didn't, what differed and where (`failures`). `detail` adds to each result what its run returned (`"output"`), would have done (`"effects"`) and went through (`"trace"`, the run step by step). A run larger than 256 KB, or runs larger than 384 KB together, are left out and marked `detailOmitted`; run that test alone to read it. A test stops after 10 seconds and a suite after 60.

The whole suite of a saved version, run to its end, is recorded on the version as its verdict, as made by hand: the answer says `recorded`, and `list_versions` shows it. A part of the suite, a draft and a suite the time ran out on record nothing.

Both run in Tale and use the organization's budget for runs that are never stored, one unit for each run or test — the budget the app's own tries and test runs use — beside the key holder's [execution budget](https://docs.tale.dev/develop/rate-limits). When either is spent, or the server is already running as many as it can, the call is refused with `RATE_LIMITED` and `data.retryAfterMs`.

#### Read a validation result {#validation-result}

`validate_automation` answers `valid`, `errors`, `warnings`, `analysis`, and `types`. Errors stop a save and a deployment; warnings never do. `save_automation` returns the `warnings` of the version it saved, and a refused save returns its `warnings` beside its `errors`, so you hear about both while you work.

| Issue field | What it holds |
| --- | --- |
| `code` | The stable value to branch on, such as `REF_UNKNOWN_FIELD` or `MAYBE_NULL` |
| `message`, `hint` | English sentences that stay stable between releases; show them, but branch on `code` |
| `nodeId` | The node the issue is about, when there is one |
| `at.pointer` | A JSON Pointer into the document you sent, such as `/nodes/2/input/to`; `""` is the whole document |
| `at.range` | `[start, end)` in UTF-16 code units inside the string at `at.pointer`, when the issue is one expression in a template, a condition, or code |
| `at.subject` | `key` when the pointer names a field that should not exist; `missing` when it names one that should exist and does not |
| `params` | The facts the message is built from, such as `node`, `field`, `ref`, `key`, and `suggestion` |
| `related` | Other places involved: the node a read depends on, the node whose condition or failure causes the issue, readers, or the members of a cycle |

`analysis.nodes.<id>` says whether a node is `reachable`, whether it `alwaysRuns`, how it can be skipped (`maySkip`), and whether its failure stops the run (`failureHandling: "halts"`) or lets the run go on (`"continues"`). `analysis.paths` lists the ways a successful run can go, up to 32 of them with `count` for all, and names the nodes whose failure ends a run. `types` gives the JSON Schema of the run input, of each node's output, and of the automation's result; `get_catalog` gives each capability's `outputSchema` the same way. [What Tale checks before a run](https://docs.tale.dev/platform/automations/concepts#checks) explains each family of checks.

`detail` chooses what comes beside the issues: `["analysis"]`, `["types"]`, or `[]` for the issues alone; left out, you get both. Some warnings compare the document with your organization instead of checking it: a skill no run of the automation can reach (`SKILL_UNKNOWN`), a connector nobody has connected (`CONNECTOR_NOT_CONNECTED`), a secret nobody has stored (`SECRET_UNKNOWN`, reported only to Owners, Admins and Developers), an agent runtime this deployment can't run (`HARNESS_UNKNOWN`), and an event trigger that waits for an event Tale doesn't raise (`EVENT_UNKNOWN`). They never stop a save; the [discovery tools](https://docs.tale.dev/develop/mcp-endpoint#discovery) list what exists.

### Run & trigger management {#management}

| Tool             | What it does                                                                                                   |
| ---------------- | -------------------------------------------------------------------------------------------------------------- |
| `run_deployed`   | Run the deployed version live and WAIT for the finished result — output, trace and effects in one answer; a run that outlives the wait answers with its `runId` to poll. Takes the same optional `idempotencyKey` as `start_run`, on the same ledger the REST endpoint keeps: a repeat answers the first run with `duplicate: true` and starts nothing, whichever door started it. |
| `start_run`      | Start a run in the background and return a run handle immediately; poll get_run for the result. `mode: "live"` (the default) runs the deployed version for real; `mode: "mock"` runs any saved version — the latest when `version` is omitted — against the deterministic mocks, and the run is recorded in the run history like one started in the app. Takes an optional `idempotencyKey` — the REST endpoint's `Idempotency-Key`, the same ledger: the same key with the same arguments answers the first run's handle with `duplicate: true` and starts nothing, the same key with different arguments is refused (`IDEMPOTENCY_KEY_REUSED`). The `Idempotency-Key` HTTP header is refused on this endpoint (**400**, `INVALID_HEADER`) — a batch carries up to 20 calls, so the key rides in the tool arguments. |
| `list_runs`      | Recent runs the key may read, newest first — of one automation or across the organization's projects; each names its `projectId`. `mode` and `statuses` filter, and `nextCursor` (null on the last page) is the `cursor` of the next older page; pass it unchanged with the same filters. |
| `get_run`        | One run in full: status, output, trace, effects and `projectId` — a project run's id is the one `GET /api/v1/projects/{id}/runs/{runId}` takes. A run waiting on a person's answer (`waitingFor: "ask"`) names the question under `ask`: its `askId` and the question. `detail` picks what to answer beside the status; `detail: []` answers the status alone, for polling a long run. `include: ["record"]` adds the run step by step under `record`: each step's status, why it ran or was skipped — each condition explained with the values it read — why it failed (`failure.reason` and its explanation), and glimpses of its values; `include: ["travels"]` adds the data that travelled between steps. |
| `get_run_node`   | One step of a run read whole — or one of its items or passes (`item`, `pass`): what it received and returned, secrets withheld; where each `{{ }}` unit's text landed; what it read from earlier steps; how its output differs from its input; and its call to a connector or a model. A step the record doesn't hold is refused (`NODE_RUN_NOT_FOUND`). |
| `compare_runs`   | Two runs of one automation side by side (`a`, the earlier, and `b`): what changed in the version, how input and output differ, each step's status and decisions, and the first step where the runs went different ways, with the values that flipped its condition. Runs of different automations are refused (`RUN_COMPARE_MISMATCH`). |
| `replay_run`     | Run a run again, in its own project: with its own input (`kind: "again"`), with an edited `input` (`"edited"`), or from one step (`"from"` with `from`) — the steps it finished outside that step and what it feeds are reused, the rest run anew. `version` picks the version (the run's own by default), `mode` the mode (the run's own; a fork of a mock run stays mock). `dryRun: true` answers the plan instead — what is reused, what runs again, and how many writes go out a second time. A live replay needs the developer role and the deployed version; refusals: `REPLAY_RUN_NOT_FINISHED`, `REPLAY_NODE_UNKNOWN`, `REPLAY_GRAPH_CHANGED` (with `data.nodes`), `REPLAY_MODE_MISMATCH`, `REPLAY_PROGRESS_UNREADABLE`, `REPLAY_INPUT_UNAVAILABLE`. Takes `idempotencyKey` like `start_run`. |
| `cancel_run`     | Stop a run at its next node boundary.                                                                          |
| `answer_run_ask` | Answer the question a waiting run asked a person (`get_run` answers it as `ask`, with the `askId` to pass); the run resumes on the answer, which is recorded as the key holder's. |
| `list_versions`  | One automation's immutable version history, newest first; each row says whether it is the `deployed` one, who saved it (`createdBy`, and `createdByName` while they are a member) through which door (`createdVia`, `clientName`), which version it was based on (`basedOnVersion`) and restored from (`restoredFromVersion`), what it changed (`changes`), how its tests went (`tests`) and when it last went live (`lastDeployedAt`). `limit` asks for a page, and `nextBefore` (`null` on the last page) is the `before` of the next one; `deployedVersion` and `latestVersion` name the live and the newest version beside the list (`null` while there is none). `deployments` lists when versions went live, newest first, with what was live before. |
| `set_automation_projects` | Install an automation in projects (`add`) and remove it from others (`remove`) in one change; the answer names what was `added`, `removed` and `unchanged`. Removing it from a project it is not in is refused (`AUTOMATION_NOT_INSTALLED`). |
| `get_automation_metrics` | The organization's run figures for 7, 30 or 90 days (`periodDays`), live runs by default or mock ones: runs by outcome, success rate, average duration, a per-day series and the busiest automations, each against the window before. |
| `list_triggers`  | What starts the automations (never the webhook secret).                                                        |
| `delete_trigger` | Unbind an automation's trigger; its versions and run history stay.                                             |
| `set_trigger`    | Bind what starts the automation: a schedule (a repeat rule or a cron expression, in a time zone), a webhook or a platform event, each with an optional fixed `input`. The call replaces the whole trigger, so send back the `startDate` and `input` that `list_triggers` reads to keep them. A webhook's `token` is answered once, here, and never again — store it; `deployed` says whether deliveries will run: a trigger bound to an automation with no deployed version is stored and fires nothing until one is deployed. `warnings` say what the deployed version would make of the trigger's input. |

| Choose | When |
| --- | --- |
| `run_automation` | Try an unsaved document against deterministic mocks, with simulated outputs for some of its steps if you like; `mode: "live"` is refused |
| `run_deployed` | Run the saved deployment live and wait up to 30 seconds; poll the returned `runId` if it continues |
| `start_run` | Start the saved deployment in the background and poll `get_run`; pass `idempotencyKey` to make a retry safe |
| `start_run` with `mode: "mock"` | Run a saved version — deployed or not — against the mocks, with a record anyone can open in the run history |

To fix a failed run, read it with `get_run {runId, include: ["record"]}`: the failed step's `failure.reason` names what went wrong and its explanation shows the values the expression read. `get_run_node` reads that step whole. Fix the document, try it with `run_automation` on the run's input, and once a new run finished, `compare_runs` shows where it went another way.

Both deployed-run tools use the durable runner with the same authorization and execution records. `start_run` accepts an optional `projectId`. A project-bound automation must run in a project where it is installed; a sole binding can be selected automatically. With no bindings, omission means organization scope. Read `projectIds` from `list_automations` and the actual `projectId` from the returned handle rather than guessing a REST polling URL.

### Discovery {#discovery}

| Tool | What it does |
| --- | --- |
| `list_models` | The models you may use, filtered by your model access: each with `providerSlug` (an `agent` node saves it as `modelProvider`), `lane` (`direct` when a provider serves it, `subscription` or `broker` when a member's subscription does), the `nodeTypes` it suits (an `llm` node takes only a directly served model) and the agent runtimes it is offered to (`harnesses`). `nodeType` and `harness` filter the list. |
| `list_harnesses` | The agent runtimes an `agent` node can name as `harness`, the `default` one that runs when it names none, and whether a subscription can serve each. |
| `list_skills` | The organization's skills; with `projectId`, also the team skills of that project, the ones a run in it can use. A project you can't read answers `PROJECT_NOT_FOUND`. |
| `list_connectors` | The connectors this deployment offers, whether your organization connected each (`connected`) and how many actions it has; `query` filters. `search_catalog` lists the actions. |
| `list_agent_secrets` | The names of the organization's agent secrets with a masked `preview`, never a value. Owners, Admins and Developers see them; anyone else gets an empty list with a `note`. |
| `list_projects` | The projects you can read: whether each is `writable` or `archived`, and the `automations` installed in it that you may see. `query` filters by name; `includeArchived` adds archived projects. |
| `list_events` | The events Tale raises, each with when it fires: what an event trigger can wait for. |

Each answer carries a `hint` that names the tool or the setting that changes it.

### Settings {#settings}

| Tool | What it does |
| --- | --- |
| `get_settings` | Without `kinds`, the catalog: every kind of setting with what it is, its operations (`ops`) and actions (`acts`), its `baseRisk`, the Settings pages where a person makes the same change (`areas`), whether this deployment serves it over MCP (`available`), and whether your role may `read` and `write` it. With `kinds` — for one kind, also `ids` or the `cursor` of its next page — every resource you may read, with its `key`, its `config` and its `hash`. A kind you may not read is listed under `refused` with the reason. A listing with more answers the cursor of each such kind under `cursors` (also as `nextCursor` after a read of one kind): pass it back with that one kind, and only that kind reads it. A page ends once what it holds would crowd the answer, so a kind with long instructions takes more pages. |
| `plan_settings` | Plan up to 32 changes without making any. Each answers its `action` (`create`, `update`, `delete`, `act` or `unchanged`), the `currentHash`, a `diff` member by member, its `effects` and its `risk` (`low`, `high` or `critical`), or the `refusal` that stops it. |
| `apply_settings` | Make the changes. `expected` maps each changed resource's `key` to the hash you read, or `null` for one you create. If a change is refused or a resource changed since you read it, nothing is applied. Otherwise the changes run in a fixed order across kinds, and the first one that fails stops the rest: the answer lists what was `applied`, what `failed` and what was `skipped`. |

A change names its `kind`, the resource's `id` (left out for a kind with one resource) and its `op`: `set` replaces the resource with the whole `config`, creating it when it does not exist; `delete` removes it; `act` runs one of the kind's actions, with its `args`. Plan first, show the plan to the person, then apply the same changes: `apply_settings` asks the person before every call and uses a [budget](https://docs.tale.dev/develop/rate-limits) of its own.

[Settings over MCP](https://docs.tale.dev/develop/mcp-settings) lists every kind of setting, who may change it and what stays in Tale.

No secret travels through these tools. A stored secret reads as `{"masked": true, "preview": "…"}`; send that value back unchanged to keep it. A change that carries any other value in a secret's place, or a credential anywhere in its config, is refused with `SECRET_ARGUMENT_REFUSED`; a person enters a new secret in Tale.

### Capabilities & knowledge {#capabilities}

| Tool                  | What it does                                                                                                        |
| --------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `search_capabilities` | Search everything this organization can do — its deployed automations, by name and description.                     |
| `invoke_capability`   | Invoke one capability by id: a deployed automation, run live as `run_deployed` runs it. A step the organization gates for approval leaves the run waiting for a person to decide in Tale. |
| `get_knowledge`       | Retrieve passages from the organization's knowledge — its documents and its crawled web pages. `corpus` is `private` (documents), `public-web` (crawled pages) or `all`; the REST spellings `documents` and `web` are taken too. `query` is capped at 2000 characters. Each passage carries `text`, `source` (a title), `ref`, `corpus`, `chunkIndex`, `score`, `similarity` when the dense leg ranked it, `url` for a web page, and — for a document — the `documentId` that `GET /api/v1/documents/{id}` takes (a project file's id for a project hit) and its `projectId`, the same citation the REST search answers. |

The capability registry currently contains deployed automations. It does not include builtin tools, connector actions, skills, or external MCP servers. Invoking a deployed automation is the same live operation as `run_deployed`, and it waits up to 30 seconds for the run to finish the same way. When a step needs an [approval](https://docs.tale.dev/platform/approvals/concepts), the run waits for a person to decide in Tale: the answer's `output` is the run with `status: "waiting"`, and `get_run` shows `waitingFor: "approval"`. No tool approves or rejects it, so your client should tell the person rather than retry. Stopping the run with `cancel_run` withdraws the approval.

## Resources and prompts {#resources-and-prompts}

Besides tools, the endpoint serves resources, which a client reads by address, and prompts: ready-made requests a person starts work from. Both read what a tool already answers, for the same person and through the same checks.

### Read by address {#resources}

`resources/list` names the fixed resources, then every automation the key holder can see, 100 per page; follow `nextCursor` for the next page. `resources/templates/list` returns the address patterns, and `resources/read` returns a resource's contents.

| Address | Contents | Read like |
| --- | --- | --- |
| `tale://docs/authoring`, `tale://docs/triggers`, `tale://docs/validation`, `tale://docs/settings`, `tale://docs/skill` | The references, as Markdown | `get_docs` with that `topic` |
| `tale://catalog/{kind}` (`transform`, `llm`, `agent`, `subautomation`, `connector`) | A core node kind's section of the reference, or the connector actions | `get_catalog` with that `kind` |
| `tale://automations/{name}` | The latest saved version, as JSON | `get_automation` |
| `tale://automations/{name}/versions/{version}` | One saved version; `{version}` is a number or `deployed` | `get_automation` with `version` |
| `tale://runs/{runId}` | One run with its output, trace and effects | `get_run` |

Write each `/` in an automation name as `%2F`: `tale://automations/billing%2Fdunning`. An address that reads nothing returns JSON-RPC `-32002` (`-32602` on `2026-07-28`) with the refusal's code in `data.code`, such as `AUTOMATION_NOT_FOUND`; an automation the key holder cannot see returns the same error as one that does not exist. A malformed address, such as a version that is not a number, returns `-32602`.

### Start from a prompt {#prompts}

`prompts/list` names three prompts, and `prompts/get` returns the message with what it is about attached as a resource. Claude Code lists them as `/tale:edit_automation` and so on.

| Prompt | Arguments | What it asks the agent to do |
| --- | --- | --- |
| `edit_automation` | `name` (optional) | Change the automation or create one, validate it, test it on the mocks and save it with `baseVersion`; it deploys nothing |
| `debug_failed_run` | `runId` | Explain why the run failed, reproduce the failure on the mocks and propose the smallest fix |
| `add_trigger` | `name`, `kind` (optional: `schedule`, `webhook` or `event`) | Decide what starts the automation, and ask before calling `set_trigger` |

Each argument is a single word, because clients such as Claude Code split arguments at spaces. An argument sent empty counts as left out. A prompt whose run or automation the key holder cannot read returns `-32602` with that read's code.

## What the key may do

| Operation | Required access |
| --- | --- |
| Reads, validation, mock runs (`start_run` with `mode: "mock"` included) and acceptance tests, capability search, knowledge retrieval | Organization membership, plus the resource's normal access rules |
| Answer a run's question | Organization membership; for a run in a project, edit access to that project |
| Agent secret names (`list_agent_secrets`) | Owner, Admin or Developer role; anyone else gets an empty list |
| Read, plan and change settings (`get_settings`, `plan_settings`, `apply_settings`) | What your role allows on the same Settings page; the catalog says what your role may do with each kind |
| Save, deploy, delete, install in projects, set/delete a trigger, cancel a run, or execute live | Developer capability, plus the resource's normal access rules |

The key identifies its holder; it does not expand that person's role or project access. An automation installed only in projects the key holder cannot read is left out of `list_automations`, and every read of it answers `AUTOMATION_NOT_FOUND`, as for one that does not exist. Live `invoke_capability` calls also pass through the execution checks.

Every change an MCP call makes to an automation — a version saved, a deploy, a delete, a trigger set or removed, an installation added or removed — is in the organization's audit log, marked as coming through MCP with the tool and the API key. In the event's details, **Source** reads Coding agent, and **Client** names the agent's app when it names itself on every call, as on revision 2026-07-28; an app on a 2025 revision names itself only on `initialize`, so its events show no **Client** ([Audit logs](https://docs.tale.dev/platform/admin/governance/audit-logs)).

Read `GET /api/v1/me` before configuring privileged tools: `capabilities.developer` reports the current role gate, while `deploymentEditor` is a separate operator allowlist and does not authorize MCP authoring. The MCP tool-error envelope below still applies; a REST capability check does not change JSON-RPC error handling.

### Distinguish a transport error from a refused tool

| Result | How to handle it |
| --- | --- |
| JSON-RPC `-32601` | Correct the unknown method; on `2026-07-28` it comes with HTTP `404`, also for `initialize` and `ping` |
| JSON-RPC `-32602` | Correct the tool name using `tools/list`, a prompt's name or arguments using `prompts/list`, or a resource address. With HTTP `400`, complete the `2026-07-28` `_meta` named in `data.missing` or `data.malformed` |
| JSON-RPC `-32002` | The resource address reads nothing; `data.code` names the refusal, such as `AUTOMATION_NOT_FOUND`. On `2026-07-28` the same answer has the code `-32602` |
| JSON-RPC `-32020` (HTTP `400`) | A `2026-07-28` request's headers do not repeat its body; send `MCP-Protocol-Version`, `Mcp-Method` and `Mcp-Name` as described in [Protocol revisions](https://docs.tale.dev/develop/mcp-endpoint#protocol-revisions) |
| JSON-RPC `-32022` (HTTP `400`) | Send one of the revisions in `data.supported`: in `MCP-Protocol-Version`, and on `2026-07-28` also in `_meta`. A 2025 revision is opened with `initialize` |
| Tool result with `isError: true` | Read its text payload's stable `code`, explanatory `error`, and actionable `hint`; `data` may contain field problems |
| `validate_automation` with `valid: false` | Normal validation result; inspect `errors` and where each one is ([Read a validation result](https://docs.tale.dev/develop/mcp-endpoint#validation-result)), even though `isError` remains false. Warnings never make a document invalid |
| Capability `output` with `status: "waiting"` | The run waits, for example for an approval a person gives in Tale; it has neither finished nor failed. Poll `get_run` and do not retry it |
| Capability result `refused` | Error result; correct the stated cause |

Tool refusal codes include `AUTOMATION_NOT_FOUND`, `AUTOMATION_VERSION_UNKNOWN`, `AUTOMATION_NOT_DEPLOYED`, `RUN_NOT_FOUND`, `AUTOMATION_INVALID`, `AUTOMATION_TESTS_FAILING`, `AUTOMATION_VERSION_STALE`, `AUTOMATION_DEPLOYMENT_STALE`, `AUTOMATION_NAME_TAKEN`, `AUTOMATION_HAS_ACTIVE_RUNS`, `AUTOMATION_NOT_INSTALLED`, `HUMAN_ASK_NOT_FOUND`, `HUMAN_ASK_NOT_PENDING`, `HUMAN_ASK_EXPIRED`, `EMPTY_ANSWER`, `INVALID_CURSOR`, `BENCH_INVALID`, `LIVE_MODE_UNAVAILABLE`, and `NOT_SUPPORTED`. The latter means the host does not support that run/version/trigger operation. `start_run` refuses a reused `idempotencyKey` with different arguments as `IDEMPOTENCY_KEY_REUSED`; `invoke_capability` refuses an id the registry does not hold — a saved-only automation is not in it — as `CAPABILITY_NOT_FOUND` and input its schema rejects as `CAPABILITY_INPUT_INVALID`; `get_knowledge` lifts the knowledge endpoint's own codes through (`KNOWLEDGE_UNAVAILABLE` when the search itself failed). Platform errors retain their own code, hint, and optional data; for example, missing developer access returns `FORBIDDEN_DEVELOPER_SETTINGS`. `INVALID_ARGUMENTS` lists every argument problem; a value outside an enumerated set is refused with the set named. `RATE_LIMITED` means the tool needed an execution, or a settings change, and the key holder's [budget](https://docs.tale.dev/develop/rate-limits) for it is spent: wait `data.retryAfterMs`. `run_automation` and `test_automation` are refused the same way when the organization's budget for runs that are never stored is spent. `INTERNAL_ERROR` means the call failed unexpectedly; its `data.requestId` is the id to quote to whoever runs the deployment. The settings tools add `SETTINGS_STALE` (a resource changed since you read it; `data.currentHash` is its hash now), `SECRET_ARGUMENT_REFUSED` (a change carried a secret; `data.places` says where, never what), `SETTINGS_KIND_UNAVAILABLE` (this deployment does not serve that kind over MCP), `SETTINGS_NOT_FOUND` (an action on a resource that does not exist) and `SETTINGS_DUPLICATE` (one resource changed twice in one call).

An unknown automation name is an error even for `list_versions`, `list_runs`, and `list_triggers`; an empty list means an existing automation has no matching items. The one exception is run history: a deleted automation keeps its runs, so `list_runs {name}` answers them for as long as they exist, and only a name that never ran is `AUTOMATION_NOT_FOUND`. `get_catalog` narrowed to a core node kind (`transform`, `llm`, `agent`, `subautomation`) answers an empty list with a `hint` pointing at `get_docs`, as `search_catalog` does, and the kind's own section of the reference as `reference`. Invalid documents passed to tools that need a valid one, search failures, and missing deployments set `isError: true`. Only the validation tool reports an invalid document as its ordinary verdict.

## Where this fits

REST and MCP share keys, organization scoping, and durable run objects. Use REST when you want explicit HTTP routes; use MCP when your client understands tool discovery and calls. Tale does not register or call external MCP servers through this endpoint.
