Repository navigation
Slice 4d.4: namespaced MCP tool forwarder (DoD #3 dispatch half) - #294
Merged
Pal Lakatos-Toth (pallakatos) merged 1 commit intoMay 13, 2026
Merged
Conversation
Closes Slice 4 DoD #3 dispatch half: /mcp now forwards tool calls to upstream McpServer.spec.url instead of serving only the in-tree EchoDispatcher. Producer (controller): - McpServerMeta gains url + allowed_tools fields, written into meta.json alongside the OAuth metadata from Slice 4d.3. Consumer (router): - New mcp::forwarder module with RouterToolDispatcher implementing AsyncToolDispatcher. - Startup discovery POSTs tools/list to each upstream, filters through allowed_tools ("*" exposes all; explicit list selects named subset; empty fails closed), prefixes each tool with snake_case(server_name). - Servers with empty URL, empty allow-list, outbound OAuth requirement (deferred to 4d.5), unreachable upstream, or HTTP errors are recorded in skipped[] with operator-actionable reason and excluded from the catalog. Router still starts. - Dispatch splits name on first '.', looks up server, forwards tools/call to upstream URL. JSON-RPC errors collapse to is_error:true; 5xx surfaces as DispatchError::ExecutionFailed; bad names surface as DispatchError::UnknownTool. - McpRouteState gains with_tools() builder; build_mcp_router (now async) swaps the dispatcher when registry is non-empty. Scope: - Unauthenticated upstreams only. Outbound OAuth is Slice 4d.5 (requires per-server credential source). Servers requiring bearer credentials are deliberately skipped per §5 (no scaffolding — only ship the consumer we can drive end-to-end). - Single tools/list page (multi-page support deferred until a real upstream hits the cap). Test coverage: 15 new unit tests covering every skip reason, both allow-list semantics, namespaced dispatch (single + multi server), upstream JSON-RPC errors, HTTP 5xx, and unknown-tool branches. Backward compatibility: registries with no meta.json or no url fall through to the existing EchoDispatcher unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.
Summary
Closes the dispatch half of Slice 4 DoD #3 ("per-server JWKS + namespaced tools"). The OAuth half landed in PR #293 (Slice 4d.3); this PR wires the actual upstream forwarder so
/mcpno longer ships only the in-treeEchoDispatcher.When the registry advertises at least one
McpServerwith a usable upstream URL,RouterToolDispatcherdiscovers each upstream'stools/listat startup, filters throughspec.allowedTools, and exposes the surviving tools under a{server_snake_case}.{tool}namespace. Tool calls are then forwarded to the matching upstream URL.Wire contract — what the controller emits
meta.json(per-server ConfigMap) gains two fields alongside Slice 4d.3's OAuth metadata:urlMcpServer.spec.urlallowedToolsMcpServer.spec.allowedTools[\"*\"]exposes all)Wire contract — what the router consumes
`mcp::registry::DiscoveredMcpServerMeta` matches the producer shape, and the new `mcp::forwarder::RouterToolDispatcher`:
Out-of-scope (Slice 4d.5)
Test coverage (15 new unit tests)
Every skip reason is asserted with the expected substring (operator-actionable). The happy path runs against a real `axum::Router` mock upstream — full JSON-RPC `tools/list` → `tools/call` round-trip through the dispatcher.
Negative branches:
Multi-server dispatch is asserted with two distinct mock upstreams — each invocation lands at the correct URL.
Backward compatibility
Registries without `meta.json` files or with empty `url` fall through to the existing `EchoDispatcher`. The single-issuer `MCP_JWKS_PATH` legacy path remains as the third fallback.
Verification
Slice 4 DoD scoreboard after this PR
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com