gem-mcp
by praneeshlabs
README.md
# Agentic Metabolic Systems Biology - Stateful COBRApy via MCP & SKILLS.md
**GSoC 2026 · NRNB (National Resource for Network Biology) · Project #280**
(Self-Project)
A portable, deterministic bridge between Large Language Models and genome-scale
metabolic modeling (GEM) tooling - built on the **Model Context Protocol (MCP)**
for tool discovery/execution and the **SKILLS.md** standard for encoding expert
workflows as reusable, semantic execution contracts.
This repository is the foundational boilerplate for the project: a stateful
FastMCP server wrapping [COBRApy](https://opencobra.github.io/cobrapy/), a
sample SKILLS.md workflow, a fully containerized one-click deployment, and a
test suite that doubles as a working proof-of-concept report.
---
## Why this exists
Genome-scale metabolic models are computationally powerful but operationally
fragile: COBRApy, CarveMe, and Neo4j each have their own idiosyncratic APIs,
and naively feeding an LLM full model dumps or library docs either overflows
its context window or invites it to hallucinate a metabolic pathway that
doesn't exist. This project's answer is architectural, not prompt-based:
- **MCP is the kitchen** — a slim, dynamically-discoverable set of tools the
agent can call, with the underlying biological objects (models, solvers)
kept server-side.
- **SKILLS.md is the recipe book** — the expert standard operating procedure
(load → validate gene → knock out → re-optimize → compare) that keeps the
LLM as an *orchestrator* of deterministic linear-programming calls, never
the source of a numeric result itself.
## Architecture
```
Application Host Process
┌────────────────────────────────────────────────────┐
│ LLM Agent (e.g. OpenCode) │
│ │ │
│ │ reads workflow contract │
│ ▼ │
│ skills/fba_workflow.md (SKILLS.md) │
│ │ │
│ │ emits tool calls (JSON-RPC 2.0) │
│ ▼ │
│ MCP Client ─────────────────stdio/HTTP──────────►│──┐
└────────────────────────────────────────────────────┘ │
▼
┌─────────────────────────────────┐
│ FastMCP Server (server.py) │
│ ───────────────────────────── │
│ SESSIONS: dict[session_id -> │
│ ModelSession] │
│ │
│ tools: │
│ load_model │
│ knockout_gene │
│ run_fba │
│ get_flux_distribution │
│ list_genes │
│ list_sessions / reset_session │
│ │
│ COBRApy ──► GLPK/CBC solver │
└─────────────────────────────────┘
```
**Host** - the LLM application (e.g. an OpenCode agent) that decides *when*
to call a tool.
**Client** - the MCP client embedded in the host, speaking JSON-RPC 2.0 over
stdio (or streamable-HTTP inside Docker).
**Server** - `server.py`: a FastMCP process that owns the COBRApy models and
the solver, and exposes a small, versioned tool surface.
### Stateful, URI-addressable models
The proposal's central design constraint is that a GEM must **never** be
serialized into the LLM's context window - a genome-scale SBML file can be
tens of megabytes and instantly blow the token budget. Instead:
1. `load_model` reads the model once, server-side, and returns only a
**`session_id`** plus a `metabolic://models/{session_id}` resource URI and
small summary metadata (reaction/gene/metabolite counts).
2. Every subsequent tool call (`knockout_gene`, `run_fba`, ...) takes that
`session_id` and mutates or queries the *same* in-memory `cobra.Model`
object held in the server's `SESSIONS` registry.
3. A gene knockout applied in one conversational turn is therefore still in
effect the next time the agent calls `run_fba` with that `session_id` -
no reload, no re-serialization, no context bloat.
4. Only scalars (objective value, solver status) or small top-N summaries
are ever returned to the agent — the full flux vector is available on
request via `get_flux_distribution`, but is not sent by default.
This in-memory registry is intentionally simple (a Python dict) for this
boilerplate. The proposal's noted limitation - a server restart clears all
sessions - is real; a persistence layer (e.g. pickling `ModelSession` objects
to the `model_data` Docker volume) is documented future scope, not
implemented here.
## Repository layout
```
.
├── Dockerfile # multi-stage build: builder (solver deps) -> slim runtime
├── docker-compose.yml # one-click orchestration
├── requirements.txt
├── server.py # FastMCP server + stateful COBRApy tools
├── skills/
│ └── fba_workflow.md # SKILLS.md execution contract for FBA + knockout
└── tests/
└── test_server.py # unit tests, MCP protocol integration test, PoC metrics
```
## Tools exposed by the server
| Tool | Purpose |
|---|---|
| `load_model(model_name, sbml_path)` | Load a built-in model (`textbook` / `ecoli_core`, `salmonella`) or a custom SBML file. Returns `session_id` + summary metadata. |
| `knockout_gene(session_id, gene_id)` | Knock out a gene in the session's model; persists for future calls. Errors deterministically on an unknown gene. |
| `run_fba(session_id)` | Runs FBA on current model state; returns only solver status + objective value. |
| `get_flux_distribution(session_id, top_n)` | Opt-in detail: top-N reactions by |flux|, for when the agent needs pathway-level insight. |
| `list_genes(session_id, limit)` | Grounds the agent in real gene IDs before a knockout, preventing hallucinated IDs. |
| `list_sessions()` | Lists all active server-side sessions. |
| `reset_session(session_id)` | Discards a session's in-memory state. |
Built-in models (`textbook` → *E. coli* core, `salmonella`) are loaded from
COBRApy's bundled local data files, so `load_model` works fully offline -
no BiGG/BioModels network call is required, which matters inside an
air-gapped Docker container.
## The SKILLS.md contract
[`skills/fba_workflow.md`](./skills/fba_workflow.md) defines the FBA +
gene-knockout workflow as YAML-frontmatter metadata (inputs, outputs, tool
dependencies, execution constraints, failure-recovery rules) followed by a
plain-language procedure. Two constraints worth highlighting:
- The agent must capture the **wild-type** objective value via `run_fba`
*before* calling `knockout_gene`, so there is always a baseline to compare
against.
- The agent must confirm a gene ID against `list_genes` before calling
`knockout_gene` - a gene ID is never guessed.
This file is mounted read-write into the container (`./skills:/app/skills`)
so new workflows can be authored or edited without rebuilding the image —
the "dynamic SKILLS.md hot reload" behavior described in the proposal.
## Quickstart
### Option A - Docker (recommended, one-click)
```bash
git clone repo_url.git
cd gem-mcp
docker compose up --build
```
This builds the multi-stage image (solver dependencies compiled in a
`builder` stage, copied into a slim `runtime` stage) and starts the
FastMCP server listening on stdio by default. To expose it over HTTP
instead (e.g. for a containerized agent host to reach it across the Docker
network), override the command:
```bash
docker compose run --rm -p 8000:8000 cobrapy-mcp \
python server.py --transport streamable-http --host 0.0.0.0 --port 8000
```
### Option B - Local Python
```bash
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python server.py # stdio transport, for a local MCP host
# or:
python server.py --transport streamable-http --port 8000
```
### Running the tests / PoC metrics
```bash
pip install -r requirements.txt
pytest tests/test_server.py -v # unit tests + real MCP client/server round trip
python tests/test_server.py # prints PoC metrics, e.g.:
# === E. coli core model — PoC metrics ===
# Reactions: 95
# Metabolites: 72
# Genes: 137
# Wild-type growth rate (1/hr): 0.873922
# Top 5 fluxes (wild type): ATPS4r +45.51, CYTBD +43.60, NADH16 +38.53, ...
# Knockout gene: b1241
# Post-knockout growth rate (1/hr): 0.873922
# Growth ratio (KO / WT): 1.0000
```
The test suite includes:
- **Unit tests** against the tool functions directly (session creation,
invalid gene/session handling, knockout persistence across separate calls).
- **An in-process MCP integration test** using FastMCP's `Client`, which
drives the exact same JSON-RPC path a real agent host would use — proving
the tools are correctly registered, not just correct as plain functions.
- A **PoC metrics report** (`python tests/test_server.py`) that loads the
*E. coli* core model, runs a baseline FBA, shows the top fluxes, performs a
knockout, and reports the resulting growth ratio — the same numbers you'd
want in a GSoC progress update.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues