@cyanheads/ensembl-mcp-server
Look up genes, fetch sequences, predict variant consequences, find orthologs, and retrieve cross-database xrefs from Ensembl REST via MCP. STDIO or Streamable HTTP.
7 Tools • 4 Resources • 1 Prompt
Overview
Gene, sequence, and variant data for vertebrates and other model organisms from the Ensembl REST API. Look up genes, fetch sequences, predict variant consequences, find orthologs, and cross-reference external databases.
Tools
Resources
The species resources return the first 50 species by internal name; the ensembl_list_species tool pages through the rest and filters by name.
Prompts
Capability reference
Ensembl's stable-ID endpoints answer a versioned ID (ENSG00000139618.19) as not found, so every tool and resource that takes an Ensembl stable ID drops a trailing .N from an ENS… ID before the request and returns the current version. HGVS input to ensembl_predict_variant (ENST00000380152.8:c.2T>A) keeps its version, and IDs from other sources (AT3G52430.1) are forwarded unchanged.
ensembl_list_species tool
- Optional
division (EnsemblVertebrates, EnsemblPlants, EnsemblFungi, EnsemblMetazoa, EnsemblProtists; omitted, the endpoint default — vertebrates, ~356 species on GRCh38) and nameContains, a substring match against name, display name, and common name
- Returns the internal name every other tool expects (
homo_sapiens), plus display name, common name, taxon ID, assembly, and division
- Sorted by internal name and paged by
offset (default 0) and max_results (default 50, 0 uncapped); totalCount reports every match, and nextOffset is where the next page starts when more follow
ensembl_lookup_gene tool
- Exactly one of
symbol (+ species, default homo_sapiens), id, ids, or symbols (up to 1,000 submitted items; first 20 distinct trimmed queries processed); expand_transcripts (default false) adds the transcript list with biotype and canonical flag
canonicalTranscript identifies the canonical transcript without expansion; transcript-ID lookups also return parentGeneId. Expanded transcripts include maneSelect RefSeq accessions when available (BRCA2: ENST00000380152.8, NM_000059.4)
- A symbol that is not a current gene name falls back to synonyms and former symbols (
HER2 → ERBB2), reported as matchedAlias; a name shared by several genes (MDR1) returns ambiguous with candidates to retry by id
- A single lookup returns
gene (or ambiguous); a batch returns ordered succeeded items carrying the submitted query, failed items with query, reason (not_found, ambiguous with candidates, or unavailable), error, and recovery, and omitted queries past the processing cap. Omitted queries are never requested or reported as failed; resubmit them separately. Errors: not_found, invalid_species, no_input, conflicting_input
ensembl_get_sequence tool
- A stable ID (
ENSG…/ENST…/ENSP…) or a region (species:chr:start-end, or chr:start-end with species; at most 10,000,000 bases); type is genomic (default), cdna, cds, or protein — cdna and cds from a transcript ID only, protein from a transcript or protein ID; expand_5prime/expand_3prime add flanking bases
- Regions take a
:1/:-1 strand suffix (1:1000000-1000020:-1 returns the reverse complement); the mitochondrion is MT in vertebrates (mitochondrion_genome in Drosophila, MtDNA in C. elegans), and M/chrM are rejected as invalid_region where they would not resolve to it
- Returns a window set by
offset (default 0) and max_length (default 10000, 0 uncapped); length is the full sequence length, and truncated/nextOffset say where to resume
- Errors:
not_found, type_mismatch (also a protein ID asked for genomic/cdna/cds, or a non-coding transcript asked for protein/cds), invalid_species, missing_species, invalid_region (also an id with a colon that is not a valid region)
ensembl_query_region tool
region as species:chr:start-end, or chr:start-end with species (at most 5,000,000 bases; reversed coordinates are rejected; mitochondrion naming as in ensembl_get_sequence); feature defaults to ["gene"] and also accepts transcript, variation, regulatory, exon; optional case-sensitive biotype filter (protein_coding, lncRNA). An empty filtered result suggests removing the filter without inferring the locus is intergenic
max_results caps the list (default 100, 0 uncapped) while totalCount reports the true count; assemblyName names the coordinates' assembly, and exon rows carry parentId and rank. Errors: invalid_region, invalid_species, missing_species
ensembl_predict_variant tool
variant as HGVS (transcript-relative or genomic), region+allele (chr:start:end:strand/allele), or a dbSNP rsID
- Returns the most severe consequence, per-transcript impact (HIGH/MODERATE/LOW/MODIFIER), and colocated known variants with clinical significance. Errors:
invalid_notation, not_found, invalid_species
- Requests HGVS, canonical and MANE annotations on every input route. Consequences retain
variantAllele, optional canonical/maneSelect, HGVS and available SIFT/PolyPhen prediction-score pairs; records retain alleleString
- Colocated
clinicalSignificance is aggregate, not a classification of the submitted allele. clinicalSignificanceByAllele preserves explicit per-allele terms and clinicalSignificanceReferenceAllele identifies their reference allele; absent annotations remain omitted
max_transcript_consequences and max_pubmed_ids_per_variant (default 10 each, 0 uncapped; include_all_colocated_pubmed lifts the PubMed cap) bound the result, while transcriptConsequencesTotal and pubmedTotal report the full counts
ensembl_get_homology tool
- Exactly one of
symbol (+ species, default homo_sapiens) or id; type is orthologues (default), paralogues, or all; optional target_species filter
- A
target_species outside the comparison Ensembl runs for the source species (a fly gene to homo_sapiens) is answered from Ensembl's pan-taxonomic comparison when it holds both species, with a notice saying so
max_results caps the list (default 25, 0 uncapped) while totalCount reports the true count. Errors: not_found, invalid_species (unknown species or target_species), target_not_compared (a target outside the source species' comparison that the pan-taxonomic comparison cannot answer), no_input, conflicting_input
- Each row retains optional
targetProteinId for ensembl_get_sequence with type:"protein". Optional targetSymbol comes from a lookup of the returned rows' distinct gene IDs, in chunks of at most 1,000, with one five-second budget bounded by the call's remaining deadline and no retries. Missing names or failed lookups retain every primary homolog with a notice; cancellation still aborts
ensembl_get_xrefs tool
id (ENSG…/ENST…/ENSP…) required; optional dbname filter (e.g. HGNC, Uniprot_gn, Uniprot/SWISSPROT for the reviewed UniProt accession on the translation, EntrezGene, MIM_GENE, RefSeq_mRNA, Reactome_gene for gene pathways, Reactome for translation pathways, GO)
- Default
all_levels:false queries the requested object's level. Set all_levels:true to include a gene's transcript and translation references, independently of dbname; for example, gene-level RefSeq_mRNA may need this or a transcript ID. Empty lists remain successful with totalCount:0. Errors: not_found
- Each reference carries how Ensembl assigned it (
infoType: DIRECT, DEPENDENT, PROJECTION, …; infoText: its source, such as UniProt or the ortholog a projection came from) and its evidence codes (linkageTypes, GO evidence such as IDA or IEA) when supplied, so GO and Projected GO rows, and experimental and electronic annotations, stay distinct on both surfaces
ensembl://gene/{id} resource
id is a gene stable ID (ENSG…); a version suffix is optional and dropped
- Returns location, biotype, description, and transcript list. Errors:
not_found
ensembl://transcript/{id} resource
id is a transcript stable ID (ENST…); a version suffix is optional and dropped
- Returns parent gene, location, biotype, canonical flag, and length. Errors:
not_found
ensembl://species resource
- No parameters — covers the endpoint default division (vertebrates, ~356 species on the default GRCh38 endpoint)
- Returns the first 50 species by internal name with
totalCount, shown, and truncated; a truncated body's notice points to ensembl_list_species (offset, max_results) for the rest
- For a named division, read
ensembl://species/{division} instead
ensembl://species/{division} resource
division required: EnsemblVertebrates, EnsemblPlants, EnsemblFungi, EnsemblMetazoa, or EnsemblProtists
- Returns that division's first 50 species by internal name — internal name, display name, assembly, taxon ID, and division — with
totalCount, shown, and truncated; a truncated body's notice points to ensembl_list_species (division, offset, max_results) for the rest
ensembl_gene_dossier prompt
- Arguments:
gene_symbol required; species optional (default homo_sapiens)
- Sequences a 7-step workflow: resolve the gene → fetch the protein sequence → find variants in the locus → predict variant consequences → find model-organism orthologs → get external database IDs → synthesize the dossier
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Ensembl-specific:
- Keyless REST API — no API key required; Ensembl REST is fully public at 55,000 req/hr
- Retries 429 responses (honoring
Retry-After) and transient 5xx errors
- Upstream calls time out at 180 s per attempt and 240 s per call, so a stalled Ensembl fails instead of hanging
- Batch lookups use
POST /lookup/id and POST /lookup/symbol/{species} to avoid N+1 round trips
- GRCh37 legacy support via
ENSEMBL_BASE_URL — point the entire server at https://grch37.rest.ensembl.org for clinical workflows on the older assembly
- All coordinate-bearing responses echo the assembly name so agents never see a bare genomic position without assembly context
Agent-friendly output:
ensembl_get_sequence returns bounded windows (10,000 characters by default) with the full length and a nextOffset to continue
ensembl_list_species is the discovery step for internal species names, 50 per page with a nextOffset
- Xref IDs from
ensembl_get_xrefs are described as inputs for protein, disease, pathway, and structure servers; the ensembl_gene_dossier prompt chains 6 of the 7 tools
Getting started
Connect to the hosted instance, no install:
Or run it locally:
For other clients, add this to the MCP config file:
Ensembl REST is public, so no API key is needed. Set ENSEMBL_BASE_URL to https://grch37.rest.ensembl.org to query the GRCh37 assembly instead of GRCh38. For Docker or a source build, see Running the server.
Configuration
All configuration is validated at startup via Zod schemas in src/config/server-config.ts.
See .env.example for the full list of optional overrides.
Running the server
Local development
Requires Bun v1.4.0 or higher (or Node.js v24+).
-
Clone and install:
-
Build and run:
-
Run checks and tests:
Docker
The image defaults to HTTP transport, stateless session mode, and logs to /var/log/ensembl-mcp-server. To run it over stdio from an MCP client, use "command": "docker" with "args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/ensembl-mcp-server:latest"]. To build locally, docker build -t ensembl-mcp-server .; OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catch in tool logic
- Use
ctx.log for request-scoped logging, ctx.state for tenant-scoped storage
- Register new tools and resources in the
createApp() arrays in src/index.ts
- Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
License
Apache-2.0 — see LICENSE for details.