8.9 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | ||
|---|---|---|---|---|---|---|---|---|---|
| http/review-mcp | Review MCP adapters for ADR-037/041 conformance (streamable HTTP, 4-tool gateway, output handling) | completed |
|
moderate | low | phase | review |
Description
Review the MCP adapters (from_mcp, to_mcp) for spec conformance,
pattern consistency, and correctness. This is the quality checkpoint
for the feature-gated MCP work.
Review Checklist
-
from_mcpconformance (http-mcp.md):FromMCPstruct withendpoint,auth_token,namespaceOperationAdapterimpl (async fn import)- Connects via rmcp
StreamableHttpClientTransport::from_uri(endpoint) - Connection failure →
AdapterError::DiscoveryFailed - 401 →
AdapterError::Unauthorized - Calls
tools/list→ MCP tools - For each tool:
HandlerRegistrationwithFromMCPprovenance spec.name= tool name (ornamespace/tool_name)spec.op_type=Mutation(MCP tools are call/response)spec.visibility=Internal(ADR-015)spec.input_schema= tool'sinputSchemaspec.output_schema= declaredoutputSchemaif present, elseContentBlockunionprovenance=FromMCP,composition_authority: None,scoped_env: None(ADR-022)capabilities= bearer token (injected at registration)
-
from_mcpoutput handling (http-mcp.md §"Output handling"):structured_contentpresent → use as result (validated againstoutput_schema)structured_contentabsent → mapcontent: Vec<ContentBlock>toContentBlockunion- No heuristic
JSON.parseof text blocks (carry asContentBlock) isError: true→CallError(not the output handling path)- rmcp client connection maintained for registration lifetime
-
from_mcpno-env-vars (ADR-014):- Handler reads
context.capabilities, neverstd::env::var - Bearer token from
Capabilities, not env vars
- Handler reads
-
to_mcpconformance (http-mcp.md, ADR-041):- Implements rmcp
ServerHandlertrait (call_tool,list_tools) tools/listreturns 4 fixed gateway tools (search,schema,call,batch)tools/listdoes NOT return registry's operations (discovered viasearch)search→ dispatchesservices/listviaGatewayDispatch::invokesearchresults areAccessControl::check(identity)-filteredSubscriptionops filtered out ofsearchresults (ADR-041 §2)schema→ dispatchesservices/schemaviaGatewayDispatch::invokecall→ dispatches viaGatewayDispatch::invoke(shared spine)callresult →CallToolResult::structured(value)forOkcallerror →CallToolResult::structured_error(details)forErr(CallError)batch→ loop overinvoke, returns arrayto_mcpis a pure projection (consumes registry, does not produce entries)
- Implements rmcp
-
to_mcpauth (http-mcp.md, research §4.4):- Bearer auth via shared
bearer_auth_middleware(applied aroundnest_service) Identityread fromRequestContext.extensionsinsidecall_tool- Identity survives rmcp framing (research §6 #2 — confirmed by spike)
- MCP client has no
PeerId(not an alknet peer, ADR-034 §4) AccessControlgatessearchresults andcalldispatch
- Bearer auth via shared
-
to_mcprmcp integration (http-mcp.md, research §4):StreamableHttpServicenested into axumRouterat/mcpServerHandlerimpl is a gateway service (4 fixed tools)- rmcp owns JSON-RPC framing, session management, SSE priming
to_mcpownscall_tool/list_toolsdispatch +CallToolResultmapping
-
Streamable HTTP only (ADR-037):
- stdio transport NOT built (not a dependency, not optional, not behind a feature)
mcpfeature pulls in rmcp with streamable HTTP transport features onlytransport-child-processis not a dependency
-
Feature gate isolation:
from_mcp/to_mcpcompile only withmcpfeaturecargo check -p alknet-http(nomcp) succeeds — MCP code not compiledcargo check -p alknet-http --features mcpsucceeds
-
Shared dispatch spine (research §5.1):
to_mcpcallusesGatewayDispatch::invoke(same spine asto_openapi)ResponseEnvelope→CallToolResultmapping isto_mcp-specific- No
GatewayDispatchtrait (concrete struct) - Identity resolution, context construction, invoke are shared (security axis)
- Wire framing, discovery, streaming are per-gateway (presentation axis)
-
Error fidelity (ADR-023):
from_mcperror_schemas from MCP tool error descriptionsto_mcpcallerror →CallToolResult::structured_errorwith typeddetails- No collision with protocol-level codes
-
Security constraints:
- No-env-vars invariant (from_mcp reads context.capabilities, ADR-014)
- MCP clients are not peers (no PeerId, ADR-034 §4)
- AccessControl gates search results and call dispatch
- No secret material in CallToolResult
-
Test coverage:
- Unit tests for from_mcp (import, outputSchema present/absent, isError)
- Unit tests for to_mcp (tools/list returns 4, search/schema/call/batch)
- Integration test: MCP client → search → schema → call round-trip
- Integration test: Bearer auth gates to_mcp service
- Integration test: no-env-vars (no std::env::var in from_mcp)
- Feature gate: cargo check without mcp succeeds
Acceptance Criteria
- from_mcp matches http-mcp.md (OperationAdapter, tools/list, outputSchema handling)
- from_mcp output handling: structuredContent-preferred, ContentBlock fallback, no JSON.parse
- from_mcp no-env-vars: reads context.capabilities, never std::env::var
- to_mcp matches ADR-041 (4-tool gateway, not one tool per operation)
- to_mcp Subscription excluded from search results
- to_mcp uses GatewayDispatch::invoke (shared spine)
- to_mcp CallToolResult mapping correct (structured/structured_error)
- to_mcp auth: shared middleware, Identity survives rmcp framing
- Streamable HTTP only (ADR-037 — stdio NOT built)
- Feature gate isolation (mcp code not compiled without mcp feature)
- No GatewayDispatch trait (concrete struct, research recommendation)
- Error fidelity (ADR-023 — no collision with protocol codes)
- MCP clients are not peers (no PeerId, ADR-034 §4)
- AccessControl gates search and call
- No secret material in CallToolResult
- Test coverage adequate for all MCP functionality
cargo fmt --check -p alknet-http --features mcppassescargo clippy -p alknet-http --features mcppasses with no warningscargo test -p alknet-http --features mcppassescargo check -p alknet-http(no mcp) succeeds
References
- docs/architecture/crates/http/http-mcp.md — full MCP spec
- docs/research/alknet-http-gateway-factoring/findings.md — §4 (rmcp constraints), §4.4 (auth sharing)
- docs/architecture/decisions/037-mcp-stdio-transport-exclusion.md — ADR-037
- docs/architecture/decisions/041-mcp-tool-gateway-pattern.md — ADR-041
- docs/architecture/decisions/017-call-protocol-client-and-adapter-contract.md — ADR-017 §5
- docs/architecture/decisions/023-operation-error-schemas.md — ADR-023
- docs/architecture/decisions/014-secret-material-flow-and-capability-injection.md — ADR-014
- docs/architecture/decisions/034-outgoing-only-x509-and-three-peer-roles.md — ADR-034 §4
- /workspace/rust-sdk/ — rmcp SDK (ServerHandler, StreamableHttpService, CallToolResult)
Notes
The MCP adapters are feature-gated and the most SDK-coupled part of the crate (rmcp integration). The review should verify: (1) streamable HTTP only (no stdio, ADR-037), (2) the 4-tool gateway pattern (ADR-041, not one tool per operation), (3) the output handling (structuredContent-preferred, ContentBlock fallback, no JSON.parse), (4) the shared dispatch spine is used for to_mcp's call (same spine as to_openapi), (5) the auth middleware is shared (Identity survives rmcp framing — research §6 #2), (6) the no-env-vars invariant holds, (7) feature gate isolation (MCP code not compiled without mcp). If deviations are found, document and fix before considering the MCP adapters complete.
Summary
MCP adapters reviewed against all 12 checklist items. All conformance criteria pass: from_mcp (OperationAdapter, tools/list, structuredContent-preferred output handling, no JSON.parse, isError→CallError, no-env-vars ADR-014), to_mcp (4-tool gateway ADR-041, ServerHandler, Subscription excluded, GatewayDispatch::invoke shared spine, CallToolResult mapping, Identity survives rmcp framing research §6 #2, no PeerId ADR-034 §4), streamable HTTP only (ADR-037), feature gate isolation, GatewayDispatch concrete struct (not trait), error fidelity (ADR-023), test coverage (223 mcp tests + 5 integration tests). One formatting fix applied to from_mcp/mod.rs. cargo fmt --check, clippy --features mcp --all-targets, test --features mcp, and cargo check (no mcp) all pass.