`stdio_call_tool` shipped never having been executed end to end — docs said so explicitly. Running it found a real defect: `stdio_client` and `ClientSession` are each an anyio task group, and anyio re-packages anything leaving one in a `BaseExceptionGroup`. Both errors the transport raises from inside the session (`mcp_tool_error`, `mcp_non_text_content`) therefore reached callers as exception groups, never as the `IngestError` the whole Door A path catches and switches on by `code`. No canned-tool test could see this: they never enter a task group. `_unwrap_ingest_error` recovers the owned error and re-raises it; anything unowned is re-raised untouched, so this narrows an exception group rather than blanket-catching. Duck-typed on `.exceptions` because `except*`/`ExceptionGroup` are 3.11+ and this project supports >=3.10. Verified against a REAL server subprocess (a local process costs no model tokens, so the repo's cost discipline is untouched; the contract tests still spawn nothing): `examples/ingest-golden-mcp/` + `tests/test_ingest_golden_mcp.py` — byte-identical golden extraction mirroring the http/sql goldens, plus the tool-error and missing-`server_ref` branches. Also recorded: a server on the ingest path must expose a NULL-ARGUMENT tool, so `datasource.build_mcp_server` cannot serve it (`retrieve_cost_docs(query)` has a required parameter, verified to return an error result). The two are separate seams by design. Load-bearing MEASURED, five mutations all RED: detach the unwrap · detach `initialize()` · make the error code generic · detach the `isError` branch · change one byte of the served body. 612 -> 615 tests. ruff + format + mypy clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WiY53sm8JFqk7NN75g5wRS
27 lines
1,013 B
Python
27 lines
1,013 B
Python
r"""Golden-case MCP stdio server — the counterpart `stdio_call_tool` is measured against.
|
|
|
|
Deliberately a NULL-ARGUMENT tool. The ingest transport carries its two coordinates in the URL
|
|
(`mcp+stdio://<server_ref>/<tool>`) and calls the tool with an empty argument dict, so a server
|
|
consumed by Door A must expose a tool that needs none. `datasource.build_mcp_server` does NOT
|
|
qualify — its `retrieve_cost_docs(query)` takes a required argument; it serves the agents'
|
|
retrieval path, not the ingest path.
|
|
|
|
The body is a fixed literal so the golden is byte-deterministic, and it carries a `|` and a `\`
|
|
to pin the §5 rule that an http-family body is rendered VERBATIM inside a fence, never escaped
|
|
into a markdown table.
|
|
"""
|
|
|
|
from mcp.server.fastmcp import FastMCP
|
|
|
|
server = FastMCP("portfolio-optimiser-golden")
|
|
|
|
_BODY = '{"service": "cost-docs", "state": "ready | partial", "path": "c:\\temp\\cache"}\n'
|
|
|
|
|
|
@server.tool()
|
|
def cost_docs() -> str:
|
|
return _BODY
|
|
|
|
|
|
if __name__ == "__main__":
|
|
server.run()
|