portfolio-optimiser/examples/ingest-golden-mcp
Kjell Tore Guttormsen 9dc3722161 fix(ingest): run the MCP stdio transport against a real server, and repair its error contract (kø-x)
`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
2026-08-03 17:56:19 +02:00
..
expected-bundle fix(ingest): run the MCP stdio transport against a real server, and repair its error contract (kø-x) 2026-08-03 17:56:19 +02:00
ingested-at.txt fix(ingest): run the MCP stdio transport against a real server, and repair its error contract (kø-x) 2026-08-03 17:56:19 +02:00
manifest.json fix(ingest): run the MCP stdio transport against a real server, and repair its error contract (kø-x) 2026-08-03 17:56:19 +02:00
server.py fix(ingest): run the MCP stdio transport against a real server, and repair its error contract (kø-x) 2026-08-03 17:56:19 +02:00