@cyanheads/pubchem-mcp-server
Search the PubChem chemical database for compounds, properties, safety data, bioactivity, cross-references, and entity summaries via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://pubchem.caseyjhand.com/mcp
Overview
Chemical compounds and bioassays from PubChem. Search compounds by name, formula, or structure; fetch properties, safety data, bioactivity, drug-food interactions, molecular targets, and 3D structures; and find bioassays by biological target.
Tools
| Tool | Description |
|---|---|
pubchem_search_compounds | Search for compounds by name, SMILES, InChI, InChIKey, formula, substructure, superstructure, or 2D similarity, or list the compounds a bioassay found active. |
pubchem_get_compound_details | Get physicochemical properties, descriptions, synonyms, drug-likeness, and classification for compounds by CID. |
pubchem_get_compound_image | Fetch a 2D structure diagram (PNG) for a compound by CID. |
pubchem_get_compound_3d_structure | Fetch a 3D conformer (atomic coordinates and bonds) for a compound by CID, as parsed JSON or raw SDF. |
pubchem_get_compound_xrefs | Get external database cross-references (PubMed, patents, genes, CAS and EC numbers, etc.). |
pubchem_get_compound_safety | Get GHS hazard classification and safety data for one or more compounds by CID (batch). |
pubchem_get_bioactivity | Get a compound's bioactivity profile: assay results, targets, activity values, assay types, and cited PubMed IDs; filter by outcome, molecular target, or assay type. |
pubchem_get_compound_interactions | Get a compound's drug-food interactions and the molecular targets its assays name, each with assay counts and the most potent active value. |
pubchem_search_assays | Find bioassays by biological target (gene symbol, protein, Gene ID, UniProt accession). |
pubchem_get_summary | Get summaries for PubChem entities: assays, genes, proteins, taxonomy. |
Resources
Compound and assay records are also exposed as URI-templated resources, backed by the same client methods as the tools; many MCP clients are tool-only and never surface resources.
| Resource | Description |
|---|---|
pubchem://compound/{cid} | Core physicochemical properties (JSON). |
pubchem://compound/{cid}/safety | GHS hazard classification (JSON). |
pubchem://compound/{cid}/image | 2D structure diagram (PNG). |
pubchem://compound/{cid}/xrefs | External cross-references (JSON). |
pubchem://compound/{cid}/bioactivity | Bioassay activity profile (JSON). |
pubchem://assay/{aid} | BioAssay summary (JSON). |
Capability reference
pubchem_search_compounds tool
- Six search strategies: identifier (
identifierType+identifiers; name/SMILES/InChI/InChIKey, 25 per call, the rest returned inunprocessed), formula (formula, Hill notation, optionalallowOtherElements), substructure/superstructure and 2D similarity (query+queryType; similarity threshold 70-100, default 90), or assay (aid; compounds the bioassay marked active, or every compound tested withactiveOnly: false) - Up to 200 CIDs per page (default 20; a larger
maxResultsis served at 200 and reported incappedInputs);offsetpages identifier and assay searches to the end of their matches, and formula, substructure, superstructure, and similarity searches to 10,000 (deeper fails withoffset_ceiling_exceeded). Optionalpropertiessaves a follow-uppubchem_get_compound_detailscall - Reports
totalFound, or atotalFoundAtLeastfloor when an upstream search saturated, andunresolvedIdentifiersfor inputs with no CID. Fails withaid_not_foundfor an unknown AID andsearch_query_rejected(with a hint) for a query PubChem cannot search
pubchem_get_compound_details tool
- Up to 100 CIDs per call (the rest returned in
unprocessed); 27 available properties, defaulting to a core set of 14 - Optional descriptions (
descriptionOffset/maxDescriptions, default 3, max 20) and classification (FDA, MeSH, ATC) cover the first 10 CIDs only, the rest listed inskippedCids. Optional synonyms (synonymOffset/maxSynonyms, default 20, max 100) and a Lipinski/Veber drug-likeness check cover every found CID - Per-CID
found: falseseparates a nonexistent CID from a compound with no data
pubchem_get_compound_image tool
- Single CID;
sizeis"small"(100x100) or"large"(300x300, default) - Returns base64-encoded PNG plus width/height
- Typed
cid_not_founderror when PubChem has no record for the CID
pubchem_get_compound_3d_structure tool
- Single CID;
format="json"(default) returns parsed atoms (element + x/y/z) and bonds,format="sdf"returns the raw V2000 SDF text maxAtoms/maxBondscap the JSON preview (default 200 each);atomCount/bondCountalways report the full totals, with any capping disclosed via enrichmentincludeRawSdfbypasses the default 500-line cap on the raw SDF text- Optional
includeAlternateConformerIdslists conformer IDs beyond the default - Typed
no_3d_structureerror when PubChem has no computed 3D coordinates (large molecules, mixtures, some salts)
pubchem_get_compound_xrefs tool
- Single CID; one or more
xrefTypes:CAS,EC,RN,RegistryID,PatentID,PubMedID,GeneID,ProteinGI,TaxonomyID CASandECare the numbers deposited for the compound, every distinct value ordered by how many source records deposited it, with those counts insourceCounts.RNis PubChem's unranked registry-number list: the CAS number sits beside deprecated and related CAS RNs and EC numbersProteinGIcurrently returns no data from PubChem; useGeneIDfor gene/protein associationsmaxPerTypeup to 500 (default 50), with oneoffsetapplied to every type; each type reportstotalAvailableandtruncated.PubMedIDcomes newest first- A type that fails to load is named in
failedTypesand the others still return; the call fails only when a type fails and no type other than ProteinGI (which PubChem no longer serves) loads. An empty result says whether the compound has none of the requested types or the CID may be mistyped
pubchem_get_compound_safety tool
- Up to 25 CIDs per call (the rest returned in
unprocessed) - Returns the GHS signal word, pictograms, H-codes, and P-codes merged across sources, plus
classifications[]with each source's own label; precautionary statements carry adecodedflag - Per-CID
status:ok,not_classified(notifiers found no GHS hazard),no_ghs_data(no deposited classification), orcid_not_found, so a bad CID never reads as "no hazards on file"
pubchem_get_bioactivity tool
- Single CID; filter by
outcomeFilter(active/inactive/all, defaultall),targetGeneId/targetAccession, andassayType(any ofconfirmatory/screening/summary/other); an assay must pass every filter set - A target filter matches any identifier tied to the same target, so Gene ID 5742, UniProt P23219, and RefSeq NP_542158 all reach the same COX-1 assays
- Each result carries the assay's
assayTypeand the PubMed IDs it cites (pmids, newest first) - Up to 100 results per page (default 20);
offsetreaches the rest. A multi-target assay reports the matched target and lists the others underotherTargets - Reports
totalAssays/activeCount/inactiveCountfor the whole compound andfilteredCount/returnedCountfor the page; notices say whether there is no data, the filter excluded everything, oroffsetis past the end
pubchem_get_compound_interactions tool
- Single CID; one or more
kinds—drug-food(PUG View),target,drug-drug(DrugBank); default["drug-food"], and an empty drug-food page points attarget targetgives one entry per target the compound's assay summary names: a Gene ID with the accessions the table pairs with it, or an identifier it never pairs. A Gene ID and an accession that no row pairs are separate entries even when they name the same protein (erlotinib's ERBB2 appears asaccession:P04626and asGeneID:2064). Each entry names the target from a batched gene or protein summary lookup, plus theGeneID:oraccession:valuepubchem_get_bioactivityfilters on, and states the assay counts by outcome and the most potent Active value with its AID. Active targets come first, most potent first. If a name lookup fails or does not answer within a few seconds, the entries keep their identifier and the notice says somaxEntriesper kind per page (max 50, default 10);offsetcounts records (targets, fortarget), up to 2,147,483,646. Each kind pages independently, reported inpaging[](totalRecords/nextOffset/truncated)- A kind that fails is named in
failedKindsand the others still return.drug-drugcomes from PubChem's SDQ service, which refuses automated requests, so it lands infailedKindsand a retry will not help
pubchem_search_assays tool
- Search by
targetType:genesymbol/proteinname(text),geneid(NCBI Gene ID),proteinaccession(UniProt) - Up to 200 AIDs per page (default 50);
offsetpages through the results - Rejects a blank
targetQueryand a non-numericgeneid - Reports
totalFoundand distinguishes "no match" from "offset past the end"
pubchem_get_summary tool
entityType:assay(AID),gene(NCBI Gene ID),protein(UniProt accession), ortaxonomy(Tax ID); up to 10 identifiers per call- A comma-joined string is split into identifiers; any past the first 10 are returned in
unprocessed, and more than 1000 fails astoo_many_identifiers - AIDs, Gene IDs, and Tax IDs must be positive integers up to 2147483647; other pieces get an
invalid: truerow, and the call fails asinvalid_identifieronly when none is usable - Per-identifier
foundflag; fields depend onentityType(taxonomy: orderedlineage; gene:symbol/taxonomy; assay:method, the first 25targetswithtargetCount, tested/active compound counts,lastDataChange)
pubchem://compound/{cid} resource
- Core physicochemical properties (the same default 14-property set as
pubchem_get_compound_details), asapplication/json - Throws a typed not-found when the CID doesn't exist in PubChem
- Use
pubchem_get_compound_detailsto select specific properties or add descriptions, synonyms, drug-likeness, and classification
pubchem://compound/{cid}/safety resource
- GHS hazard classification as
application/json, including the per-sourceghs.classificationsand the contributorsourcelist status(ok/not_classified/no_ghs_data/cid_not_found) is the only signal distinguishing a bad CID from a compound with no deposited classification — a resource read has no notice surface
pubchem://compound/{cid}/image resource
- 2D structure diagram, 300x300 PNG, returned as a base64 blob
- Use
pubchem_get_compound_imagefor the 100x100 size option
pubchem://compound/{cid}/xrefs resource
- Focused default set —
RN,RegistryID,PubMedID,CAS(withsourceCounts) — up to 25 IDs per type, asapplication/json; the PubMed IDs are the 25 highest (most recently assigned), highest first. A type that fails to load is named infailedTypes - Use
pubchem_get_compound_xrefsfor the full set of xref types, a higher per-type cap, and offset paging
pubchem://compound/{cid}/bioactivity resource
- Up to 25 assays as
application/json, each with itsassayTypeandpmids, plustotalAssays/activeCountfor the whole compound; a multi-target assay lists its further targets underotherTargets - Use
pubchem_get_bioactivityto filter by outcome, target, or assay type, raise the cap, or page with offset
pubchem://assay/{aid} resource
- BioAssay summary as
application/json— name, description, source, protocol, substance counts - Throws a typed not-found when the AID doesn't exist
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.
PubChem-specific:
- Covers PUG REST and PUG View endpoints, plus SDQ for drug-drug interactions
- Rate-limited client (5 req/s) with request queuing and one retry on transient failures
- A 50 s budget per call fails a slow upstream as
Timeout, and a cancelled call fails withRequestCancelled - Built-in V2000 SDF parser for 3D conformers; drug-likeness computed from already-fetched properties
- All tools are read-only and idempotent, with no API keys required
Agent-friendly output:
- Per-CID
statusandfoundflags let callers branch on data instead of matching error strings - Batch tools return per-item results alongside
unresolvedIdentifiers,skippedCids,failedKinds, andfailedTypesrather than failing the whole call - Capped lists disclose
truncated,shown/cap, andnextOffset; oversized batches are served up to the cap, withcappedInputsandunprocessedreporting the rest - Failures carry a typed
reason(e.g.cid_not_found,missing_identifier_args,invalid_cid_query) with recovery text
Getting started
Public Hosted Instance
A public instance is available at https://pubchem.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"pubchem-mcp-server": {
"type": "streamable-http",
"url": "https://pubchem.caseyjhand.com/mcp"
}
}
}
Self-Hosted / Local
Add the following to your MCP client configuration file.
{
"mcpServers": {
"pubchem-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/pubchem-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"pubchem-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/pubchem-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"pubchem-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/pubchem-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
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
- No API keys required — PubChem's API is freely accessible.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/pubchem-mcp-server.git
- Navigate into the directory:
cd pubchem-mcp-server
- Install dependencies:
bun install
- Configure environment (optional):
cp .env.example .env
# edit .env to override transport, session mode, storage, or logging defaults
Configuration
| Variable | Description | Default |
|---|---|---|
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for HTTP server. | 3010 |
MCP_HTTP_HOST | Host for HTTP server. | 127.0.0.1 |
MCP_SESSION_MODE | stateless, stateful, or auto. | stateless |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
STORAGE_PROVIDER_TYPE | Storage backend. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry. | false |
See .env.example for the full list of optional overrides.
Running the server
Local development
-
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 build -t pubchem-mcp-server .
docker run --rm -p 3010:3010 pubchem-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/pubchem-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 and inits the PubChem client. |
src/mcp-server/tools/definitions/ | Tool definitions (*.tool.ts). |
src/mcp-server/resources/definitions/ | Resource definitions (*.resource.ts). |
src/services/pubchem/ | PubChem API client — rate limiting, retry, and response/SDF parsing. |
scripts/ | Build, clean, devcheck, and tree generation scripts. |
tests/ | Unit and integration tests. |
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 - Wrap external API calls: validate the raw PubChem response → normalize to a domain type → return the output schema; never fabricate missing fields
- Register new tools and resources in the
index.tsbarrel files
Contributing
Issues are welcome. Run checks before submitting:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.