POST /mcp needs workflowBuilder.workflowBuilder sees all 27 tools, and each call is checked again before it runs.Authorization: Bearer <API_KEY> with 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.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.400, because one request would otherwise run several tool calls under one rate-limit token.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. |
{
"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."
}{
"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 }
}
]
}
}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.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.API_KEY_PERMISSION_DENIED, whether the key never had the permission or no longer has it.error object instead.| Status and code | When | Recovery |
|---|---|---|
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. |
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. |
415 UNSUPPORTED_MEDIA_TYPE | The body was not declared application/json | Send the header. |
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. |
200 response with isError: true.meta envelope, so branch on the HTTP status for them rather than on meta.code:405 is { "code": "METHOD_NOT_ALLOWED", "detail": "...", "requestId": "..." } at the top level, with an Allow: POST header.406 (an Accept header without both types) and the 400 for a body that is not a JSON-RPC message come from the MCP transport itself, as a JSON-RPC error: { "jsonrpc": "2.0", "error": { "code": -32000, "message": "Not Acceptable: Client must accept both application/json and text/event-stream" }, "id": null }, with code -32700 and message Parse error for the 400.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.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.429 RATE_LIMITED can arrive before a tool runs.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.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.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.rai_. The exact list is in the connection guide for AI agents.workflowBuilder between the list and the call is refused with API_KEY_PERMISSION_DENIED.rai_list_node_types is the first tool to call, mirroring GET /catalog.rai_get_workflow returns the revision to pass to a later tool call when you want a concurrent change refused rather than overwritten.curl --location 'https://api.returning.ai/v1/workflow-builder/data-workflows/mcp' \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Accept: application/json, text/event-stream' \
--header 'Content-Type: application/json' \
--data '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}'{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "rai_get_workflow",
"title": "Get one workflow",
"annotations": {
"readOnlyHint": true,
"destructiveHint": false
}
}
]
}
}