feat(mcp): cover every armed agentic-security and prompt surface - #1
Merged
Merged
Conversation
The MCP resource was written against invented request shapes: run_step sent
{agent_id, tool, server, args} where the gateway reads {agent_id, run_id,
cost_micro_usd}; inspect_result sent {result: {...}} where the gateway reads
{content: "..."}; the HITL legs sent {decision_id, reason} where the gateway
reads {id, note}; and list_runs treated a {"runs": [...]} envelope as a bare
array. None of those calls could have worked against a live gateway.
Correct all of them against the handlers in mcp_api.rs, and add the six routes
that were missing entirely: sampling/evaluate, security/events, receipt/issue,
receipt/verify, anomaly/status, anomaly/clear.
Policy verdicts are now values rather than exceptions. The gateway is
default-deny, so a 422 deny is the single most common outcome on this surface;
raising HTTPStatusError for it made the normal path the error path. authorize,
inspect, sampling, and run_step return a typed Decision/RunStep carrying the
outcome, reason, status, and — on a 429 quota refusal — the backoff envelope.
Both parsers fail closed: anything that is not an explicit allow/continue reads
as a deny/stop. A 404 still raises, since not-entitled is not a verdict.
Prompts gains the missing `missing` policy and `cohort` arguments, and passes
arbitrary chat overrides through to the completions body. `provider` moves from
the body to x-routeplane-provider, where the gateway actually reads it — the
completions body is flattened into a chat request, which ignores an unknown
provider key, so the old argument was a silent no-op.
This was referenced Jul 27, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The prod gateway's agentic-security and prompt surfaces went live, so the SDK needs to actually reach them. It didn't.
The MCP resource was written against invented shapes
Every one of these was verified against the handlers in
crates/routeplane/src/mcp_api.rs:run_step{agent_id, tool, server, args}{agent_id, run_id, cost_micro_usd}inspect_result{result: {...}}{content: "..."}hitl.approve/.deny{decision_id, reason}{id, note}authorize_tool_call{agent_id, tool, server}argument_urls,arguments,server_manifest,run_idlist_runs{"runs": [...]}envelopeNone of these calls could have succeeded against a live gateway. All corrected.
Six routes were missing entirely
sampling/evaluate,security/events,receipt/issue,receipt/verify,anomaly/status/{id},anomaly/clear— added, grouped intoreceiptsandanomalysub-namespaces mirroring the URL structure, alongside the existinghitl.Denials are now values, not exceptions
The gateway is default-deny, so a structured
422 {outcome, reason}is the most common outcome on this surface. The shared_postcalledraise_for_status()unconditionally, which turned the normal path into the error path.authorize_tool_call,inspect_result,sampling_evaluate, andrun_stepnow return a typedDecision/RunStepcovering both outcomes, including theretry_after_ms/limit/window_msbackoff envelope a 429 quota refusal carries. Both parsers fail closed — anything that is not an explicitallow/continuereads as a deny / stop, so a malformed body can never be mistaken for permission.A 404 still raises: not-entitled is not a verdict, and these routes 404 rather than 403 precisely so an un-entitled tenant never learns the surface exists. Documented in the module docstring and the README, since it is a confusing failure mode otherwise.
_base._post/_gettake an opt-inexpecttuple of non-raising statuses; every other resource is unchanged.Prompts
Adds the
missingpolicy (error/empty) andcohortarguments, and passes arbitrary chat overrides (temperature,max_tokens,user, …) through to the completions body, which the gateway flattens into the chat request.Fixes
provider: it was going into the request body, where the flattened chat request ignores unknown keys — so it silently did nothing. It now travels asx-routeplane-provider, where the gateway reads it. Regression-tested.Verification
Ran exactly as CI does —
ruff check,ruff format --check,mypy(strict),pytest: 76 passed, no lint or type findings.Test coverage added for the corrected wire shapes, both deny paths, the quota backoff envelope, fail-closed parsing of an empty body, 404-still-raises, envelope unwrapping, and the
provider-as-header regression.Also adds
examples/agentic_security.pywalking a mediated tool loop end to end.