heracleum-tox-mcp-server
by chemagents
README.md
# heracleum-tox-mcp-server
An MCP server that **reproduces the results** of:
> Rassabina, A.E.; Fedorov, M.V.
> *Analysis of the Toxicological Profile of Heracleum sosnowskyi Manden. Metabolites Using In Silico Methods.*
> **Plants 2025, 14, 3253.** https://doi.org/10.3390/plants14213253
The paper runs entirely on the proprietary **Syntelly** platform. Since Syntelly is not
openly accessible, this server reproduces the same pipeline with **open-source analogues
of every Syntelly module** — including the very models Syntelly itself uses
(fingerprint-based CatBoost + fragment-based XGBoost; Sosnin et al., *Molecules* **2024**, 29, 1826,
the platform paper cited as ref. [36]), trained on the same open datasets the paper names
(TOXRIC / ChemIDplus / PyTDC).
## Syntelly → open-source analogue mapping
| Syntelly module (in the paper) | What it does | Open-source analogue used here |
|---|---|---|
| Canonical SMILES search | name → SMILES, standardisation | **RDKit + PubChemPy** |
| **SynMap** (clustering, §2.3) | parametric multiscale t-SNE + differential fingerprints | **differential fingerprint (Bemis–Murcko scaffold ECFP) + agglomerative (Tanimoto) + t-SNE** |
| **LD50 (mouse) prediction** (§2.4) | route-specific fingerprint regression, RMSE | six **XGBoost** models on hybrid ECFP4+descriptors+fragments, trained on the byte-pinned public **TOXRIC v5** mouse-route targets; no route-agnostic model contributes to article-reproduction results |
| **General toxicity** (§3.5) | CatBoost/XGBoost classification, ROC-AUC | legacy endpoints: **XGBoost** on fragments; **DILI v2:** soft-voting XGBoost + ExtraTrees + logistic ensemble on ECFP4, physicochemical and fragment features; trained on **TDC `DILI` / `hERG` / `Carcinogens_Lagunin`** |
| **Applicability Domain** (§2.5) | kNN(k=5) distance → normalise → Gaussian → % | **Tanimoto kNN(k=5) + Gaussian**, identical formula |
| **Synthesis cost** (§2.6) | USD/g over 1–6 stages | **ASKCOS** retrosynthesis (same engine as `chemical-mcp-server`) + heuristic fallback |
## Tools
| Tool (agent-visible name) | Reproduces | What it returns |
|------|-----------|-----------------|
| `heracleum_dataset_overview` | §3.1 | reconstructed metabolite dataset, class & cluster breakdown |
| `chemical_space_clustering` | Fig. 1 | five chemical-family clusters (A–E) + t-SNE map + outliers |
| `predict_ld50` | Figs. 2–3 / §3.3 | six live public-TOXRIC route models; per-route rankings plus a disclosed median-of-route-medians descriptive order; complete 132-cell Figure 3 comparison; identity-and-scaffold training-overlap audit |
| `predict_general_toxicity` | Table 2 / §3.5 | hepatotox / DILI / cardiotox / carcinogenicity for cluster E + per-endpoint paper comparison + heatmap |
| `applicability_domain` | Fig. S1/S2 / §2.5 | kNN(k=5)+Gaussian AD % per cluster-E compound, banded |
| `estimate_synthesis_cost` | §3.6 | USD/g (published value, ASKCOS, or heuristic) |
| `predict_molecule_profile` | — | full in-silico tox profile for **any** molecule (name/SMILES) |
| `model_quality` | Table S6 | trained-model RMSE / ROC-AUC, feature set and validation provenance vs the paper's values |
| `heracleum_reproduce_all` | — | recomputes headline numbers and compares to the paper |
| `heracleum_reproduce_claims` | all | the paper's conclusions, each restated with reproduced numbers |
Each tool returns `{"answer": ..., "metadata": ...}`. Where a tool returns an `answer.finding`,
that string is **computed from the values returned in that same call** — no hardcoded conclusions.
(`estimate_synthesis_cost` and `predict_molecule_profile` return raw per-molecule numbers and no
`finding`; values under `metadata.paper` are the paper's published figures, quoted, not recomputed.)
Figures are saved as PNG to a local artifacts dir (`HERACLEUM_ARTIFACTS_DIR`) or, if S3 is
configured, uploaded and returned as structured presigned references with SHA-256. S3
configuration is all-or-none and checked with `HeadBucket` at startup; upload errors do not
silently become paths inside the container unless `HERACLEUM_S3_ALLOW_LOCAL_FALLBACK=true` is
set explicitly for development. When Docker uses `http://minio:9000` for upload, set
`HERACLEUM_S3_PUBLIC_ENDPOINT_URL` to the caller-reachable signing endpoint.
### Tool naming (collision avoidance)
`dataset_overview`, `reproduce_all` and `reproduce_claims` exist under those exact names in the
sibling servers (`tox-antitargets-mcp-server`, `cannabis-biopesticide-mcp-server`) too. When
several of them are registered at once the agent sees duplicate names and mis-routes — in one
observed run it invented a non-existent tool `tox_reproducer` instead of calling
`reproduce_claims`. Those three tools are therefore exposed to the agent under `heracleum_`-prefixed
names via the FastMCP `@mcp.tool(name=...)` parameter:
| Python function | Agent-visible tool name |
|---|---|
| `dataset_overview` | `heracleum_dataset_overview` |
| `reproduce_all` | `heracleum_reproduce_all` |
| `reproduce_claims` | `heracleum_reproduce_claims` |
The Python function names are unchanged, so `server/`-level imports and the test-suite are
unaffected. The other seven tool names are already unique across the servers and keep their names.
### Multi-tool question routing
Tools carry their eliciting question in the docstring (which is what the RAG retriever embeds).
Multi-tool questions use one forward-only canonical chain; the terminal tool alone exposes
`metadata.next_question`, and Q4 terminates explicitly:
| Question | Tools | `next_tools` chain |
|---|---|---|
| Which metabolites, and how do they group into chemical families? | `heracleum_dataset_overview` + `chemical_space_clustering` | overview → clustering → Q2 |
| Which family is most acutely toxic, and how does toxicity depend on route? | `predict_ld50` | — |
| Toxicological profile of the furanocoumarins (hepatotox/DILI/cardiotox/carcinogenicity)? | `predict_general_toxicity` | — |
| How reliable are the predictions, and how good are the models? | `applicability_domain` across all 6 LD50 routes + 4 classifiers, then `model_quality` | applicability → quality → complete |
See [`REPRODUCTION_QUESTIONS.md`](./REPRODUCTION_QUESTIONS.md) for the full four-question scenario.
Each recommended prompt explicitly asks the agent to return every generated figure artifact with
its kind and SHA-256, rather than returning an orchestration log or a bare confirmation.
## Reproduction fidelity
`heracleum_reproduce_all` and `pytest tests/` assert these against the paper:
| Metric | Paper | This server |
|---|---|---|
| Dataset size | 225 metabolites | **225 (exact, from Supplementary S1–S5)** |
| Cluster sizes A/B/C/D/E | 25/22/132/21/22 | **25/22/132/21/22 (exact)** |
| Chemical-space clusters | 5 families (A–E) | **all 5 recovered, ~95 % family agreement** |
| Most-toxic cluster | E (furanocoumarins) | evaluated from the **six TOXRIC route models** by the disclosed equal-weight median of route-specific cluster medians; no seventh route-agnostic model is used |
| Cluster-E IV LD50 range | 62–450 mg/kg | **62–450 (bergamottin/phellopterin 62, umbelliferone 450)** |
| LD50 regression models | six routes; RMSE 0.41–0.87 (Table S6) | **six separate public TOXRIC v5 models** with duplicate-safe identity and strict scaffold holdouts; paper values are context, not a head-to-head split |
| Tox classification ROC-AUC | 0.79–0.93 (Table S6) | **0.80–0.89** on different public training data/validation splits (descriptive context, not head-to-head); DILI v2 pooled internal OOF **≈0.89** by 5-fold scaffold-group CV |
| Synthesis-cost spread | $0.19–311/g | **$0.19 / $24.9 / $311 (exact)** |
The full 225-compound dataset (standardized SMILES + SynID + cluster A–E) is reproduced
**exactly** from the paper's Supplementary Tables S1–S5 — parsed by `parse_supplementary.py`
into `server/data/supplementary_smiles.csv`, then assembled by `build_dataset.py` (which
merges the cluster-E Table 2 toxicity values and resolves compound names via PubChem). The
paper's own model-quality numbers (Supplementary Table S6) are bundled for comparison
(`model_quality` / `heracleum_reproduce_all`).
**Open-model endpoint validation.** These results are **computed and reported at call time** by
`predict_general_toxicity` (`answer.endpoint_summary`, `answer.endpoints_diverging_from_paper`)
and by `heracleum_reproduce_claims` (C5) — the numbers below are what the bundled model cache
produces today, not assertions baked into the code:
- *DILI v2*: the former fragment-only XGBoost called only 1/22 cluster-E compounds positive.
The replacement model is a fixed soft-voting ensemble (XGBoost + ExtraTrees + logistic
regression) over a hybrid ECFP4 + physicochemical-descriptor + fragment representation. On the
**public TDC DILI training set only** (n=475), five-fold stratified Bemis–Murcko scaffold-group
internal OOF evaluation gives pooled ROC-AUC **≈0.89**, average precision **≈0.88**, and balanced
accuracy **≈0.83** at the standard 0.5 threshold, with zero scaffold overlap between train/test
folds. Because the representation/ensemble were selected on this same public dataset, these are
post-selection internal estimates rather than independent external validation; the API also
returns the exact environment-specific pooled values plus mean±SD and sizes for the five folds
(the host and locked Linux image produced ROC-AUC 0.885 and 0.887, respectively). The effective
RDKit-accepted training rows, feature/AD settings, seed, estimator specification and relevant
library versions are returned in `metrics.dili.training_contract` by `model_quality` (and in
the endpoint model metrics embedded in prediction metadata). The raw source file, effective
fitted rows and complete contract each carry SHA-256 digests. A cheap cache-identity digest
over the exact source bytes, model/config specification and software versions is part of the
cache filename, while the full fitted-row digest remains available for audit.
A dataset, configuration or dependency change therefore cannot silently reuse an old DILI model.
The 22
Heracleum labels are not used for training,
threshold selection or model selection. DILI v2 calls **22/22 positive**, matching the paper,
but all 22 references are positive and cover only five unique scaffolds (one compound shares a
training scaffold). This is therefore a positive concordance/sensitivity check: specificity and
external ROC-AUC cannot be estimated. Mean applicability-domain reliability remains only
**38 % (average)**, and no matched negative control is available, so this agreement is explicitly
reported as low-evidence rather than as external model validation.
- *Broader hepatotoxicity proxy*: this deliberately remains separate from DILI v2 and retains the
legacy TDC-DILI fragment model: 1/22 positive against the paper's 17/22, mean AD ≈ 38 %. Calling
two endpoint names on the same old estimator is no longer presented as two independent models.
- *Cardiotoxicity*: the open **hERG** proxy is far more conservative than Syntelly's cardiotox
model — current locked numerical platforms flag 20–21/22 furanocoumarins as hERG blockers
(21/22 in the validated native arm64 image); the paper found 0/22. Because the paper
reference is negative-only, this comparison can estimate negative concordance/specificity but
not sensitivity or ROC-AUC; the structured `paper_comparison_design` reports that limitation.
- *Carcinogenicity*: the open model calls 0/22 carcinogenic against the paper's 10/22; the
majority direction (non-carcinogenic) still agrees, at 55 % per-compound agreement and the
highest AD of the four endpoints (mean ≈ 52 %, *high* band).
- *Per-route LD50*: the public TOXRIC v5 Figshare artifact contains all six mouse-route targets
directly: oral 23,371; IV 16,976; IP 36,294; SC 6,769; skin 214; IM 571 usable rows after the
pinned four-row SMILES quarantine. The server trains six separate hybrid XGBoost regressors and
reports both duplicate-safe identity-group and strict Bemis–Murcko scaffold-group holdouts.
The complete paper Figure 3 is also transcribed as 132 digest-pinned reference cells. Its 117
Syntelly outputs are reported only as concordance; its 15 experimental stars are checked against
route-training identity and scaffold membership and are not called external accuracy when
either overlap exists.
The headline cross-route family order is descriptive: within each route the server takes the
median predicted LD50 for each cluster, then ranks clusters by the equal-weight median of those
six route medians. That aggregate is not a physical all-route LD50 or an exposure-weighted risk
estimate; all six per-route rankings remain visible beside it.
The clustering uses a **differential fingerprint** (ECFP of the Bemis–Murcko scaffold) as the
open analogue of SynMap's differential fingerprints + parametric t-SNE (Karlov/Sosnin/Tetko/
Fedorov, *ACS Omega* 2021). Emphasising the core scaffold separates furanocoumarins (E) from
simple aromatics (D), recovering **all five families** at ~95 % agreement; set
`HERACLEUM_CLUSTER_FINGERPRINT=ecfp4` for the plain-molecule fallback (~86 %, merges D into E).
## Run locally
```bash
git clone https://github.com/chemagents/heracleum-tox-mcp-server.git
cd heracleum-tox-mcp-server
cp .env.example .env
uv sync
uv pip install --no-deps "PyTDC==0.4.1" # open datasets; pins old rdkit-pypi, so --no-deps
uv run python prepare_models.py # train/cache all 10 models; DILI v2 runs scaffold CV
uv run python -m server.heracleum_server # serves http://0.0.0.0:7331/mcp
# The 225-compound dataset is already bundled (server/data/heracleum_metabolites.csv).
# To regenerate it from the paper's Supplementary PDF:
# pdftotext -layout plants-3875800-supplementary.pdf supp.txt
# uv run python parse_supplementary.py supp.txt # -> server/data/supplementary_smiles.csv
# uv run python build_dataset.py # merges Table 2 refs + PubChem names
```
## Run with Docker
```bash
cp .env.example .env
docker compose up -d --build --wait # 127.0.0.1:7336 -> container 7331
```
The Docker build installs PyTDC and pre-trains exactly ten production models: six TOXRIC routes and
four classifiers. Startup validates all ten cache payloads without resolving source datasets,
downloading, featurizing, or training. Both the image and `docker-compose.yml` set
`HERACLEUM_REQUIRE_BAKED_MODELS=true`; the compose `environment` value outranks the copied `.env`,
whose false value is only the direct local-development default. A missing dependency, dataset,
cache, or failed fit therefore fails build/startup rather than becoming a first-request operation.
The container runs unprivileged with all Linux capabilities dropped, and the
standalone port is loopback-only by default. For the CoScientist monorepo build context and its
strict MinIO artifact smoke test, see [`COSCIENTIST_INTEGRATION.md`](./COSCIENTIST_INTEGRATION.md).
The CoScientist-context Dockerfile copies only `server/*.py` and `server/data` after model
preparation. It therefore cannot copy a host `server/model_cache` over the baked models even when
the parent build context does not apply this nested repository's `.dockerignore` rules.
## Attach to CoScientist
CoScientist discovers MCP tools via RAG (Postgres + Qdrant). Register this server once:
```bash
# from the CoScientist repo root, with the RAG stack running and .env configured
python scripts/rag_tools/cli.py load mcp-servers/heracleum-tox-mcp-server/rag_registration.json
# or directly:
python scripts/rag_tools/cli.py add \
--url http://localhost:7336/mcp \
--name heracleum-tox \
--description "In-silico toxicology of Heracleum sosnowskyi metabolites; LD50, hepato/DILI/cardio/carcinogenicity, furanocoumarins (Rassabina & Fedorov 2025)"
```
Prefer `cli.py load rag_registration.json` over the inline `--description`: the bundled
registration description is written to discriminate this server from the sibling
`tox-antitargets-mcp-server` (both are "toxicology"). The discriminators are **plant secondary
metabolites**, **Heracleum sosnowskyi / hogweed / борщевик**, **furanocoumarins and coumarins**,
**route of administration**, **DILI / hepatotoxicity / carcinogenicity**, **applicability domain**
and **synthesis cost**; `tox-antitargets` owns antitarget/off-target protein affinity, docking and
the Bowes panel.
After registration the `ToolRetrieverAgent` surfaces these tools for plant-metabolite /
toxicity / LD50 / furanocoumarin queries, and `ExperimentAgent` (FEDOT.MAS) calls them by
URL. If CoScientist runs in the same Docker network, register the in-network URL instead:
`http://heracleum-tox-mcp-server:7331/mcp`.
See [`REPRODUCTION_QUESTIONS.md`](./REPRODUCTION_QUESTIONS.md) for the exact prompts to ask
CoScientist — the recommended four-question scenario for the validation report, plus one prompt
per paper assertion and a single "reproduce everything" fallback.
## Public TOXRIC v5 route-model contract
The six mouse-route targets are public in the byte-pinned TOXRIC v5 Figshare release
(`10.6084/m9.figshare.27195339.v5`). No author-only dataset, TOXRIC licence, or manually supplied
CSV is required. The ingestion layer verifies the archive and main-table digests, exact schema and
shape, target unit (`-log10(mol/kg)`), and the four explicitly quarantined invalid structures before
training.
The server fits one fixed XGBoost regressor per route over hybrid ECFP4, physicochemical descriptors,
and RDKit fragment features. It reports both normalized-identity-group and Bemis–Murcko
scaffold-group holdouts with overlap audits. The TOXRIC release's published random-split files are a
secondary reproducibility benchmark; they are not substituted for the stricter scaffold estimate.
The paper's Figure 3 values are never used for fitting, hyperparameter selection, or threshold
selection.
The overall cluster order shown by `predict_ld50`, C3/C8, and `heracleum_reproduce_all` is derived
from these same six models: median predicted LD50 within cluster for each route, followed by an
equal-weight median across the six route medians. It is a descriptive ordering only, while the
route-specific values and rankings remain the primary model outputs.
The sealed production Docker build produced the strict metrics below. A separate deterministic
audit of TOXRIC's published split files produced the repeated-70/30 benchmark column. Values are
RMSE in `-log10(mol/kg)`:
| Route | Strict scaffold holdout | TOXRIC five repeated 70/30 holdouts, mean ± SD | Paper S6 reference |
|---|---:|---:|---:|
| oral | 0.463 | 0.421 ± 0.007 | 0.45 |
| IV | 0.477 | 0.446 ± 0.006 | 0.41 |
| IP | 0.496 | 0.434 ± 0.006 | 0.49 |
| SC | 0.649 | 0.581 ± 0.013 | 0.65 |
| skin | 0.802 | 0.829 ± 0.076 | 0.81 |
| IM | 1.051 | 0.709 ± 0.070 | 0.87 |
Every strict split has zero normalized-scaffold overlap. The bundled TOXRIC splits have zero exact
RTECS overlap but 33–72% of test rows share a normalized scaffold with training (route-dependent),
and test sets overlap one another; they are repeated holdouts, not mutually exclusive K-folds.
Neither column is a head-to-head superiority test against Syntelly because the paper used a
different snapshot and validation contract.
`HERACLEUM_TOXRIC_CACHE_DIR` controls the verified download/cache location and
`HERACLEUM_LD50_N_JOBS` controls XGBoost parallelism. `prepare_models.py` and the production Docker
build bake all six route models plus four classifiers, then the final image resets the route-model serving default
to one worker. Worker count is a deployment resource and is excluded from cache identity, so the
four-worker baked models load unchanged with the one-worker runtime limit. Serving therefore never
depends on a first-request download or fit: the complete ten-model set is cache-only preflighted
before the MCP transport starts. Set
`HERACLEUM_RETRAIN=true` only when an intentional rebuild is required; cache identity includes the
pinned source, estimator/configuration contract, feature contract, and relevant software versions.
Model caches use Python/joblib deserialization: keep `HERACLEUM_MODEL_CACHE_DIR` on a trusted local
volume and never mount content writable by an untrusted producer.
The complete paper Figure 3 transcription is independently digest-pinned: 22 compounds × 6 routes
= 132 cells. The 117 unstarred Syntelly predictions are assessed only for model-to-model
concordance. The 15 starred experimental values are reported separately, with a per-cell TOXRIC
identity-and-scaffold training-membership audit; either kind of overlap prevents them from being
described as external accuracy.
## Tests
```bash
uv run pytest tests -v # all (trains models on first run, then cached)
uv run pytest tests -v -m "not slow" # fast deterministic checks only
```
## License / data
Open datasets via Therapeutics Data Commons (PyTDC) and TOXRIC. Please cite
Rassabina & Fedorov (2025) when using these results, and TDC / the Syntelly platform paper
(Sosnin et al., *Molecules* 2024, 29, 1826) for the methods.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues