orphanet-link
# orphanet-link
[](https://www.python.org/)
[](https://github.com/berntpopp/orphanet-link/actions/workflows/ci.yml)
[](https://github.com/berntpopp/orphanet-link/actions/workflows/conformance.yml)
[](LICENSE)
A read-only [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that grounds
rare-disease questions in **Orphanet's scientific knowledge files**
([Orphadata](https://www.orphadata.com/), INSERM), served from a locally-built SQLite + FTS5 index
of the eight English Orphadata XML products.
> [!IMPORTANT]
> Research use only. Not clinical decision support. Do not use for diagnosis,
> treatment, triage, or patient management.
## Why
Orphadata ships its rare-disease knowledge as eight separate bulk XML products — nomenclature,
cross-references, ~33 per-specialty classification files, gene associations, HPO phenotypes,
epidemiology, natural history, functional consequences. They are a **download, not a query
surface**: asking "which genes are linked to Aicardi syndrome, what is its prevalence, and what is
it called in OMIM?" means fetching ~150 MB of XML, joining four products on ORPHAcode, and walking
a poly-hierarchy by hand.
`orphanet-link` does that join once — into a normalized, read-only SQLite + FTS5 index with
precomputed classification closures — and serves it as MCP tools. A free-text label, a synonym, a
bare ORPHA code, or an external CURIE (OMIM, MONDO, ICD-10/11, UMLS, GARD, MeSH, MedDRA) all
resolve to the same canonical disorder, and every answer is reproducible against a stated Orphanet
release.
## Quick start
The GeneFoundry instance is hosted — no install:
```bash
claude mcp add --transport http orphanet-link https://orphanet-link.genefoundry.org/mcp
```
Locally (Python 3.12+ and [uv](https://docs.astral.sh/uv/)):
```bash
git clone https://github.com/berntpopp/orphanet-link.git
cd orphanet-link
uv sync --group dev
make data-fetch # pull the prebuilt SQLite index from the GitHub Release
make dev # unified REST + MCP → http://127.0.0.1:8000/mcp
claude mcp add --transport http orphanet-link http://127.0.0.1:8000/mcp
```
The server needs a database before it can answer. `make data-fetch` pulls the prebuilt one
published by CI; `make data` builds it from the Orphadata XML instead (~150 MB). Either way the
local server also bootstraps one on first start when none is present — see [Data](docs/data.md).
Production uses the hardened init sidecar to fetch and verify its pinned release before the app
starts with a read-only snapshot.
`make dev` runs `--transport unified`, the **only** mode that serves MCP; `--transport http` is
REST/health-only. `make docker-up` runs the container stack and prints its MCP URL. See
[Deployment](docs/deployment.md).
## Tools
Every tool is read-only, accepts `response_mode` (`minimal` / `compact` / `standard` / `full`,
default `compact`), and returns the fleet's `success` / `_meta` / payload-or-error envelope.
`_meta.next_commands` carries ready-to-call follow-ups — see
[Architecture & the MCP surface](docs/architecture.md).
| Tool | Purpose |
|---|---|
| `get_server_capabilities` | Discovery: tool signatures, response modes, workflows, error taxonomy, limits, Orphanet release |
| `get_diagnostics` | Index status: Orphanet release, disorder counts, schema version, build time, runtime metrics |
| `resolve_disease` | Free-text label, synonym, ORPHA code (`ORPHA:166024` or `166024`), or external xref CURIE → canonical `{orpha_code, name, match_type}` |
| `search_diseases` | FTS over disease names and synonyms; relevance-ranked, paginated, optional obsolete inclusion |
| `get_disease` | Full disorder record: type/group, synonyms, grouped cross-references, classification parents/children, association counts; sparse `fields` projection |
| `get_disease_genes` | Gene associations: symbol, HGNC id, association type and status, source PMIDs, gene xrefs |
| `get_disease_phenotypes` | HPO annotations: HPO id, term name, frequency category; optional frequency filter |
| `get_disease_prevalence` | Epidemiology: prevalence type, class band, numeric ValMoy, geography, validation status, source |
| `get_disease_natural_history` | Age-of-onset categories and inheritance patterns |
| `get_disease_disability` | Functional-consequence annotations: ability categories affected and severity grades |
| `get_disease_classification` | Immediate parents and children in Orphanet's poly-hierarchical classification trees |
| `get_disease_ancestors` | Transitive classification ancestors (precomputed closure), paginated |
| `get_disease_descendants` | Transitive classification descendants (precomputed closure), paginated |
| `map_cross_ontology` | A disorder's cross-references grouped by source (OMIM, MONDO, ICD-10/11, UMLS, GARD, MeSH, MedDRA) with mapping relations |
| `resolve_xref` | External CURIE → matching Orphanet disorder(s), paginated |
| `find_diseases_by_gene` | Reverse lookup: HGNC gene symbol → associated disorders, paginated |
| `find_diseases_by_phenotype` | Reverse lookup: HPO term id → associated disorders, paginated |
| `resolve_disease_batch` | Batch-resolve up to `MAX_BATCH_ITEMS` labels/codes/xrefs; partial success per item |
| `get_disease_batch` | Batch-fetch up to `MAX_BATCH_ITEMS` disease records; partial success per item; sparse `fields` projection |
Leaf names are intentionally **unprefixed**, per the fleet's Tool-Naming Standard v1. Behind
[genefoundry-router](https://github.com/berntpopp/genefoundry-router) this server mounts under the
`orphanet` namespace, so tools surface as `orphanet_<tool>` — e.g. `orphanet_resolve_disease`, the
pinned entry point.
## Data & provenance
**Source** — [Orphadata](https://www.orphadata.com/), the free-access scientific-knowledge file
distribution of Orphanet (INSERM, Paris). Eight English XML products, downloaded directly with no
authentication.
**Refresh** — Orphanet releases bi-annually. CI rebuilds the index weekly and publishes it as a
versioned `data-<release>` GitHub Release (`orphanet.sqlite.gz`). Local development can fetch that
artifact, verify its sha256, and fall back to a local build; production's init sidecar verifies the
declared immutable digest and the app only reads the resulting snapshot. `make data-status` prints
the loaded release. Details: [Data & the build pipeline](docs/data.md).
**Runtime data identity** — a pinned deployment proves which data release it is serving. The init
sidecar records the release tag and digests in `data-identity.json` beside the database; `/health`
rehashes the database on every call and publishes `data_available` plus a `release_identity`
`{expected, actual}` pair, answering 503 if they differ. `python -m orphanet_link.data_probe`
prints a deterministic read-only `{data_schema_version, record_count, query_result_sha256}`
observation of the live store. Details:
[Runtime data identity](docs/deployment.md#runtime-data-identity-runtime-v1).
**Licence** — Orphadata are **CC BY 4.0**. Redistributing a *derived* SQLite database is explicitly
permitted provided attribution is given and changes are indicated.
**Required citation** — also served by the `orphanet://citation` resource:
> "Orphadata Science: Free access data from Orphanet. © INSERM 1999.
> Available on http://sciences.orphadata.com/. Data version [date/version]."
> Changes: "Converted Orphadata XML to a normalized SQLite database."
This derived database is **not an official Orphanet product** and has not been validated by
Orphanet or INSERM.
## Documentation
- [Data & the build pipeline](docs/data.md) — the eight Orphadata products, the data CLI, licensing, and the CI artifact pipeline.
- [Deployment](docs/deployment.md) — transports (and the `--transport http` footgun), Docker, the Host/Origin/CORS boundary, router integration, deploy verification.
- [Configuration](docs/configuration.md) — the `ORPHANET_LINK_*` variables; [`.env.example`](.env.example) is the exhaustive annotated reference.
- [Architecture & the MCP surface](docs/architecture.md) — response envelope, response modes, `orphanet://` resources, error taxonomy, and where the research-use warning lives.
- [`AGENTS.md`](AGENTS.md) — engineering conventions: the two planes, invariants, the determinism contract, package layout.
- [`CHANGELOG.md`](CHANGELOG.md) — release history.
## Contributing
See [`AGENTS.md`](AGENTS.md) for the conventions and the definition of done. `make ci-local` is the
gate — format, lint, line budget, README standard, action pins, mypy, and tests — and it must be
green before merge.
## License
Code: [MIT](LICENSE) © 2026 Bernt Popp.
Data: Orphadata is **CC BY 4.0** © INSERM 1999 — attribution required and changes indicated, as
stated under [Data & provenance](#data--provenance).
TDQS
Scored across 19 tools
Most tools map to clearly distinct data facets, but get_disease can return xrefs, natural history, and other fields that have dedicated tools, and map_cross_ontology overlaps with get_disease's xrefs. The overlap is documented in descriptions, so an agent can still disambiguate, but it is not always obvious which tool is primary.
Tool names follow a consistent snake_case verb_noun pattern: get_disease_*, resolve_*, find_diseases_by_*, search_diseases, and batch variants extend the same pattern predictably. This makes the toolset easy to navigate and guess.
19 tools is on the heavy side for an MCP server, and some redundancy exists: get_disease can compose several facets that also have dedicated tools, and resolve_disease_batch/get_disease_batch add convenience but also surface area. The count is defensible for the breadth of Orphanet data, but feels slightly over-scoped.
The toolset covers the core Orphanet domain thoroughly: disease resolution and search, full disease records with genes/phenotypes/prevalence/natual-history/disability, reverse lookups by gene and phenotype, classification closure, external cross-reference mapping in both directions, and batch operations. There are no obvious dead ends or missing critical operations for a read-only data server.