feat(mcp): route stateless tools/call through static broker catalog#340
feat(mcp): route stateless tools/call through static broker catalog#340nerdalert wants to merge 1 commit into
Conversation
2da7dee to
9a92fb9
Compare
da7e51c to
0802158
Compare
192a2e8 to
a7ef731
Compare
praxis-bot
left a comment
There was a problem hiding this comment.
Review: feat(mcp): route stateless tools/call through static broker catalog
Clean, well-structured PR. The implementation correctly routes stateless tools/call requests through the broker catalog with proper name rewriting, cluster selection, path rewriting, and Mcp-Name header repair.
Correctness
- Routing flow is sound:
extract_tool_namevalidates params, catalog lookup selects the backend, body is mutated with the original (un-prefixed) tool name, andMcp-Nameheader is rewritten for the upstream. - Error ordering is deliberate and correct:
-32602(invalid params) fires before-32020(header mismatch), so a malformedtools/callbody is rejected before header validation runs. The teststateless_header_body_mismatch_still_returns_32020confirms the inverse case works too. encode_mcp_namecorrectly handles both ASCII and non-ASCII tool names, falling back to the=?base64?...?=sentinel format. The triple guard (is_ascii + no control chars + HeaderValue::from_str) is defense-in-depth.- Content-Length repair is handled by the framework (praxis-proxy/praxis#760) after body mutation; the integration test
mcp_stateless_tools_call_backend_sees_repaired_content_lengthverifies this end-to-end with a cross-check between echo and header-echo backends. - Current profile remains unchanged:
tools/callstill returns-32601.
Test Coverage
- Unit tests (330 new lines): known tool routing, path rewriting, prefix stripping, Mcp-Name rewriting, argument preservation,
encode_mcp_name(ASCII + non-ASCII), second server routing, unknown tool rejection, missing/null/array params, missing/non-string name, header-body mismatch ordering. - Integration tests (257 new lines): weather and calendar backend routing (verifying cross-isolation), path rewriting via URI-echo backend, Mcp-Name header at backend via header-echo, unknown tool 400, malformed params 400, Content-Length repair.
- Example config test added for the stateless broker
tools/callpath. - Edge cases are well-covered: null params, array params, non-string name, missing name, unknown tool, header/body mismatch ordering.
Conventions & Style
#[expect]attributes are properly justified.CatalogToolvisibility widened frompub(super)topub(crate)anddead_codeexpect removed -- both correct now that the struct fields are used for routing.base64::Engine as _import hoisted to module scope since it is now needed by bothencode_mcp_nameanddecode_mcp_name.- Field reordering in
apis/src/lib.rsandfilters/src/lib.rstest utilities follows alphabetical convention. - Doc updates in
docs/filters/mcp.mdand the example config accurately reflect the new behavior.
Security
- Tool names are validated via
extract_tool_nameand looked up against the pre-built catalog (trusted list), so injection via crafted tool names is not possible. - Body mutation uses
serde_json(safe serialization), and the mutated body replaces the original atomically. - Non-ASCII tool names are base64-encoded for header safety.
| Category | Verdict |
|---|---|
| Correctness | Pass |
| Test coverage | Pass |
| Conventions | Pass |
| Security | Pass |
No issues at Critical, Large, or Medium severity. Ship it.
a7ef731 to
4ef1b2b
Compare
aslakknutsen
left a comment
There was a problem hiding this comment.
docs/architecture/agentic-protocols.md diagram line 95 still reads "tools/call: not yet supported (returns -32601)" for the broker path. Filter docs were updated but this architecture page contradicts merged behavior and will mislead operators.
4ef1b2b to
0ba0843
Compare
Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
0ba0843 to
dc80d0a
Compare
@aslakknutsen ty!
|
Summary
Adds backend forwarding for stateless MCP
tools/callrequests using the static broker catalog.The stateless broker can now receive a
tools/call, validate the stateless MCP headers/body, look up the exposed tool name, select the configured backend cluster, rewrite the backend path, and forward the call with the backend tool name. This turns the static catalog from discovery-only into a working broker path for configured tools.With this PR, the catalog becomes the routing table for stateless
tools/call:weather_get_forecast.params.namefrom the exposed name to the backend tool name, forexample
get_forecast.Mcp-Nameheader so the backend sees a consistent MCP request.The temporary Praxis dependency pin is needed because this forwarding happens after the request body has been inspected and rewritten. The MCP filter sets the rewritten
Mcp-Nameheader during the body-processing phase, andpraxis-proxy/praxis#760is the core fix that preserves those body-phase header mutations before the request is sent upstream.Changes
tools/callby static catalog entry.params.namefrom exposed name to backend/original tool name.params.argumentsunchanged.Mcp-Nameheader to the backend tool name.=?base64?...?=sentinel format.Content-Lengthafter request body mutation.-32602 Invalid Paramsfor malformedtools/callparams before header mismatch validation.current(2025-03-26) compatibility-profiletools/callbehavior unchanged.Dependency Note
This draft temporarily pins
praxis-proxy-*crates topraxis-proxy/praxiscommit2bbce4386e84f6c8a8a0e64c7cc47b52f6d34cf9because the latest crates.io release,0.4.0, predatespraxis-proxy/praxis#760.#760is required for body-phase header mutations from StreamBuffer filters to reach upstream backends, which allows routed MCPtools/callrequests to forward the rewrittenMcp-Nameheader.This pin should be replaced with the next published
praxis-proxy-*crate version once available.Protocol Boundary
This PR completes base stateless
tools/callrouting.The following are intentionally left to separate focused PRs:
pingremovallogging/setLeveland legacy notification conformance cleanupx-mcp-header/Mcp-Param-*supportsubscriptions/listeninput_requiredValidation
cargo test -p praxis-ai-filters mcp— 265 passedcargo test -p praxis-tests-integration --test suite mcp_broker— 30 passedcargo test -p praxis-tests-schema— 90 passedcargo clippy --workspace --all-targets -- -D warningscargo +nightly fmt --all --checkcargo xtask lint-filter-docscargo xtask lint-example-testscargo xtask sync-example-readmegit diff --checkRefs #148