Deprecated. Deprecated by the publisher: Moved to @cyanheads/protein-mcp-server
MCP Server for 3D protein structural data retrieval & analysis from RCSB PDB, PDBe, and UniProt.
Runs on your machine from its package; the steps are below.
At most: only reads or looks up information.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
npx -y protein-mcp-server
MCP client config
{
"mcpServers": {
"protein": {
"command": "npx",
"args": [
"-y",
"protein-mcp-server"
]
}
}
}Run
npx -y protein-mcp-server
MCP client config
{
"mcpServers": {
"protein": {
"command": "npx",
"args": [
"-y",
"protein-mcp-server"
]
}
}
}Federated protein structure & annotation across experimental (PDB) and predicted (AlphaFold) models via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://protein.caseyjhand.com/mcp
Experimental (PDB) and predicted (AlphaFold) protein structures behind one surface. Search, fetch, align, and annotate structures and their ligands across RCSB, AlphaFold DB, 3D-Beacons, UniProt, InterPro, and Foldseek. No API key required.
| Tool | Description |
|---|---|
protein_search_structures | Search experimental and predicted structures by text, sequence, or organism/method/resolution, with optional facets |
protein_get_structure | Fetch metadata and coordinate URLs for experimental, predicted, or best-available structures |
protein_find_similar | Find sequence homologs (RCSB mmseqs2) or fold homologs (Foldseek) |
protein_track_ligands | Resolve ligands to component IDs, find structures containing one, or map its binding site |
protein_compare_structures | Align structures with TM-align or jFATCAT, against a reference or all pairs |
protein_analyze_collection | Profile the PDB as counts, histograms, timelines, and cross-tabs |
protein_get_annotations | Fetch UniProt features and variants plus InterPro domains with GO terms |
| Resource | Description |
|---|---|
pdb://{entry_id} | Experimental structure summary for a PDB entry |
af://{uniprot} | AlphaFold DB predicted-structure summary for a UniProt accession |
Both resources are also reachable through protein_get_structure, for clients that don't surface resources.
protein_search_structures toolquery, sequence (an mmseqs2 search; min_identity / max_evalue apply only with it), organism, method, or max_resolution; content_type is experimental, predicted, or all (default); up to 100 hits per page (default 25) via limit / startsequence needs at least 10 residues; whitespace is stripped and case normalized, and an invalid one fails as invalid_sequence. Each hit names its source and an entry id you can pass to other toolsfacets (method, organism, polymer_type, resolution, release_year, molecular_weight) add a breakdown capped at PROTEIN_FACET_BUCKET_CAP buckets per dimensionprotein_get_structure toolsource: experimental (default) takes PDB entry IDs and the AF_* / MA_* computed-model IDs search returns (these come back as source: predicted); predicted takes UniProt accessions for AlphaFold models with pLDDT/PAE; best_available takes UniProt accessions and returns the highest-resolution experimental structure, else the best prediction. Up to PROTEIN_MAX_BATCH_IDS IDs per call (default 25)failed[], and requested / processed show IDs dropped past the cap; the call fails as all_failed when nothing resolves and mixed_id_types when the IDs don't fit source. Records include coordinateUrls for the files that existinclude_coords inlines coordinate text within a size budget; an over-budget batch returns an overflow outline to re-call with sections: [ids]protein_find_similar toolby: sequence runs a synchronous RCSB mmseqs2 search from sequence, pdb_id, or uniprot, filtered by max_evalue / min_identity; by: structure runs an async Foldseek search from pdb_id or uniprot against pdb100 + afdb50 unless databases says otherwise. A field only the other mode reads fails as mode_mismatched_field; up to 100 hits per page (default 25)protein_search_structures, and a direct sequence wins over identifiers. Hits name their source (and database for structure hits), ranked by score; responses report totalCount and nextStartstatus: computing with a ticketId; re-call with ticket_id to resume it. A multichain structure is searched one chain at a time via query (0-based, default 0) out of queryCountprotein_track_ligands toolmode is find_ligand (query: a name or formula), structures_with_ligand (comp_id), or binding_site (pdb_id, optional comp_id); a missing mode input fails as missing_param. limit is 1–100 (default 25), and start pages the last two modesfind_ligand returns component IDs with formula, weight, SMILES, and InChIKey, ranked by depositionCount, with totalCount and candidatesConsidered showing when the candidate pool was cut short; structures_with_ligand lists entries highest resolution firstbinding_site returns pocket residues with contact distances, numbered in both label (asymId, seqId) and author (authAsymId, authSeqId) namespaces; it works on experimental structures onlyprotein_compare_structures toolPROTEIN_MAX_COMPARE_STRUCTURES (default 10, max 25), each a pdb_id with an optional label-namespace chain; method is tm-align (default), fatcat-rigid, or fatcat-flexible; reference is first (default) or all_pairs; timeout_s (5–120) sets the per-pair poll budgetstatus (complete, computing, failed), tmScore, rmsd, alignedResidues, modeledResidues, and coverage; TM-score is normalized by a's length, so reversing a pair changes it. similarityScore and sequenceSimilarity appear when the upstream supplies themuuid; re-call with { a, b, uuid } in resume[] to poll it instead of resubmitting. A mismatched method or pair fails as resume_method_mismatch or resume_job_mismatchprotein_analyze_collection toolgroup_by takes one of method, organism, polymer_type, resolution, release_year, molecular_weight, or two distinct ones for a cross-tab; scope with query, organism, method, max_resolution, and content_type (default experimental). interval sets a numeric bin width for resolution / molecular_weight or "year" for release_yeartotal and bucketsReturned; buckets carry label and count (numeric bins add rangeFrom / rangeTo), and every dimension reports missingValueCountbucket_limit (1–500, default PROTEIN_FACET_BUCKET_CAP) caps each dimension separately; truncated and notice say where results were cutprotein_get_annotations tooluniprot accession, or a pdb_id resolved through its UniProt cross-reference (author chain picks one accession); include is features, domains, variants, or all (default); limit pages each class separately (1–200, default 50), with independent zero-based features_offset, variants_offset, and domains_offset (default 0)ambiguityfeaturesTotal, variantsTotal, or domainsTotal; a page with more records sets truncated and names the next offset in noticemissing_identifier, invalid_accession, no_uniprot_mapping, or chain_not_foundpdb://{entry_id} resourceentry_id is a PDB entry ID (e.g. 4HHB); returns application/json with title, methods, resolution, organisms, ligands, and polymerEntities carrying both authAsymIds and labelAsymIdsprotein_get_structure with source: experimentalaf://{uniprot} resourceuniprot is a UniProt accession or an AlphaFold DB entry ID (e.g. AF-P69905-F1); returns application/json with meanPlddt, confidenceBuckets, cifUrl / pdbUrl / bcifUrl, and modelVersionprotein_get_structure with source: predictedBuilt 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.
PDB / AlphaFold-specific:
PROTEIN_ASYNC_POLL_TIMEOUT_MS (default 30 s), then hand back a resume handle (ticketId, per-pair uuid) instead of blockingauthAsymIds feed protein_get_annotations chain, labelAsymIds feed protein_compare_structures chainAgent-friendly output:
source (experimental / predicted) and the engine or database behind it, and protein_get_structure and protein_get_annotations carry an attribution block (see Upstream data licensing)failed[] rows and per-pair status keep one bad ID or pair from failing the requesttotalCount and nextStart, a start past the end is flagged in notice separately from zero matches, and every advisory joins into that one noticeA public instance is available at https://protein.caseyjhand.com/mcp, no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"protein-mcp-server": {
"type": "streamable-http",
"url": "https://protein.caseyjhand.com/mcp"
}
}
}
Add the following to your MCP client configuration file. No API key is needed.
{
"mcpServers": {
"protein-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/protein-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"protein-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/protein-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"protein-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/protein-mcp-server:latest"]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
git clone https://github.com/cyanheads/protein-mcp-server.git
cd protein-mcp-server
bun install
cp .env.example .env
# every variable has a working default
Every variable is optional.
| Variable | Description | Default |
|---|---|---|
PROTEIN_ASYNC_POLL_TIMEOUT_MS | Poll budget for an async alignment or Foldseek job before it returns computing (min 1000). | 30000 |
PROTEIN_MAX_BATCH_IDS | Max IDs per protein_get_structure call (1–100). | 25 |
PROTEIN_MAX_COMPARE_STRUCTURES | Max structures per protein_compare_structures call (2–25). | 10 |
PROTEIN_FACET_BUCKET_CAP | Buckets per dimension for protein_search_structures facets, and the protein_analyze_collection default (1–500). | 50 |
PROTEIN_FANOUT_CONCURRENCY | Max concurrent upstream requests for per-ID and per-pair fan-out (1–16). | 5 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto. Overrides the stateless mode the server declares in code. | stateless |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
OTEL_ENABLED | Enable OpenTelemetry. | false |
Each upstream's base URL can also be pointed at a mirror: RCSB_SEARCH_BASE_URL, RCSB_DATA_BASE_URL, RCSB_FILES_BASE_URL, RCSB_MODELS_BASE_URL, RCSB_ALIGNMENT_BASE_URL, BEACONS_BASE_URL, ALPHAFOLD_BASE_URL, MODELARCHIVE_BASE_URL, FOLDSEEK_BASE_URL, UNIPROT_BASE_URL, INTERPRO_BASE_URL. See .env.example for their defaults and the full list of optional overrides.
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 build -t protein-mcp-server .
docker run --rm -p 3010:3010 protein-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/protein-mcp-server. OpenTelemetry peer dependencies are installed by default; build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point: registers tools and resources, inits the provider services. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts) and shared schemas (_schemas.ts). |
src/mcp-server/resources | Resource definitions (*.resource.ts). |
src/services | Upstream clients: rcsb/ (search, data, facets), alphafold/, beacons/ (3D-Beacons), uniprot/ (UniProt, InterPro, GO), alignment/ (RCSB Structural Comparison), foldseek/, and shared/ HTTP, concurrency, identifier, and attribution helpers. |
tests/ | Unit and integration tests mirroring src/. |
See CLAUDE.md/AGENTS.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagesrc/mcp-server/*/definitions/index.ts and register them in the createApp() arrays in src/index.tsStructure and annotation data comes from public databases, each under its own license. protein_get_structure and protein_get_annotations return an attribution block with the license, citation, and homepage of every source that contributed to that response. CC BY and CC BY-SA sources require attribution on redistribution; CC0 sources ask only for citation.
| Source | Contributes to | License |
|---|---|---|
| RCSB PDB | protein_get_structure — experimental records | CC0 1.0 Universal |
| AlphaFold DB | protein_get_structure — predicted models | CC BY 4.0 |
| ModelArchive | protein_get_structure — MA_* computed models | CC BY 4.0 |
| SWISS-MODEL | protein_get_structure — best_available models | CC BY-SA 4.0 |
| BFVD | protein_get_structure — best_available models | CC BY 4.0 |
| UniProt | protein_get_annotations | CC BY 4.0 |
| InterPro | protein_get_annotations — domain/family data | CC0 1.0 Universal |
| GO | protein_get_annotations — GO terms | CC BY 4.0 |
best_available models arrive through 3D-Beacons, so the block credits the provider that actually supplied the model; a provider with no curated entry gets a See provider terms fallback instead of a guessed license. InterPro and GO are credited separately, each only when it contributes. This table covers upstream data; the server's own code is licensed separately (see License).
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.