@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.
Public Hosted Server: https://ensembl.caseyjhand.com/mcp
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
| Tool | Description |
|---|---|
ensembl_list_species | List species supported by Ensembl with display name, common name, assembly, taxon ID, and division |
ensembl_lookup_gene | Resolve a gene by symbol (synonyms and former symbols included) + species or by stable ID to its Ensembl ID, genomic location, biotype, and transcript list |
ensembl_get_sequence | Fetch the DNA, cDNA, CDS, or protein sequence for a gene, transcript, protein, or genomic region |
ensembl_query_region | Find genomic features (genes, transcripts, variants, regulatory elements, exons) overlapping a chromosomal region |
ensembl_predict_variant | Predict functional consequences of a sequence variant using the Ensembl Variant Effect Predictor (VEP) |
ensembl_get_homology | Find orthologs and/or paralogs of a gene across species with percent identity and taxonomy level |
ensembl_get_xrefs | Retrieve cross-database references for a gene — HGNC, UniProt, EntrezGene, OMIM, RefSeq, Reactome, and others |
Resources
| Resource | Description |
|---|---|
ensembl://gene/{id} | Gene record by stable ID (ENSG…) — location, biotype, description, and transcript list |
ensembl://transcript/{id} | Transcript record by stable ID (ENST…) — parent gene, location, biotype, canonical flag, and length |
ensembl://species | Supported Ensembl species for the endpoint default division (vertebrates on the default endpoint) |
ensembl://species/{division} | Supported species in one division (EnsemblVertebrates, EnsemblPlants, EnsemblFungi, EnsemblMetazoa, EnsemblProtists) |
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
| Prompt | Description |
|---|---|
ensembl_gene_dossier | Structured workflow for assembling a complete gene profile: symbol → ID + location → sequence → variants → orthologs → xrefs |
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) andnameContains, 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(default0) andmax_results(default50,0uncapped);totalCountreports every match, andnextOffsetis where the next page starts when more follow
ensembl_lookup_gene tool
- Exactly one of
symbol(+species, defaulthomo_sapiens),id,ids, orsymbols(up to 1,000 submitted items; first 20 distinct trimmed queries processed);expand_transcripts(defaultfalse) adds the transcript list with biotype and canonical flag canonicalTranscriptidentifies the canonical transcript without expansion; transcript-ID lookups also returnparentGeneId. Expanded transcripts includemaneSelectRefSeq 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 asmatchedAlias; a name shared by several genes (MDR1) returnsambiguouswith candidates to retry byid - A single lookup returns
gene(orambiguous); abatchreturns orderedsucceededitems carrying the submittedquery,faileditems withquery,reason(not_found,ambiguouswithcandidates, orunavailable),error, andrecovery, andomittedqueries 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, orchr:start-endwithspecies; at most 10,000,000 bases);typeisgenomic(default),cdna,cds, orprotein—cdnaandcdsfrom a transcript ID only,proteinfrom a transcript or protein ID;expand_5prime/expand_3primeadd flanking bases - Regions take a
:1/:-1strand suffix (1:1000000-1000020:-1returns the reverse complement); the mitochondrion isMTin vertebrates (mitochondrion_genomein Drosophila,MtDNAin C. elegans), andM/chrMare rejected asinvalid_regionwhere they would not resolve to it - Returns a window set by
offset(default0) andmax_length(default10000,0uncapped);lengthis the full sequence length, andtruncated/nextOffsetsay where to resume - Errors:
not_found,type_mismatch(also a protein ID asked forgenomic/cdna/cds, or a non-coding transcript asked forprotein/cds),invalid_species,missing_species,invalid_region(also an id with a colon that is not a valid region)
ensembl_query_region tool
regionasspecies:chr:start-end, orchr:start-endwithspecies(at most 5,000,000 bases; reversed coordinates are rejected; mitochondrion naming as inensembl_get_sequence);featuredefaults to["gene"]and also acceptstranscript,variation,regulatory,exon; optional case-sensitivebiotypefilter (protein_coding,lncRNA). An empty filtered result suggests removing the filter without inferring the locus is intergenicmax_resultscaps the list (default100,0uncapped) whiletotalCountreports the true count;assemblyNamenames the coordinates' assembly, and exon rows carryparentIdandrank. Errors:invalid_region,invalid_species,missing_species
ensembl_predict_variant tool
variantas 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, optionalcanonical/maneSelect, HGVS and available SIFT/PolyPhen prediction-score pairs; records retainalleleString - Colocated
clinicalSignificanceis aggregate, not a classification of the submitted allele.clinicalSignificanceByAllelepreserves explicit per-allele terms andclinicalSignificanceReferenceAlleleidentifies their reference allele; absent annotations remain omitted max_transcript_consequencesandmax_pubmed_ids_per_variant(default10each,0uncapped;include_all_colocated_pubmedlifts the PubMed cap) bound the result, whiletranscriptConsequencesTotalandpubmedTotalreport the full counts
ensembl_get_homology tool
- Exactly one of
symbol(+species, defaulthomo_sapiens) orid;typeisorthologues(default),paralogues, orall; optionaltarget_speciesfilter - A
target_speciesoutside the comparison Ensembl runs for the source species (a fly gene tohomo_sapiens) is answered from Ensembl's pan-taxonomic comparison when it holds both species, with a notice saying so max_resultscaps the list (default25,0uncapped) whiletotalCountreports the true count. Errors:not_found,invalid_species(unknownspeciesortarget_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
targetProteinIdforensembl_get_sequencewithtype:"protein". OptionaltargetSymbolcomes 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; optionaldbnamefilter (e.g.HGNC,Uniprot_gn,Uniprot/SWISSPROTfor the reviewed UniProt accession on the translation,EntrezGene,MIM_GENE,RefSeq_mRNA,Reactome_genefor gene pathways,Reactomefor translation pathways,GO)- Default
all_levels:falsequeries the requested object's level. Setall_levels:trueto include a gene's transcript and translation references, independently ofdbname; for example, gene-levelRefSeq_mRNAmay need this or a transcript ID. Empty lists remain successful withtotalCount:0. Errors:not_found - Each reference carries how Ensembl assigned it (
infoType:DIRECT,DEPENDENT,PROJECTION, …;infoText: its source, such asUniProtor the ortholog a projection came from) and its evidence codes (linkageTypes, GO evidence such asIDAorIEA) when supplied, soGOandProjected GOrows, and experimental and electronic annotations, stay distinct on both surfaces
ensembl://gene/{id} resource
idis 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
idis 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, andtruncated; a truncated body'snoticepoints toensembl_list_species(offset,max_results) for the rest - For a named division, read
ensembl://species/{division}instead
ensembl://species/{division} resource
divisionrequired:EnsemblVertebrates,EnsemblPlants,EnsemblFungi,EnsemblMetazoa, orEnsemblProtists- Returns that division's first 50 species by internal name — internal name, display name, assembly, taxon ID, and division — with
totalCount,shown, andtruncated; a truncated body'snoticepoints toensembl_list_species(division,offset,max_results) for the rest
ensembl_gene_dossier prompt
- Arguments:
gene_symbolrequired;speciesoptional (defaulthomo_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/idandPOST /lookup/symbol/{species}to avoid N+1 round trips - GRCh37 legacy support via
ENSEMBL_BASE_URL— point the entire server athttps://grch37.rest.ensembl.orgfor 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_sequencereturns bounded windows (10,000 characters by default) with the full length and anextOffsetto continueensembl_list_speciesis the discovery step for internal species names, 50 per page with anextOffset- Xref IDs from
ensembl_get_xrefsare described as inputs for protein, disease, pathway, and structure servers; theensembl_gene_dossierprompt chains 6 of the 7 tools
Getting started
Connect to the hosted instance, no install:
claude mcp add --transport http ensembl-mcp-server https://ensembl.caseyjhand.com/mcp
Or run it locally:
claude mcp add ensembl-mcp-server -- npx -y @cyanheads/ensembl-mcp-server
For other clients, add this to the MCP config file:
{
"mcpServers": {
"ensembl-mcp-server": {
"command": "npx",
"args": ["-y", "@cyanheads/ensembl-mcp-server"]
}
}
}
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.
| Variable | Description | Default |
|---|---|---|
ENSEMBL_BASE_URL | Ensembl REST API base URL. Override for GRCh37 (https://grch37.rest.ensembl.org) or a local mirror. | https://rest.ensembl.org |
MCP_TRANSPORT_TYPE | Transport: stdio or http | stdio |
MCP_HTTP_PORT | HTTP server port | 3010 |
MCP_HTTP_ENDPOINT_PATH | HTTP endpoint path | /mcp |
MCP_SESSION_MODE | HTTP session mode: auto, stateful, or stateless | stateless |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.) | info |
LOGS_DIR | Directory for log files (Node.js only) | <project-root>/logs |
LOG_TOOL_FAILURE_PAYLOADS | Log each failed tool call's arguments and result, redacted by key name and capped at LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES (default 16384). A secret inside a free-form value is not redacted. | false |
OTEL_ENABLED | Enable OpenTelemetry | false |
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:
git clone https://github.com/cyanheads/ensembl-mcp-server.git cd ensembl-mcp-server && bun install cp .env.example .env # optional: set ENSEMBL_BASE_URL for GRCh37 or a mirror -
Build and run:
# One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:http -
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec
Docker
docker run --rm -p 3010:3010 ghcr.io/cyanheads/ensembl-mcp-server:latest
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
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools/resources/prompts and inits services |
src/config | Server-specific environment variable parsing and validation with Zod |
src/mcp-server/tools | Tool definitions (*.tool.ts) — 7 tools |
src/mcp-server/resources | Resource definitions (*.resource.ts) — gene, transcript, species |
src/mcp-server/prompts | Prompt definitions (*.prompt.ts) — gene dossier workflow |
src/services/ensembl | Ensembl REST API client — HTTP, rate-limit handling, retry, error normalization |
tests/ | Unit and integration tests mirroring src/ |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storage - Register new tools and resources in the
createApp()arrays insrc/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:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.