# Call the workflow tools over MCP

Serves the workflow API as Model Context Protocol tools, so an AI agent can build, test, deploy and monitor a workflow without writing HTTP calls.

- Endpoint: `POST https://api.returning.ai/v1/workflow-builder/data-workflows/mcp`
- Section: Workflow Builder / AI agent access
- Authentication: `Authorization: Bearer <API_KEY>` (Community API key)
- Permission: `workflowBuilder`
- Retries: Each request is independent, so a dropped connection loses nothing on the server side.
- Guide: generated from the published specification
- Last updated: 28 Oct 2026
- Web page: https://docs.returning.ai/api-reference/workflow-builder/call-the-workflow-tools-over-mcp

## Authentication

- Header: `Authorization: Bearer <API_KEY>`
- Permission: `workflowBuilder`

That one permission registers every tool: the eight reads, the fourteen writes, the two tests, deploy and undeploy, and `rai_retry_run`. There is no per-tool permission and no second permission to add for a live test or a retry.

A key without `workflowBuilder` gets `403` rather than an empty tool list, so an empty list never means a broken server.

`Content-Type: application/json` is required. The client must list both `application/json` and `text/event-stream` in `Accept`; a request that does not list both is refused with `406`. The response itself is JSON.

Send one JSON-RPC message per request. A batch (a JSON array of messages) is refused with `400`, because one request would otherwise run several tool calls under one rate-limit token.

Tool arguments are strict: an argument the tool does not declare is rejected rather than ignored, so an agent cannot smuggle a REST-only field into a call. That rejection is a JSON-RPC `error` (code `-32602`, invalid params) inside an HTTP `200`, not an HTTP error. Each tool's schema lists exactly what it accepts, and the read tools return the same payloads the REST routes return.

| Failure | Meaning | Recovery |
| --- | --- | --- |
| `400 VALIDATION_FAILED` | A JSON-RPC batch or an `mcp-session-id` header | Send one message per request, with no session header. |
| `400` with JSON-RPC error `-32700` | The body is JSON but not a JSON-RPC message | Send a JSON-RPC 2.0 request object. |
| `403 API_KEY_PERMISSION_DENIED` | The key has no `workflowBuilder` permission | Grant the permission. |
| `405 METHOD_NOT_ALLOWED` | `GET` or `DELETE` on this path | Use `POST`. |
| `406` with JSON-RPC error `-32000` | `Accept` does not list both `application/json` and `text/event-stream` | Send `Accept: application/json, text/event-stream`. |
| `415 UNSUPPORTED_MEDIA_TYPE` | The body was not declared `application/json` | Send the header. |

## Behaviour

Tools call the same operations the REST routes call, under the same one permission, and they fail with the same structured error codes.

The endpoint is stateless: every request is independent, there is no session id and no server-side conversation state. A key with `workflowBuilder` sees all 27 tools, and each call is checked again before it runs.

## Request

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | `string` | Yes | Community API key: `Bearer <API_KEY>`. |
| `Accept` | `string` | Yes | Must list both `application/json` and `text/event-stream`, or the MCP transport answers `406`. |
| `Content-Type` | `string` | Yes | Request body format. (`application/json`) |

### Body

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `jsonrpc` | `string` | Yes | Protocol version. |
| `id` | `integer` | No | Request id echoed in the response. |
| `method` | `string` | Yes | `tools/list` or `tools/call`. |
| `params` | `object` | No | For `tools/call`: `{ name, arguments }`. |

### Watch for

- The endpoint is stateless. Never send `mcp-session-id`; the server answers `400` rather than pretending the session exists, because ignoring it would make a client believe a session had been established.
- The whole route group is rate limited per community, so a `429 RATE_LIMITED` can arrive before a tool runs.
- Tools take an optional `revision` instead of an `If-Match` header, and return the new revision, so a chain of edits needs no re-read between calls. Omit it for last-write-wins, or send it to be refused instead of overwriting a concurrent change. A deployed workflow takes the same call, and the edit applies to it immediately.
- Test tools take `mode`, and `live` is the default: `rai_test_node` and `rai_test_workflow` really run the step unless you pass `mode: "dry-run"`. No argument confirms the side effects, so pass the mode explicitly whenever you do not mean to write or pay.
- `rai_retry_run` needs no confirmation argument. It accepts an `idempotencyKey` so a repeated call replays the first answer instead of queueing a second retry, and a second call with a different key while a retry is in flight is refused with `RETRY_IN_PROGRESS`.
- Read tools are annotated `readOnlyHint: true`. The tools that can change what members get are annotated `destructiveHint: true`: deleting a workflow, a step or a branch, testing a step or a workflow, deploying, and retrying a run. A client can use those hints to ask a person before calling them, and it should: the server asks for nothing.
- Tool names are prefixed `rai_`. The exact list is in the connection guide for AI agents.
- The MCP endpoint is not a second permission system: it loads the key's permissions and re-checks them on every call before the handler runs. A key that lost `workflowBuilder` between the list and the call is refused with `API_KEY_PERMISSION_DENIED`.

### Example request

```bash
curl --request POST \
  --url https://api.returning.ai/v1/workflow-builder/data-workflows/mcp \
  --header 'Authorization: Bearer <API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list"
  }'
```

### More examples

List the tools this key may call:

```bash
curl --request POST \
  --url "https://api.returning.ai/v1/workflow-builder/data-workflows/mcp" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json, text/event-stream" \
  --data '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

Call one tool:

```bash
curl --request POST \
  --url "https://api.returning.ai/v1/workflow-builder/data-workflows/mcp" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json, text/event-stream" \
  --data '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"rai_get_workflow","arguments":{"workflowId":"65f1a2b3c4d5e6f708192a3b"}}}'
```

A batch is refused before any tool runs:

```json
{
  "meta": {
    "status": "error",
    "statusCode": 400,
    "code": "VALIDATION_FAILED",
    "requestId": "3f4d0e8b-7a6c-4f1e-9b2d-5a7c8e1f0a3b"
  },
  "message": "API request rejected error.",
  "detail": "JSON-RPC batching is not supported by this MCP endpoint: send one request per POST.",
  "solution": "Read error detail and try again."
}
```

A tool list response is a JSON-RPC envelope; the tool's payload is a text block holding the same JSON the REST route returns:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "rai_get_workflow",
        "title": "Get one workflow",
        "description": "Read one workflow: trigger, nodes in canvas order (router branches nested), the current revision, and redactedPaths naming every credential the response replaced.",
        "annotations": { "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false }
      }
    ]
  }
}
```

## Response

HTTP `200` with a JSON-RPC 2.0 envelope. A successful tool call returns `result.content[0].text`, a JSON string with the same payload the REST route returns, so the readback rules are the REST rules: a write returns its new `revision`, and a read returns the stored state.

A tool that fails returns `result.isError: true` with a text block carrying `code`, `detail`, `statusCode` and any extra fields the REST error carries, for example `currentRevision` on a conflict, or `allowed` on an unknown field. Check `isError` before parsing the payload; the call itself is still HTTP `200`.

A permission refusal carries `API_KEY_PERMISSION_DENIED`, whether the key never had the permission or no longer has it.

An unknown method or a malformed message is a protocol-level `error` object instead.

### Response fields

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `jsonrpc` | `string` | - | Protocol version. |
| `id` | `integer` | - | Id of the request being answered. |
| `result` | `object` | - | Tool list or tool result. |
| `error` | `object` | - | Protocol-level error, for an unknown method or a malformed message. |
| `error.code` | `integer` | - | - |
| `error.message` | `string` | - | - |

### Example response (200)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "rai_get_workflow",
        "title": "Get one workflow",
        "annotations": {
          "readOnlyHint": true,
          "destructiveHint": false
        }
      }
    ]
  }
}
```

## Errors

### Fix the request

| Status | Code | What to do |
| --- | --- | --- |
| 400 | `VALIDATION_FAILED` | A JSON-RPC batch, or the request carried an `mcp-session-id` header. Send one JSON-RPC message per request, with no session header. |
| 400 | - | With JSON-RPC error `-32700`. The body is JSON but not a JSON-RPC message. Send a JSON-RPC 2.0 request object. |
| 401 | `AUTHENTICATION_REQUIRED` | No key, or an unknown key. Send a valid key. |
| 401 | `API_KEY_REQUIRED` | A valid admin session was used, because this API accepts community API keys only. Use a community API key. |
| 403 | `API_KEY_PERMISSION_DENIED` | The key has no `workflowBuilder` permission. Grant the permission. |
| 405 | `METHOD_NOT_ALLOWED` | `GET` or `DELETE`. Use `POST`. |
| 406 | - | With JSON-RPC error `-32000`. `Accept` does not list both `application/json` and `text/event-stream`. Send `Accept: application/json, text/event-stream`. |
| 413 | `VALIDATION_FAILED` | The request body is too large to read. Nothing was written. Send a smaller body; a large import is better split into a create plus step writes. |
| 415 | `UNSUPPORTED_MEDIA_TYPE` | The body was not declared `application/json`. Send the header. |

### Retry with backoff

| Status | Code | What to do |
| --- | --- | --- |
| 429 | `RATE_LIMITED` | The community's API rate limit was reached. Wait for the window to reset, then retry. |
| 500 | `INTERNAL_ERROR` | Unexpected failure outside a tool call. Retry with bounded backoff. |

**Retries:** Each request is independent, so a dropped connection loses nothing on the server side. Retrying a mutating tool is subject to the same rules as the REST route: send `revision` and a second call with the old value is refused with `VERSION_CONFLICT`; omit it and the second call applies as the last write. `rai_create_workflow` has no idempotency key in the tool schema, so a retried create can create two workflows. Read the workflow list before retrying a create whose response was lost.

## Next step

- [Create API key](https://docs.returning.ai/api-reference/api-keys-community-api-keys/create-api-key.md): `POST /v1/communities/{communityId}/api-keys`.
