Validate MCP input against policies
Validate an MCP request (query or command) against configured policies without executing it.
This endpoint enables external orchestrators (LangGraph, CrewAI, custom pipelines) to use AxonFlow as a policy gate while managing MCP connector execution themselves.
Policy Evaluation
The anchored decision engine (ADR-065) decides the request from the organization's active typed policy document: the system detectors (SQL injection, dangerous queries, PII) run over the statement and its parameters, and the engine's verdict is the response. Tenant dynamic policies no longer decide MCP requests (PRD v11 §1.2).
If the engine denies the request, the response returns allowed: false with a block_reason. A request the engine cannot decide is refused with 503; it is never let through (PRD v11 §1.7).
When to Use
Use check-input + check-output when your orchestrator manages MCP execution natively. Use /mcp/resources/query or /mcp/tools/execute when you want AxonFlow to handle both policy enforcement and connector execution.
Audit Logging
All check-input evaluations are logged to the mcp_query_audits table with operation: "check-input" for compliance tracking. Audit entries include parameters_hash (SHA-256) and parameter_count for forensic analysis.
Request
- Base URLhttps://agent.getaxonflow.com
- Authnone declared
- 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.
Optional per-request dedup token. When supplied on POST /api/v1/mcp/check-input, POST /api/v1/audit/tool-call, or POST /api/v1/hitl/queue, the platform caches the original response for 24h and returns it byte-for-byte on subsequent requests carrying the same key + same authenticated tenant + same endpoint.
Format: 1-256 chars, ^[A-Za-z0-9_.:\-/]+$. Workflow IDs from n8n, ADK, or generic SDKs all fall inside this set. A malformed key returns 400 before the handler runs.
Cache rules: 2xx + 4xx responses are cached; 5xx is NOT cached so the caller's retry can hit a fresh attempt. A cache hit returns the original response plus an Idempotent-Replayed: true response header.
Cross-tenant collisions are impossible: tenant_id participates in the primary key + an RLS policy on the storage table. Two tenants using the same key value get distinct rows.
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
Response
Input passed all policy checks
Changes
No recorded changes to this operation across all 1 revision of this API.