--- id: adapter-mcp name: from_mcp + to_mcp (mcp feature) status: completed depends_on: [client-http-host, gateway-routes] scope: broad risk: medium impact: component level: implementation tags: [adapters, mcp, phase-3] --- ## Description Port both MCP adapters from `/workspace/@alkdev/alknet/crates/alknet-http/src/adapters/{from_mcp/`, `to_mcp.rs}` behind the `mcp` feature (rmcp, streamable HTTP only — ADR-037). from_mcp: tools/list discovery, per-tool HandlerRegistration (Mutation, HandlerKind::Once, FromMCP leaf), structuredContent-preferred output handling, ContentBlock-union fallback, isError→CallError. to_mcp: 4 fixed gateway tools (search/schema/call/batch — Sub AND Pub excluded), StreamableHttpService into axum under /mcp with bearer middleware. Shared dispatch spine with to_openapi via GatewayDispatch. ## Acceptance Criteria - [x] Both adapters behind `mcp`; default features compile without rmcp - [x] from_mcp round-trip against a mock MCP server (tools/list, tools/call, structured + content-block outputs) - [x] to_mcp: 4 tools listed; call dispatch filtered by ACL; Pub/Sub ops excluded - [x] stdio transport not present anywhere (dependency audit) - [x] `cargo test --all-features` passes ## References - docs/architecture/http-mcp.md - docs/architecture/decisions/037-mcp-stdio-transport-exclusion.md, 041-mcp-tool-gateway-pattern.md - Old source: `/workspace/@alkdev/alknet/crates/alknet-http/src/adapters/from_mcp/`, `to_mcp.rs`, `tests/from_mcp_integration.rs` ## Notes Port split internally as from_mcp/ (dir: mod.rs + tests.rs) + to_mcp.rs, mirroring the old crate layout. Streamable HTTP only (rmcp 1.8, default-features off; client + server + streamable-http transport features only — no stdio anywhere in the feature set). ## Summary Ported both MCP adapters behind the `mcp` feature (rmcp 1.8): - `src/adapters/from_mcp/` (mod.rs + tests.rs): tools/list discovery over StreamableHttpClientTransport; per-tool HandlerRegistration (Mutation, HandlerKind::Once, FromMCP leaf, Internal, ADR-015/022); structuredContent-preferred output, ContentBlock-union fallback schema (5 variants), isError -> MCP_TOOL_ERROR CallError with content blocks as details; bearer token via capabilities key "mcp" (ADR-014). 19 unit tests + 5 integration tests vs a real rmcp streamable-HTTP server (tests/from_mcp_integration.rs, gated on the mcp feature): tools round-trip incl. structured + legacy content-block outputs, no-env-vars, unreachable-server error classification. - `src/adapters/to_mcp.rs`: ToMcpGateway with exactly 4 gateway tools (search/schema/call/batch — ADR-041), Sub ops excluded from search listings (op_type wire strings "sub"/"subscription"/"Sub"), call dispatch through the shared GatewayDispatch spine with ACL-filtered FORBIDDEN/NOT_FOUND mapping, batch returns per-call result array. Identity survives rmcp framing: bearer_auth_middleware stashes Option in http::request::Parts extensions; extract_identity reads it back from RequestContext extensions. StreamableHttpService nested at /mcp in build_router (feature-gated) with bearer middleware around it. 19 tests. - `GET|POST /mcp` reserved path unchanged; default build compiles without rmcp (cargo tree -e normal: 0 rmcp occurrences). 218 lib tests (default 182 / all-features 218) + 5 MCP integration + 10 WS integration green. clippy -D warnings both feature sets, fmt.