Skip to main content
Glama

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, 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.

Related MCP server: MCP Python Interpreter

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

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 (textbookE. 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 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

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:

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

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

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.

Related MCP Connectors

Related MCP Servers