Skip to content

MCP Integration

Audience: owners and operators wiring an MCP-aware client (Claude Code, Claude Desktop, IDE agents, custom Pydantic AI / OpenAI Responses agents) into an Axon instance.

Russian mirror: use the language selector at the top of the page → Русский.

What it is and why

Inbound MCP is a second control plane mounted at /api/v1/mcp. It exposes a deliberately small axon.* tool surface that lets an external agent inspect the platform (workflows, runs, approvals, projects, conversations, cases) and trigger guarded mutations (workflow lifecycle, approval decisions, outbound conversation reply) without bypassing the canonical CommandEnvelope + RBAC + policy + audit + outbox pipeline.

  • Reads delegate to the same query handlers the Console REST API uses (no parallel storage paths).
  • Mutations go through the canonical CommandProcessor; the MCP layer only builds the envelope with source="mcp", actor_type=SERVICE, actor_id="mcp:<key_id>".
  • Auth is pre-issued bearer token — Authorization: Bearer axon_mcp_<key_id>.<secret>. There is no OAuth flow in the MVP (PLAN-MCP-INBOUND §0.4).

Connecting a client

Two paths are supported; Claude Code (native HTTP) is the default.

Variant A — Claude Code (native HTTP MCP)

claude.json or project/user .mcp.json:

{
  "mcpServers": {
    "axon": {
      "type": "http",
      "url": "https://axon.example.com/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer axon_mcp_<key_id>.<secret>"
      }
    }
  }
}

Variant B — Claude Desktop via the mcp-remote bridge

~/Library/Application Support/Claude/claude_desktop_config.json (macOS) / %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "axon": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://axon.example.com/api/v1/mcp",
        "--header",
        "Authorization: Bearer ${AUTH_TOKEN}"
      ],
      "env": {
        "AUTH_TOKEN": "axon_mcp_<key_id>.<secret>"
      }
    }
  }
}

Pre-requisite: node ≥ 18 + npx. With a self-signed dev cert add "NODE_TLS_REJECT_UNAUTHORIZED": "0" to env (dev only; remove in production).

Claude Desktop's built-in custom-connector UI is OAuth-oriented; if your installed Desktop build cannot inject a static Authorization header, stay on mcp-remote until B7 OAuth/DCR ships.

IDE agents / custom MCP Python SDK clients

Any client that supports mcp.client.streamable_http.streamablehttp_client(...) with headers={"Authorization": "Bearer ..."} works without modification.

Issuing a key

Short path: Console → Settings → MCP API Keys → New key.

Plaintext is shown once on the create screen — save it now. Key rotation is revoke + create new; in-place rotation is invariant I-MCP-5 forbidden. Operational runbook lives at deploy/runbooks/mcp-api-key-lifecycle.md; incident-response runbook at mcp-key-compromised.md.

Canonical tool surface (21 tools)

Reads

Tool What Permission
axon.workflow.list List workflows in a project READ
axon.workflow.get One workflow by id READ
axon.run.get Run snapshot + step timeline READ
axon.run.history Recent runs in a project READ
axon.run.steps Step timeline scoped to a run READ
axon.approval.list List approvals READ
axon.approval.get One approval by id READ
axon.project.list Projects the key can see implicit (via scope)
axon.project.get One project READ
axon.health Health probe (no project scope) valid key only
axon.conversation.list List conversations READ
axon.conversation.get Conversation + recent messages READ
axon.case.list List cases (redacted projection) READ
axon.case.get One case by id (redacted projection) READ

Mutations

Tool Permission Notes
axon.workflow.create CREATE_WORKFLOW (MANAGER+) Inline definition or definition_id via payload
axon.workflow.start START_WORKFLOW (OPERATOR+) Downstream effects may be external_high
axon.workflow.pause PAUSE_WORKFLOW (MANAGER+)
axon.workflow.resume RESUME_WORKFLOW (MANAGER+)
axon.workflow.cancel CANCEL_WORKFLOW (MANAGER+) Destructive
axon.approval.decide APPROVE / REJECT_APPROVAL Verb approve/reject
axon.conversation.send_reply SEND_MESSAGE (OPERATOR+) Outbound reply through a durable command (external_low, policy/approval/outbox/audit). Not an alias for send_message — that one stays inbound-append. Phase 4 caveat: the final provider call lives in the send_conversation_reply activity, which is currently a stub shared with the Console-driven flow. The MCP envelope, policy gate, audit chain, and workflow signal all run end-to-end; real Telegram delivery is a follow-up worker extension and is not specific to MCP.

Excluded from MVP: axon.workflow.retry / .replan / .undo — their permissions live only on OWNER/ADMIN and are not exposed externally.

Idempotency

Every mutating tool requires an idempotency_key: str. Replays with the same key + payload return the original command_id. Replays with the same key but a different payload return an MCP-level error failure_code="idempotency_conflict" (CallToolResult isError=true) — this is not HTTP 409.

Error semantics

  • Protocol-level (auth, rate-limit, transport security) — HTTP 401/403/421/429.
  • Tool-level failures land as CallToolResult(isError=true, structuredContent.failure_code=...). Codes: validation_error, rbac_denied, version_conflict, idempotency_conflict, project_suspended, target_not_found, rate_limited, handler_reject, forbidden.

Best practices

  • Scope keys narrowly. Most automations need a single project_scopes=[<id>] key with role OPERATOR or READ_ONLY.
  • Restrict the tool set per key. If a key only needs axon.workflow.start and axon.workflow.get, set allowed_tool_names accordingly — this is the second RBAC plane.
  • Treat plaintext as a secret. Never paste it in chat history, UI fields or git. Revoke + rotate at the first sign of compromise.
  • Watch the dashboard. Console → Settings → MCP API Keys → click a row for derived telemetry: last_seen_at, requests_last_7d (success/denied split), top tools.

What MCP does NOT do

  • No long-running execution — agents stay external; the platform runs workflows on Temporal as before.
  • No AI calls — LiteLLM remains internal-only.
  • No streaming/notifications — request/response only; poll axon.run.get for run state.

Troubleshooting

Symptom Likely cause
HTTP 401 Missing/invalid Authorization: Bearer ... header.
HTTP 403 Origin not in AXON_MCP_ALLOWED_ORIGINS.
HTTP 421 Host not in AXON_MCP_ALLOWED_HOSTS.
HTTP 429 Per-key rate limit exceeded; Retry-After header included.
Locked out after a wrong-secret burst Redis transient lockout (≥10 failures / 5 min). Wait the window or revoke + rotate the key.
failure_code="version_conflict" expected_version stale — re-read via axon.*.get.

See also