Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .specify/memory/constitution.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,24 @@ shipped.
### IV. Reactome data, through the public services

Tools read the Content Service and the Analysis Service over HTTP. The server
does not hold a Neo4j connection in any deployment the team runs: graph tools stay
behind the `NEO4J_URI` gate and stay off by default.
holds no Neo4j connection, because there is no code that can open one: the Cypher
tools, the graph schema resource and the `neo4j-driver` dependency were removed on
2026-09-21.

This is a decision about blast radius, not capability. A public MCP endpoint with
database credentials is a different security proposition from one that can only
make the same calls a browser can.

**Why removed rather than gated.** This principle was enforced by a gate, which
made it a property of a configuration rather than of the code. The gate was
consulted in four places and one of them — `src/http-server.ts`, the entrypoint
that actually runs in the hosted deployment — still opened a connection on
`NEO4J_URI` alone after the other three were corrected. Neither of the two
consumers needed graph access in any case: the chatbot queries the graph directly
with its own driver when it builds embeddings, and the website needs the
REST-backed tools only. `tests/no-graph-access.test.ts` asserts the absence with
the old switches turned ON, so it cannot pass by being configured off.

### V. Analysis runs in the service, not in the agent

Over-representation analysis, species comparison and pathway filtering are
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ All notable changes to this project are documented here. This project adheres to

## [Unreleased]

### Removed

- **BREAKING: all graph database access.** The three `reactome_cypher_*` tools, the `reactome://graph/schema` resource, the Cypher section of the server instructions, the startup schema prefetch and the `neo4j-driver` dependency are gone. `NEO4J_URI`, `NEO4J_USER`, `NEO4J_PASSWORD`, `NEO4J_DATABASE`, `CYPHER_QUERY_TIMEOUT_MS` and `MCP_ALLOW_CYPHER` are inert — nothing reads them.

**Who this affects:** a curator running this server locally over stdio against `reactome_neo4j_env`, which is who the tools were built for. There is no replacement in this server; query the graph directly, as the Reactome chatbot does with its own driver when it builds embeddings.

**Why now.** This server is being hosted publicly, behind the website's nginx. Constitution Principle IV already said no deployment holds a Neo4j connection, and enforced it with a gate — which made the property true of a *configuration* rather than of the code. The gate was consulted in four places and one of them, `src/http-server.ts`, the entrypoint that actually runs in the hosted deployment, still opened a connection on `NEO4J_URI` alone after the other three were corrected. Neither consumer of this server needed graph access in the first place.

The public tool surface is **unchanged at 59** — the Cypher tools were only ever the 60th to 62nd, and only with both switches on.

`tests/no-graph-access.test.ts` asserts the absence with the old switches turned **on**, so it cannot pass by being configured off.

### Fixed
- **Ten tools read field paths the Reactome services never return.** Each called the right endpoint and reported success, so nothing flagged them: `contentClient.get<T>` asserts `T`, it does not verify it, and 51 of 56 tools had no test. `search_suggest` and `search_spellcheck` expected `{suggestions: []}` where the API returns a bare array; `entity_component_of` expected `Complex` objects where the API returns one entry per relationship type with parallel `names`/`stIds`/`schemaClasses` arrays; `participants` expected `stId` where the API returns `peDbId`; the four interactor tools read `score` one level above where it lives, throwing on `.toFixed`; `analysis_found_entities` read `mapsTo[].identifier` where the API returns `ids[]`. **`search_diagram` had never returned an answer** — it shared the grouped-results helper, but that endpoint returns a flat `entries` array, so every call threw `result.results is not iterable`.
- **Two tools dropped data silently, with no `undefined` to give it away.** `participants` never rendered external identifiers at all — the endpoint returns `refEntities` (an array), never a singular `referenceEntity`, so UniProt accessions were simply absent. `search_facets` returned nothing but its heading: each facet is an object with an `available` list, so `.length` on it was `undefined` and every section was skipped as falsy.
Expand Down
54 changes: 9 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that
- **Species & disease** — list available species and disease annotations
- **ID mapping** — map external identifiers (UniProt, Ensembl, CHEBI, etc.) to Reactome pathways and reactions

Over 40 tools and 10 resources are registered — see [Tools](#tools) and [Resources](#resources) below for the full list. Curators can additionally opt in to direct **Cypher / Neo4j** access against a local Reactome graph database (see [Graph Database / Cypher](#graph-database--cypher-3-tools-opt-in)).
59 tools and 10 resources are registered — see [Tools](#tools) and [Resources](#resources) below for the full list. `tests/readme-tools.test.ts` fails if that count drifts or a tool goes undocumented.

## Prerequisites

Expand All @@ -36,11 +36,6 @@ All configuration is via environment variables — pass them in the `env` block
| `REACTOME_BASE_URL` | `https://reactome.org` | Base URL for the Content + Analysis Services. Override to point at staging / a specific release host. |
| `REACTOME_CONTENT_SERVICE_URL` | derived from `REACTOME_BASE_URL` | Fine-grained override for the Content Service only. |
| `REACTOME_ANALYSIS_SERVICE_URL` | derived from `REACTOME_BASE_URL` | Fine-grained override for the Analysis Service only. |
| `NEO4J_URI` | _(unset)_ | Set to enable the optional Cypher tools (see below). |
| `NEO4J_USER` | `neo4j` | |
| `NEO4J_PASSWORD` | `neo4j` | Works against auth-disabled local images (`reactome_neo4j_env`). Set explicitly for any remote database. |
| `NEO4J_DATABASE` | `graph.db` | Matches the default in `reactome_neo4j_env`. |
| `CYPHER_QUERY_TIMEOUT_MS` | `30000` | Server-side transaction timeout (ms) for `reactome_cypher_*` tools. Runaway queries are terminated after this. |
| `LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error`. Logs are JSON on stderr; stdout is reserved for the MCP protocol. |

## Usage
Expand All @@ -66,8 +61,6 @@ Add the server to your Claude Desktop configuration (`claude_desktop_config.json
claude mcp add reactome node /absolute/path/to/reactome-mcp/dist/index.js
```

Add `--env NEO4J_URI=bolt://localhost:7687` (and friends) to enable the Cypher tools. See [Configuration](#configuration) for the full list.

### Example prompts

Once the server is registered, try asking Claude:
Expand All @@ -81,12 +74,6 @@ Once the server is registered, try asking Claude:
- "Find pathways in the HHV8 infection area and show me the contained reactions of the best match."
- "Export the SBGN for pathway R-HSA-1640170."

**Graph queries (requires `NEO4J_URI`):**

- "Show me the Reactome graph schema, then find all `Pathway` nodes that are disease pathways *and* have a human species annotation."
- "Using the graph DB: for reaction R-HSA-199420, list every input and output entity with its display name and stable ID."
- "Count how many `ReactionLikeEvent` nodes exist per species."

Claude reads per-server instructions on connection explaining the tool categories, ID conventions, and a recommended workflow, so it can usually chain the right calls without hand-holding. If an answer looks off, ask it to "show me the tool call and its result" and correct from there.

### Standalone
Expand Down Expand Up @@ -129,7 +116,7 @@ Starts a local web UI with an MCP bridge for browser-based exploration.
| `reactome_analysis_pathway_sizes` | Get pathway size distribution from an analysis result |
| `reactome_filter_analysis_pathways` | Filter an analysis result to specific pathways |

### Pathways (7 tools)
### Pathways (8 tools)

| Tool | Description |
|------|-------------|
Expand All @@ -140,6 +127,7 @@ Starts a local web UI with an MCP bridge for browser-based exploration.
| `reactome_pathways_for_entity` | Find pathways containing a specific entity |
| `reactome_diagram_pathways_for_entity` | Find diagram-level pathways containing an entity |
| `reactome_events_hierarchy` | Get the complete event hierarchy for a species |
| `reactome_preceding_events` | Find the events that must occur before a reaction or pathway — Reactome's event ordering |

### Search (7 tools)

Expand Down Expand Up @@ -191,39 +179,15 @@ Starts a local web UI with an MCP bridge for browser-based exploration.
| `reactome_interactor_pathways` | Find pathways where a protein's interactors appear |
| `reactome_interactor_summary` | Summarise curated interactions for a protein |

### Graph Database / Cypher (3 tools, opt-in)

Only registered when `NEO4J_URI` is set. Designed for curators running the [`reactome_neo4j_env`](https://github.com/reactome/reactome_neo4j_env) Docker image locally (or pointing at a remote Reactome Neo4j).
### Gene Set Analysis / ReactomeGSA (5 tools)

| Tool | Description |
|------|-------------|
| `reactome_cypher_query` | Run a Cypher query with optional parameters; row count, per-row size, and total response size are all capped; a server-side timeout terminates runaway queries |
| `reactome_cypher_schema` | Live APOC introspection: labels with node counts, relationship cardinalities, per-label and per-rel property types (with mandatory flags), indexes, constraints. Cached for the session after first call; pre-warmed at MCP startup. |
| `reactome_cypher_sample` | Return a small sample of nodes for a given label |

**Read-only posture — what it is and isn't.** Sessions run in Neo4j READ mode, which rejects native write clauses (`CREATE`, `MERGE`, `DELETE`, `SET`, `REMOVE`). On top of that, `reactome_cypher_query` rejects APOC procedures that can write or reach outside the graph through back-channels (`apoc.cypher.runWrite` / `apoc.cypher.doIt`, `apoc.periodic.*`, `apoc.create/merge/refactor.*`, `apoc.load/import/export.*`, `apoc.trigger.*`, `apoc.nodes.delete`). Treat this as a guardrail against accidental mutation, not a security boundary — a real trust boundary should live at the Neo4j RBAC / plugin configuration layer, or by pointing at a read-only replica.

**Configuration** (add to your Claude MCP config `env` block):

```json
{
"mcpServers": {
"reactome": {
"command": "node",
"args": ["/absolute/path/to/reactome-mcp/dist/index.js"],
"env": {
"NEO4J_URI": "bolt://localhost:7687",
"NEO4J_USER": "neo4j",
"NEO4J_PASSWORD": "neo4j",
"NEO4J_DATABASE": "graph.db",
"CYPHER_QUERY_TIMEOUT_MS": "30000"
}
}
}
}
```

`NEO4J_USER` / `NEO4J_PASSWORD` default to `neo4j` / `neo4j` (which works when the server has auth disabled, as in `reactome_neo4j_env`). `NEO4J_DATABASE` defaults to `graph.db`. `CYPHER_QUERY_TIMEOUT_MS` defaults to 30000 ms.
| `reactome_gsa_methods` | List the gene set analysis methods ReactomeGSA offers (PADOG, Camera, ssGSEA, terapadog) |
| `reactome_gsa_data_types` | List the kinds of experimental data ReactomeGSA can analyse (RNA-seq counts, normalised RNA-seq, proteomics, microarray, Ribo-seq) |
| `reactome_gsa_search_datasets` | Search public expression datasets ReactomeGSA can load — Expression Atlas, Single Cell Expression Atlas, GREIN, GEO |
| `reactome_gsa_examples` | List the bundled example datasets |
| `reactome_gsa_sources` | List the external dataset sources ReactomeGSA can load from |

### Utilities (7 tools)

Expand Down
14 changes: 7 additions & 7 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,30 @@ Include, where possible:

- A description of the issue and the potential impact
- Steps to reproduce (a minimal query, MCP config snippet, or command)
- Whether the issue affects the REST-client path, the Neo4j / Cypher path, or the web demo
- The version of `reactome-mcp` (see `package.json`) and any relevant runtime versions (Node, Neo4j server, APOC)
- Whether the issue affects the REST-client path, the HTTP transport, or the web demo
- The version of `reactome-mcp` (see `package.json`) and any relevant runtime versions (Node)

You can expect an acknowledgement within a few business days. We will coordinate a fix and, where appropriate, credit you in the advisory.

## Scope

In scope:

- The MCP server (`src/`, `dist/`) and its REST / Neo4j clients.
- The MCP server (`src/`, `dist/`) and its REST clients.
- The bundled web demo (`web/`).

Out of scope (report upstream):

- Vulnerabilities in `https://reactome.org` — report via the Reactome website.
- Vulnerabilities in Neo4j, APOC, or `neo4j-driver` — report via those projects.
- Vulnerabilities in Claude Desktop / Claude Code / the MCP SDK — report to the respective vendor.

## Threat Model (brief)

- The server is designed to be run locally by a trusted curator against either the public Reactome APIs or a local Reactome Neo4j image. It is **not** hardened for multi-tenant or internet-facing deployment without additional controls (auth, TLS, network isolation).
- The `reactome_cypher_query` tool runs in a Neo4j READ-mode session and rejects known APOC write-through procedures, but this is a guardrail, not a security boundary. For untrusted inputs, point at a read-only replica and configure Neo4j RBAC / plugin loading accordingly.
- The server reads the public Reactome Content and Analysis Services over HTTP and holds no database credentials. As of 2026-09-21 it has **no graph database access at all**: the Cypher tools, the graph schema resource and the `neo4j-driver` dependency were removed when the server began to be hosted publicly. `NEO4J_URI` and `MCP_ALLOW_CYPHER` are inert — nothing reads them.
- It is still **not** self-hardened for internet-facing deployment: it has no authentication, no TLS and no rate limiting of its own. A public deployment must put those at the reverse proxy, and should bind the server to loopback so the proxy is the only way in. What removing graph access changes is the blast radius if that proxy is misconfigured — a caller reaching the server directly can make the same calls a browser can make against reactome.org, and nothing more.
- Tool inputs are bounded: every list argument has an explicit maximum, and `MAX_ANALYSIS_IDENTIFIERS` caps what one call may submit to the Analysis Service. The HTTP transport separately refuses a request body over 100 KiB.
- The web demo (`web/mcp-bridge.js`) uses an allow-list CORS policy defaulting to localhost. Do not deploy it publicly without adding authentication.

## Handling of Secrets

- `NEO4J_PASSWORD` and any future credential env vars are read at process start and passed to the driver. They are never logged. REST error bodies are logged verbatim on failure — if you deploy against an authenticated proxy, ensure upstream errors do not echo credentials.
- The server holds no credentials of its own. REST error bodies are logged verbatim on failure — if you deploy behind an authenticated proxy, ensure upstream errors do not echo credentials.
Loading
Loading