Skip to content

Add an MCP client for reactome-mcp, harvested from #127 and #137 - #212

Merged
adamjohnwright merged 2 commits into
mainfrom
feat/mcp-client
Sep 14, 2026
Merged

adamjohnwright merged 2 commits into
mainfrom
feat/mcp-client

Conversation

@adamjohnwright

Copy link
Copy Markdown
Contributor

@GovindhKishore's client, process manager and tool wrappers from #127 and #137, reworked onto a main that has moved 140 commits since they were written. #127 and #142 are closed; #137 stays open until this lands.

This is the piece that makes spec 007's analysis destination reachable at all.

The package is reactome_mcp, not mcp

The official MCP Python SDK is published on PyPI as mcp. There's no such dependency here today so nothing breaks now — but it's the obvious library to adopt, and a local package of that name shadows it. The failure would arrive as an import resolving to the wrong thing, not as an error.

Three protocol bugs fixed

No initialize handshake. The original went straight to tools/call. The server accepts that today — I verified it — because the SDK is lenient. But the protocol requires it, and depending on leniency means one SDK release breaks every call at once. Doing it properly also removed an arbitrary sleep(1) used to decide the server had started: a successful initialize is the readiness check.

Replies weren't matched to requests. The original returned the next line on stdout, whatever it was. A notification arriving between request and reply is read as the answer — and every later call is then one reply out of step, answering each question with the previous question's answer. Nothing raises. It just returns the wrong thing, plausibly.

Two tests pin it, and both fail against the original behaviour:

FAILED test_a_notification_is_not_mistaken_for_the_answer
FAILED test_a_reply_to_a_different_request_is_skipped

No serialisation. A lock now wraps each write/read pair. Two coroutines on one pipe is the same desync by another route.

Shapes are checked, not asserted

json.loads returns Any, and casting it to the hoped-for shape is exactly how reactome-mcp shipped ten formatters reading fields the API never returns. A non-object result, a non-list tool list and a malformed content block each raise with a message saying what arrived.

Five tools, not fifty-three

Deliberate, and a test pins the number so growing it is a decision. Every description is spent from the model's context before it answers anything, and a model choosing between 53 similarly-named tools chooses worse than one choosing between five. The five cover what the bundle can't do: live search, live pathway lookup, enrichment analysis, and the two metadata questions a snapshot can't answer.

One test asserts the analysis tool's description tells the model not to answer a gene list from retrieval — spec 007's edge case, where a retrieved answer looks like an analysis and isn't.

./bin/mcp-probe

  connected to reactome 1.4.0
  server exposes 53 tools
  chatbot wraps 5 of them
  reactome_database_info             ok   0.15s      64 chars
  reactome_species                   ok   0.39s    1988 chars
  reactome_search                    ok   0.13s    3974 chars
  reactome_get_pathway               ok   0.05s    2425 chars
  reactome_analyze_identifiers       ok   0.04s    2667 chars

Wrong path, missing node, stale build, Content Service down and a rejected handshake all look alike from inside the chatbot, and a question that quietly falls back to retrieval reports none of them. This separates them and exits non-zero, so it can gate a deployment.

Not wired into the graph

Deliberately. Routing belongs on the existing intent_classifier rather than in a second one (the reason #142 was closed), and that change deserves its own review — it also has to answer where the subprocess lifecycle lives in a Chainlit app. bin/mcp-probe means this is usable today rather than dormant.

Verification

  • 18 tests, no network; ruff, ruff format and mypy clean across all 95 files
  • full suite passes
  • verified live against reactome-mcp 1.4.0

🤖 Generated with Claude Code

adamjohnwright and others added 2 commits September 14, 2026 17:44
@GovindhKishore's client, process manager and tool wrappers, reworked onto a
main that has moved 140 commits since they were written.

**The package is `reactome_mcp`, not `mcp`.** The official MCP Python SDK is
published on PyPI as `mcp`. There is no such dependency here today, so nothing
breaks now -- but it is the obvious library to adopt, and a local package of
that name shadows it. The failure would arrive as an import resolving to the
wrong thing rather than as an error.

**Three protocol bugs fixed.**

The original never sent the `initialize` handshake. The server accepts that
today -- I checked -- because the SDK is lenient, but the protocol requires it,
and depending on leniency means one SDK release breaks every call at once.
Doing it properly also removed an arbitrary `sleep(1)` used to decide the
server had started: a successful initialize is the readiness check.

The original returned the next line on stdout, whatever it was. A notification
between request and reply would be read as the answer, and every later call
would be one reply out of step -- answering each question with the previous
question's answer. Nothing raises. Two tests pin it, and both fail against the
original behaviour.

A lock now serialises each write/read pair. Two coroutines on one pipe is the
same desync by another route.

**Shapes are checked, not asserted.** `json.loads` returns Any, and casting it
to the hoped-for shape is exactly how reactome-mcp shipped ten formatters that
read fields the API never returns.

**Five tools, not fifty-three, deliberately.** Every description is spent from
the model's context before it answers anything, and a model choosing between 53
similar names chooses worse than one choosing between five. The five cover what
the bundle cannot: live search, live pathway lookup, enrichment analysis, and
the two metadata questions a snapshot cannot answer. A test pins the number so
growing it is a decision.

**`./bin/mcp-probe` checks the integration end to end.** Wrong path, missing
node, stale build, Content Service down and a rejected handshake all look alike
from inside the chatbot, and a question that quietly falls back to retrieval
reports none of them.

Nothing is wired into the chat graph yet; that is spec 007, and it belongs on
the existing intent_classifier rather than in a second one. This is the piece
that makes the analysis destination reachable at all.

18 tests, no network. Verified live against reactome-mcp 1.4.0: handshake, 53
tools listed, all five wrappers answering in 0.04-0.39s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@adamjohnwright
adamjohnwright merged commit 218b119 into main Sep 14, 2026
10 checks passed
@adamjohnwright
adamjohnwright deleted the feat/mcp-client branch September 14, 2026 17:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant