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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ All notable changes to this project are documented here. This project adheres to
- `fetchWithRetry` rethrew `lastError`, typed `unknown`, so a non-`Error` rejection reached callers as something they could not read `.message` off.

### Added
- **ReactomeGSA tools** — `reactome_gsa_methods`, `reactome_gsa_data_types`, `reactome_gsa_search_datasets`, `reactome_gsa_examples`, `reactome_gsa_sources`. Reactome has **two** analysis services and this server only knew about one:

| | | |
|---|---|---|
| `AnalysisService` | over-representation over a list of identifiers | already here |
| **ReactomeGSA** (`gsa.reactome.org`) | gene set analysis over an expression matrix — PADOG, Camera, ssGSEA, terapadog | new |

Camera is described by the service as *"a gene set analysis algorithm similar to the classical GSEA algorithm"*. The gap was found the hard way: a Reactome chatbot asked to "run a GSEA with my list of genes" replied that Reactome could not, and offered `fgsea` and a YouTube tutorial. It can — nothing here could reach the service that does it.

The tool descriptions carry the distinction that caused the confusion, since a description is all a model reads before choosing: gene set analysis needs an expression matrix with sample groups, so a user holding only a list of gene names wants over-representation, whatever they called it.

Submitting an analysis is deliberately absent. `POST /analysis` takes the whole expression matrix inline, which is neither something a chat user can paste nor something to push through a tool result. `reactome_gsa_search_datasets` covers the case that *is* reachable — Expression Atlas, Single Cell Expression Atlas, GREIN and GEO can be searched, so someone with no data of their own can still be pointed at a published dataset.
- **Streamable HTTP transport**, alongside stdio. `MCP_HTTP_PORT=4320 node dist/http-server.js`. stdio remains the default and is untouched — every existing client is configured to spawn it. This is what a hosted instance needs, because a reverse proxy cannot front a process that talks over stdin/stdout.

Each session gets its own server instance, built by the `createServer()` factory. Idle sessions are reaped (`MCP_SESSION_TTL_MS`, 30 min) and concurrency is capped (`MCP_MAX_SESSIONS`, 256), so a client that never sends `DELETE` cannot accumulate servers until the process dies.
Expand Down
8 changes: 8 additions & 0 deletions scripts/sweep-live.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const ARGS = {
format: "png",
type: "pathways",
pathways: ["R-HSA-109581"],
keywords: "melanoma",
};

/**
Expand All @@ -67,6 +68,8 @@ const TOOL_ARGS = {
// A correctly-spelled term legitimately returns nothing, which tells us
// only that the call succeeded. A misspelling exercises the formatter.
reactome_search_spellcheck: { query: "kinse" },
// GSA wants the species NAME; a taxonomy id returns zero results silently.
reactome_gsa_search_datasets: { keywords: "melanoma", species: "Homo sapiens" },
reactome_psicquic_summary: { resource: "IntAct", accession: "P04637" },
reactome_psicquic_details: { resource: "IntAct", accession: "P04637" },
};
Expand Down Expand Up @@ -110,6 +113,11 @@ const EXPECT = {
reactome_analyze_identifiers: ["R-HSA-"],
reactome_complex_subunits: ["R-HSA-"],
reactome_events_hierarchy: ["R-HSA-"],
reactome_gsa_methods: ["PADOG", "Camera", "reactome.org/gsa"],
reactome_gsa_data_types: ["rnaseq_counts", "reactome_analyze_identifiers"],
reactome_gsa_search_datasets: ["Homo sapiens"],
reactome_gsa_examples: ["EXAMPLE_"],
reactome_gsa_sources: ["Expression Atlas"],
};

/**
Expand Down
51 changes: 51 additions & 0 deletions src/clients/gsa.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { GSA_SERVICE_URL } from "../config.js";
import { fetchWithRetry } from "./http.js";

/**
* ReactomeGSA (gsa.reactome.org), the gene set analysis service.
*
* Separate from the Analysis Service in every respect that matters: a
* different host, a different API, and a different analysis. Over-representation
* takes a list of identifiers; gene set analysis takes an expression matrix
* with sample groups. Conflating them is the mistake this client exists to stop
* the model making.
*/
export class GsaClient {
private baseUrl: string;

constructor(baseUrl: string = GSA_SERVICE_URL) {
this.baseUrl = baseUrl;
}

private buildUrl(
path: string,
params?: Record<string, string | number | boolean | undefined>
): URL {
const url = new URL(`${this.baseUrl}${path}`);
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) url.searchParams.set(key, String(value));
});
}
return url;
}

async get<T>(
path: string,
params?: Record<string, string | number | boolean | undefined>
): Promise<T> {
const response = await fetchWithRetry(this.buildUrl(path, params).toString(), {
service: "gsa",
headers: { Accept: "application/json" },
});

if (!response.ok) {
const text = await response.text();
throw new Error(`GSA Service error ${response.status}: ${text}`);
}

return response.json() as Promise<T>;
}
}

export const gsaClient = new GsaClient();
17 changes: 17 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,23 @@ export const CONTENT_SERVICE_URL =
export const ANALYSIS_SERVICE_URL =
process.env.REACTOME_ANALYSIS_SERVICE_URL ?? `${REACTOME_BASE_URL}/AnalysisService/`;

/**
* ReactomeGSA, which is a different service from the Analysis Service and does
* a different thing.
*
* AnalysisService over-representation over a list of identifiers
* ReactomeGSA gene set analysis over an expression matrix -- PADOG,
* Camera ("similar to the classical GSEA algorithm"),
* ssGSEA, terapadog
*
* Nothing here talked to it until now, which is why a user asking this server's
* chatbot to "run a GSEA" was told Reactome could not, and offered fgsea and a
* YouTube tutorial instead. Reactome can; it just was not reachable from here.
*/
export const GSA_SERVICE_URL = normalizeBaseUrl(
process.env.REACTOME_GSA_SERVICE_URL ?? "https://gsa.reactome.org/0.1"
);

export const DEFAULT_SPECIES = "Homo sapiens";
export const DEFAULT_PAGE_SIZE = 25;

Expand Down
Loading