MCP JSON-RPC endpoint
Single Streamable-HTTP endpoint for the built-in MCP server. Accepts MCP JSON-RPC 2.0 messages (initialize, tools/list, tools/call, …) and returns JSON-RPC responses. Governance tools exposed: check_policy, check_output, audit_tool_call, list_policies, get_policy_stats, explain_decision, plus Pro-tier tools.
Authentication: HTTP Basic org:license-key on every request. An Enterprise deployment MAY additionally send a per-user token in X-User-Token (ADR-043/044). It is optional unless the organization's require_user_token posture demands one (see BasicAuth).
A call held for approval (#4370, Enterprise). When the organization's active document requires a person's approval for a check_policy call, the tool answers allowed: false with a pending_approval object (see PendingApproval) and a block_reason that leads with approval_pending; the approval is queued in the portal's Approvals view. Once a person approves it, call check_policy again with the SAME arguments, naming the approval in the X-Axonflow-Approval-Id header or the approval_id argument: it answers allowed: true once and names the approval in approval_id. Any other outcome is refused with an ApprovalHoldReason code. Nothing is queued on the Community build, which keeps approval_required. An approval requirement that also binds the response (check_output) still refuses there with approval_required until the requirement is scoped to the request plane (#4371).
Governance segments no longer gate check_policy or check_output (they did from #3430, refusing with blocked_by: "segment_identity_unresolved" or "segment_resolution_failed"). Neither tool consults segments: each is decided by the anchored engine, which reads none (PRD v11 §1.1, §1.2), so a segment resolution that would fail changes nothing.
Two identity rules are narrower than an operator usually expects, and both are deliberate:
- X-User-Email does not substitute for a validated per-user token, even when AXONFLOW_TRUST_IDENTITY_HEADERS is on. Under the organization's require_user_token posture, a caller without a validated token is refused before the header can supply an identity. The header keeps its attribution role; the ADR-044 override writes answer the freeze from v11.0.0 (#4252).
- A shared synthetic identity never holds a per-user session override. ADR-044 overrides are scoped to (tenant, user, policy), so an override created under an identity many callers share would flip deny to allow for every one of them (#2896). create_override refuses such a session for its identity first (every other session is answered LEGACY_POLICY_WRITE_FROZEN from v11.0.0, #4252), and no override is offered to it; its audit rows still carry the identity. The census is IsSharedSyntheticIdentity in platform/shared/identity: the reserved axonflow.local and axonflow.internal addresses, the MCP client pseudo-identity prefix, and the Community-SaaS evaluator evaluator@try.getaxonflow.com; in Community mode the local-dev identity is exempt. Token minting validates only that the subject contains @, so a token for such an identity validates, and check_policy and check_output accept it like any other validated token.
The Community SaaS per-minute limit and daily quota are answered with HTTP 429, a Retry-After header, and the RateLimitEnvelope as JSON text in result.content[0].text, with result.isError: true (#4261; before it, the same result came with a 200). The per-minute limit answers limit_type: per_minute, window minute, resets_at one minute out and Retry-After: 60; the daily quota answers limit_type: daily_quota, window daily_utc, resetting at midnight UTC. A credential refused by the pre-credential per-minute limiter gets the same per-minute answer, with an empty tier because the tier is not resolved yet, instead of a 401. The tools/call tier gates (feature_pro_only, hitl_approvals_window) answer the same result shape with HTTP 403, X-Axonflow-Tier-Limit and X-Axonflow-Upgrade-URL, as the REST routes do (#4274; before it, a 200 with no headers). Only hitl_approvals_window has a reset time, so only it is sent with Retry-After: the seconds until its resets_at.
JSON-RPC error codes: -32001 is the one application code, an authentication failure, sent with HTTP 401 and a WWW-Authenticate: Basic challenge. The rest are the JSON-RPC 2.0 standard codes: -32700 (parse error) with HTTP 400, -32600 (invalid request) with HTTP 400, or 415 when the Content-Type is not application/json, and -32601 (method not found) and -32602 (invalid params) with a 200.
The JSON-RPC check_output tool result DOES include redacted_message — unlike the standalone REST /api/v1/mcp/check-output endpoint (#2870).
Request
- Base URLhttps://agent.getaxonflow.com
- Authnone declared. Required headers: Authorization
- TypeScriptA function with typed inputs. Its response is checked against the schema.
Headers
The approval a retry spends (#4370). A call held for a person's approval is answered a pending_approval naming an id; once a person approves it in the portal, the caller retries the SAME call naming that id here (or in the body's approval_id field, for a client that cannot set headers - a header and a field naming different ids are refused approval_not_found).
The retry is decided exactly as the first call was. Only when it is again held for approval, and the approval is approved by a person who is not the caller, before its expiry, unspent, and granted for this very call (the same input, tool, route, requester and requirement), does it pass - and the approval is spent: it admits exactly one call. A call that is allowed anyway spends nothing; no approval lifts a deny. Every other outcome is refused with one of the ApprovalHoldReason codes. Enterprise; the Community build has no approval queue and ignores the header.
WHAT "THE SAME CALL" MEANS. On the MCP routes: the connector, the tool, the operation, the statement and its parameters (and the row limit on /mcp/resources/query). On /api/v1/decide: the WHOLE request the engine decided - stage, target, query AND context. A retry that changes any of it is refused bound_input_changed: resend the same context, and carry per-request values (request ids, timestamps) in headers, never in context - the correlation id is a header.
OAuth2-style Basic authentication header. Format: Basic base64(clientId:clientSecret)
- clientId: Your organization identifier (required)
- clientSecret: Authentication credential (optional for community mode)
Not required when DEPLOYMENT_MODE=community.
base64url (padding optional) of the PEPHandshake document.
The ADR-065 PEP capability handshake: base64url of a compact JSON document in which an external enforcement point declares what it is and which obligations it can discharge. See PEPHandshake for the document.
Absent is the default and changes nothing. A caller that omits the header takes byte-for-byte the path it took before this header existed.
What an absent header means for a redaction depends on the plane, by design (PRD v11 section 1 item 16, #4257). The MCP passes discharge a redaction of the content they hand back: the request pass (check-input, check_policy) masks the statement, and a redaction that masks nothing in the statement is refused unsupported_obligation to every caller, and one that masks a request parameter to every caller that has not declared field_redact at version 2 (on Community, to every caller) (#4264); the response passes (check-output, the MCP server's check_output) mask the rows or the message. /api/v1/decide and the gateway pre-check return a decision rather than content, so a required redaction is a field_redact obligation for the enforcement point, and a caller that has not declared field_redact is refused unsupported_obligation. A caller that declares NO redaction (capabilities: []) is refused on each of these planes; on Community the MCP passes still return a checksum validator's masked content to it (reachable only through a directly inserted detection_action_overrides row) until #4122.
A header that is PRESENT and cannot be read is refused, never treated as absent: degrading a malformed declaration to "legacy caller" would go on handing an enforcement point obligations it had just said it cannot discharge. The refusal is 400 and its message names this header, which matters on /api/v1/access/evaluation where the refusal is rendered through that surface's existing incomplete_evaluation code and the message is the only thing distinguishing a malformed HEADER from a malformed body ENVELOPE.
Present more than once is refused: RFC 7230 permits an intermediary to join repeated field lines with a comma, and a comma is outside the base64 alphabet, so a joined pair can only decode to malformed. That is why the document is base64 rather than raw JSON, which would join into something a lenient parser might accept.
When a decision carries a mandatory obligation the declared set does not cover, the request is answered 200 with verdict: deny and the reason unsupported_obligation (ADR-065 invariant 8) - a decision about the request, not a transport error. That holds on every edition. An Enterprise deployment adds a second reason beginning pep_capability_unsupported naming the gap, and refuses a checksum validator's mask on the MCP passes; a Community deployment hands that masked content over (#4257 split 2, #4122).
Request body
Example request
{
"method": "tools/call"
}Response
JSON-RPC response (errors are carried in the JSON-RPC error member)
Changes
No recorded changes to this operation across all 1 revision of this API.