Federated protein structure & annotation across experimental (PDB) and predicted (AlphaFold) models.
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
Seven tools spanning the structure-research arc — discover, fetch, find homologs, track ligands, compare, profile the corpus, and annotate — over experimental (PDB) and predicted (AlphaFold) structures from one surface:
| Tool | Description |
|---|---|
protein_search_structures | Search experimental and predicted structures by free text, sequence, or organism/method/resolution filters, with optional facet breakdowns. |
protein_get_structure | Fetch metadata and coordinate-file URLs by ID — experimental (PDB), predicted (AlphaFold), or best-available — with batch partial success and optional coordinate inlining. |
protein_find_similar | Find sequence homologs (RCSB mmseqs2) or fold homologs (Foldseek) from a sequence, PDB ID, or UniProt accession. |
protein_track_ligands | Resolve ligand names/formulas to component IDs, find structures containing a ligand, or map binding-site residues. |
protein_compare_structures | Structurally align multiple structures (TM-align / jFATCAT) to a reference or as a full pairwise matrix. |
protein_analyze_collection | Profile the PDB into distributions and trends with server-side facets — counts, histograms, timelines, and cross-tabs. |
protein_get_annotations | Fetch UniProt features and natural variants plus InterPro domain/family memberships with GO terms. |
protein_search_structuresFederated search across experimental (PDB) and predicted (computed-model) structures via RCSB Search v2.
content_type scopes the search to experimental, predicted, or all — the default all is a genuine union of both universes, so computed models appear alongside PDB entriessource; experimental hits are enriched with title, method, resolution, and organism, while computed models carry the UniProt accession parsed from their IDfacets return a method / organism / release-year breakdown alongside the hits at no extra call, each reporting how many matches carry no value for that dimension; each dimension may be listed onceprotein_get_structureprotein_get_structureFetch structures with metadata and coordinate-file URLs, resolving across providers by source.
source: experimental takes PDB entry IDs, batched in one RCSB GraphQL call; it also resolves the computed-model IDs search returns (AF_* / MA_*), which come back as source: predicted credited to their modelling providersource: predicted takes UniProt accessions and returns the AlphaFold model with pLDDT/PAE confidencesource: best_available takes UniProt accessions and returns the top federated model (experimental if one exists, else the best prediction)failed[], not a batch-level errorinclude_coords inlines coordinate content; when a batch overflows the response budget it returns a per-structure size outline, so you can re-call with sections: [ids] for specific structuresattribution block naming the upstream data licenses and citations (see Upstream data licensing)protein_find_similarFind structurally or evolutionarily related proteins, by sequence or by fold.
by: sequence runs a synchronous RCSB mmseqs2 search; by: structure runs an asynchronous Foldseek search against experimental and predicted databasespdb100 + afdb50; override via databases (e.g. afdb-swissprot, BFVD)status: computing with a ticketId — re-call with ticket_id set to that value to poll the same job instead of resubmittingprotein_track_ligandsLigand discovery and binding-site analysis across the PDB.
mode: find_ligand resolves a name or formula to chemical component IDs with formula, weight, SMILES, and InChIKeymode: structures_with_ligand returns PDB entries containing a ligand by exact component IDmode: binding_site returns the protein residues lining a ligand's pocket in a structure, with contact distancesprotein_compare_structuresStructural alignment of multiple structures (up to the configured PROTEIN_MAX_COMPARE_STRUCTURES cap) via the RCSB Structural Comparison service.
tm-align, fatcat-rigid, fatcat-flexiblereference: first aligns every structure to the first; reference: all_pairs computes the full pairwise matrixchain restricts the alignment to a single chainstructures[] is compared once — the repeat would only add a self-alignment and a mirrored pair, which the resume mechanism cannot tell apart from the originalstatus: computing with its job uuid, and a failed pair degrades its row without sinking the others{ a, b, uuid } entry in resume[] (copied from a prior response's pairs[]) to poll a computing pair's job instead of resubmittingmodeledResidues and coverage — each a [a, b] tuple, with coverage a 0–100 percentage of that structure's own modeled-residue countprotein_analyze_collectionProfile the PDB into distributions and trends over an optional scoping query — backed by RCSB's server-side facet engine (one call, compact buckets, no row pull).
method, organism, polymer_type, resolution, release_year, or molecular_weightgroup_by dimension for a breakdown, or two distinct dimensions for a cross-tab (the first nests the second); a repeated dimension is rejectedinterval sets the bin width for value histograms or the period for date histograms (year / month / quarter)query, organism, method, or max_resolution; content_type selects the structure universebucket_limit caps buckets per dimension level, not per response — a cross-tab applies it separately to the parent dimension and to the nested child inside each parent bucket, so up to bucket_limit × (1 + bucket_limit) buckets come back. Each level flags its own truncation, and bucketsReturned gives the realized totalmissingValueCount — matches in scope carrying no value for that attribute, which therefore fall in no bucket (a resolution breakdown does not cover NMR entries, and neither method nor resolution covers computed models)protein_get_annotationsSequence and functional annotation for a protein.
ambiguity. Pass chain (an author chain ID, e.g. A) to select a specific oneinclude scopes which annotation classes are fetched: features, domains, variants, or allattribution block naming the upstream data licenses and citations (see Upstream data licensing)| Type | Name | Description |
|---|---|---|
| Resource | pdb://{entry_id} | Experimental structure summary for a PDB entry — title, method, resolution, organism, chains, and bound ligands. |
| Resource | af://{uniprot} | Predicted-structure summary for a UniProt accession from AlphaFold DB — mean pLDDT, confidence-band fractions, model URLs, and version. |
All resource data is also reachable via tools — pdb://{entry_id} mirrors protein_get_structure for source: experimental, and af://{uniprot} mirrors it for source: predicted. Many MCP clients are tool-only and don't surface resources; the summaries remain reachable through the tools.
Built on @cyanheads/mcp-ts-core:
none, jwt, oauthin-memory, filesystem, Supabase, Cloudflare KV/R2/D1Protein-specific:
ticketId / per-pair uuid) instead of blocking — re-call with ticket_id or a resume[] entry to poll the same job instead of resubmittingAgent-friendly output:
source (experimental / predicted), the engine and database that produced it, and effective-query / total-count echoes so agents can reason about coveragefailed[], per-pair status) instead of failing the whole request, each with actionable recovery textsource and status unions, computing results with resume tickets, and budget-overflow outlines let callers branch on data, not string parsingA public instance is available at https://protein.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"protein": {
"type": "streamable-http",
"url": "https://protein.caseyjhand.com/mcp"
}
}
}
Add the following to your MCP client configuration file. No API key is required — every upstream provider is keyless.
{
"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
All upstream providers are keyless, so the server runs out of the box with no configuration. Every variable below is optional.
| Variable | Description | Default |
|---|---|---|
PROTEIN_ASYNC_POLL_TIMEOUT_MS | Max wall-clock to poll an async job (alignment / Foldseek) before returning a computing result. | 30000 |
PROTEIN_MAX_BATCH_IDS | Cap on IDs accepted by protein_get_structure in one batch (1–100). | 25 |
PROTEIN_MAX_COMPARE_STRUCTURES | Cap on structures per protein_compare_structures call (2–25). | 10 |
PROTEIN_FACET_BUCKET_CAP | Default cap on buckets per protein_analyze_collection dimension (1–500). | 50 |
PROTEIN_FANOUT_CONCURRENCY | Max concurrent upstream requests for per-ID / per-pair fan-out (1–16). | 5 |
RCSB_SEARCH_BASE_URL | Base URL for the RCSB Search API v2. | https://search.rcsb.org |
ALPHAFOLD_BASE_URL | Base URL for the AlphaFold Protein Structure Database API. | https://alphafold.ebi.ac.uk |
FOLDSEEK_BASE_URL | Base URL for the Foldseek structural-similarity search service. | https://search.foldseek.com |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for the HTTP server. | 3010 |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
OTEL_ENABLED | Enable OpenTelemetry instrumentation. | false |
See .env.example for the full list of provider base-URL overrides and tuning limits.
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 -e MCP_TRANSPORT_TYPE=http -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/resources and inits the provider services. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). |
src/mcp-server/resources | Resource definitions (*.resource.ts). |
src/services | Provider service layer — RCSB, AlphaFold, 3D-Beacons, UniProt, InterPro, Foldseek, and shared HTTP/identifier 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.tsIssues and pull requests are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Structure and annotation data comes from public upstream databases, each under its own license. protein_get_structure and protein_get_annotations carry an attribution block on every response — the license, citation, and homepage for each source that contributed to that specific response — so the attribution obligation travels with the data to downstream consumers rather than living only here. CC BY / CC BY-SA sources require attribution on redistribution; CC0 sources are citation-only (attribution encouraged, not required).
| 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 federates predicted models through 3D-Beacons, so the attribution block credits the actual contributing provider (AlphaFold DB, SWISS-MODEL, BFVD, …); a provider without a curated license entry carries a See provider terms fallback pointing back to 3D-Beacons rather than a fabricated license. InterPro's own domain/family classifications are CC0; the GO terms carried alongside them are separately CC BY 4.0, so each is credited independently only when it actually contributes. Full citations for each source travel in the attribution block of the relevant tool responses. This covers upstream data licensing — the server's own code is licensed separately (see License).
Apache-2.0 — see LICENSE for details.