--- id: review-001-mcp-tool-fidelity name: MCP tool-gateway runtime fidelity (PRJ-07, PRJ-08, PRJ-09, PRJ-10, PRJ-13) status: completed depends_on: [review-001-schema-internal-visibility] scope: narrow risk: low impact: component level: implementation tags: [adapters, review-001, mcp] --- ## Description Review 001 MCP projection findings in `src/adapters/to_mcp.rs` (the schema-ACL half of the MCP story is review-001-schema-internal-visibility; this task covers the rest of the tool surface): - **PRJ-07**: the `search` tool advertises an optional substring `query` filter but `call_tool` drops `arguments` entirely (`:382`) — an LLM passing `{"query": "fs"}` gets the full unfiltered listing (context waste/overflow in big registries). Honor the filter. - **PRJ-08**: `search` excludes only `Sub`, not `Pub` (`:268-274`), contradicting ADR-068 ("excludes both"). A discovered Pub op can never be invoked via the `call` tool — the advertised discovery surface is a lie for the whole Pub class. (The `"subscription"`/`"Sub"` match arms are dead — `op_type_str` only emits lowercase.) - **PRJ-09**: batch item shape `{"isError", "output"|"error"}` contradicts the tool description ("each shaped like a `call` result") — fix the description or the shape. - **PRJ-10**: `structuredContent` passes output through verbatim, so string/array/null outputs produce non-object `structuredContent` (strict MCP clients may reject; batch returns a top-level array). Wrap or document. - **PRJ-13**: hand-rolled argument errors omit `retryable` (required by the OpenAPI error schemas, always present on `CallError`), and a non-string `operation` reports the misleading "missing required field". Build argument errors in the `CallError` wire shape. ## Acceptance Criteria - [x] `search` respects a `query` argument (test); `Pub` ops excluded from results (test) - [x] Batch item doc matches the emitted shape (or shape changed to match — one way or the other, tested) - [x] Non-object outputs wrapped into objects or documented as an exception; batch shape consistent - [x] MCP argument errors carry `retryable` and truthful messages - [x] `cargo test --all-features` passes ## References - docs/reviews/001-initial-implementation-review.md (Part F, PRJ-07..PRJ-10, PRJ-13) - docs/architecture/decisions/041-mcp-tool-gateway-pattern.md - docs/architecture/decisions/068-gateway-publish-endpoint.md ## Notes > Agent fills during implementation. Depends on the visibility task > because both rework the `schema` tool in `to_mcp.rs`. ## Summary All five findings fixed in `src/adapters/to_mcp.rs`: - **PRJ-07**: `call_tool` extracts the optional `query` argument and `handle_search` filters the listing by substring match on the operation name (after the visibility/ACL filter — the query narrows what the caller is allowed to see, never widens it). - **PRJ-08**: exclusion now covers both `"sub"` and `"pub"` (ADR-068 §1 and ADR-041 §2); the dead `"subscription"`/`"Sub"` match arms are removed. A `search_excludes_pub_ops` test asserts the Pub op is invisible to MCP discovery. - **PRJ-09**: shape decided over doc — `batch` now returns `{"results": [...]}` where each entry is `{"isError": bool, "output": ...}` on success or `{"isError": true, "error": }` on failure; the tool description states exactly that (multiple dispatch-path failures in one batch let the other items run to completion, so the old "shaped like a `call` result" description was the wrong side to keep). - **PRJ-10**: module doc documents the object guarantee: object outputs pass through verbatim, non-object outputs are wrapped as `{"result": }` (`object_result`), applied consistently to `call`'s `structuredContent` and to batch item `output` — so every `structuredContent` is a JSON object and batch is never a top-level array. - **PRJ-13**: all argument errors (`call`, `batch`, `schema`, and the batch per-item errors) are `CallError` values serialized through the same `call_error_to_structured_error` path, so `retryable` is always present. `parse_call_arguments` now distinguishes a missing `operation` ("missing required field") from a non-string one ("must be a string, got "). Tests: `search_honors_query_substring_filter`, `search_excludes_pub_ops`, `call_wraps_non_object_output_into_object_ structured_content`, `call_argument_errors_carry_retryable_and_truthy_ messages`, `batch_returns_object_with_result_entries` (retryable + INVALID_INPUT per-item check); existing tests updated to the new shapes.