Skip to content

feat(mcp): connect Reactome MCP server to LangGraph agent with LangChain tool wrappers - #137

Closed
GovindhKishore wants to merge 2 commits into
reactome:mainfrom
GovindhKishore:feat/mcp-langchain-tools
Closed

GovindhKishore wants to merge 2 commits into
reactome:mainfrom
GovindhKishore:feat/mcp-langchain-tools

Conversation

@GovindhKishore

Copy link
Copy Markdown

Summary

Integrates the Reactome MCP server into the LangGraph agent by wrapping MCP tools as LangChain StructuredTool objects and connecting them to the React-to-Me chat profile. Builds directly on the MCP client foundation from #127.

Changes

New - src/mcp/mcp_tools.py

  • create_mcp_tools() async factory that starts the MCP server and returns LangChain tool wrappers
  • Five tools covering the most impactful MCP capabilities:
    • search_reactome - search pathways, proteins, genes by keyword
    • get_pathway - retrieve pathway details by stable ID (e.g. R-HSA-109582)
    • analyze_identifiers - run pathway enrichment analysis on a gene/protein list
    • get_database_info - get current Reactome release version
    • get_species - list all species available in Reactome
  • Returns ([], None) when no server path provided - chatbot works unchanged without MCP

Modified - src/agent/graph.py

  • MCP server started in initialize() controlled by MCP_SERVER_PATH environment variable
  • Graphs built individually per profile - React-to-Me receives mcp_tools, Cross-Database unaffected
  • MCP server stopped cleanly in close_pool() on app shutdown
  • llm, embedding, profiles stored as instance variables so initialize() can access them

Modified - src/agent/profiles/react_to_me.py

  • create_reactome_graph() and ReactToMeGraphBuilder accept optional mcp_tools parameter
  • Tools bound to LLM once at init time via bind_tools() - not re-bound on every message
  • call_model implements tool-calling loop with 15 iteration guard against infinite loops
  • Falls back gracefully to existing reactome_rag behaviour when mcp_tools is empty

How It Works

User message arrives
-> LLM receives message + tool schemas via bind_tools()
-> LLM decides: answer directly or call an MCP tool
-> if tool call requested -> LangChain executes matching @tool function
-> MCPClient sends JSON-RPC to Node.js reactome-mcp server
-> Node.js calls Reactome REST API and returns result
-> result sent back to LLM as ToolMessage
-> LLM generates final answer using tool result
-> loop repeats if LLM requests another tool, up to 15 iterations max
-> if no tools available -> existing reactome_rag behaviour unchanged

Usage

Set MCP_SERVER_PATH to the compiled MCP server entry point:

MCP_SERVER_PATH=/path/to/reactome-mcp/dist/index.js

Leave unset to run in existing RAG-only mode with zero behaviour change.

Relation to Previous Work

AI Transparency

AI Tools (Claude) were used to assist with drafting code and documentation. All changes were reviewed and verified by me.

closes #136

@adamjohnwright

Copy link
Copy Markdown
Contributor

This is the MCP PR we intend to take. #127 is closed as contained in it, and #142 as adding a second classifier beside the existing one. Sorry it has been quiet for six months.

What is good here: separating the process manager from the client from the tool wrappers is the right shape. It is what makes the transport swappable, which matters more now than when you wrote it — reactome-mcp gained a Streamable HTTP transport today, so a later version of this can talk to a hosted instance instead of spawning one, and only mcp_process_manager.py needs to know.

Three things before it can land:

1. The package name src/mcp/ will collide. The official MCP Python SDK is published on PyPI as mcp. There is no such dependency in this repo today so nothing breaks now, but it is the obvious library to adopt, and the day someone adds it from mcp.mcp_client import ... becomes ambiguous. Wants renaming — src/reactome_mcp/ or similar.

2. Five tools out of 53 is a choice worth making explicitly. search_reactome, get_pathway, analyze_identifiers, get_database_info, get_species is a defensible curated surface — handing a model all 53 is its own problem. But it should read as a decision with a comment saying why those five, rather than as the set that happened to be needed that week.

3. main has moved 140 commits since this branch's base, including the retriever rewrite and the LangChain 1.x migration, so this needs reworking rather than a straight rebase.

Where it fits: specs/007-answer-cascade describes routing analysis questions to an analysis agent, with retrieval, live Reactome search and Tavily behind it. This PR is the substrate for that branch — it is what makes the analysis destination reachable at all. Worth reading that spec before reworking, because the routing belongs on the existing intent_classifier rather than in a new one (which is the note on #142).

Thank you for this — it is the piece the design is being built on.

adamjohnwright added a commit that referenced this pull request Sep 14, 2026
Add an MCP client for reactome-mcp, harvested from #127 and #137
@adamjohnwright

Copy link
Copy Markdown
Contributor

Landed — in pieces, over #212, #216, #218 and #219, with credit in each commit. Closing this as superseded rather than unmerged.

What came from here

The three-layer shape — process manager, client, tool wrappers — is what src/reactome_mcp/ is. Keeping the transport behind the process manager is what made it possible to add a second one later without touching the tools or the graph, which turned out to matter more than either of us expected.

The five curated tools. search, get_pathway, analyze_identifiers, database_info, species — kept exactly, now with a test pinning the number so growing it is a decision rather than drift.

What changed on the way

The package is reactome_mcp, not mcp — the official MCP Python SDK is published on PyPI as mcp, and a local package of that name shadows it. Nothing breaks until someone adds the dependency, and then it breaks confusingly.

Three protocol bugs, none of which fail loudly:

  • no initialize handshake — the server accepts that today because the SDK is lenient, but the protocol requires it
  • replies weren't matched to requests by id, so a notification arriving mid-flight would be read as the answer and every later call would be one reply out of step
  • no lock, so two coroutines could interleave on one pipe

A second transport. Your version spawns node over stdio. That works on a developer's machine and cannot work where the chatbot runs — the deployed image is Python, has no node, and doesn't mount reactome-mcp. REACTOME_MCP_URL uses Streamable HTTP instead, and the stdio path you wrote stays for local development.

Routing extends the existing classifier rather than adding a second one — the reason #142 was closed. It gained a live destination, offered only when an MCP is actually reachable.

What it does now

Live on beta as of today:

Q: what species are in reactome
A: Reactome includes pathway data for a total of 96 species...

Before this, that question was answered from the vector store: "primarily Homo sapiens... no indications of other species being included." Reactome has 96. The corpus is a sample of the content and the question is about the scope, so no amount of better retrieval was ever going to fix it — it needed exactly what you built.

Thank you for this, and sorry it sat for six months before anyone told you it was the right shape.

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.

feat: integrate Reactome MCP tools into LangGraph agent via LangChain tool wrappers

2 participants