Pubchem MCP Server README

The full upstream README, mirrored for reference. Install config, tool schemas, adoption signals and our own overview live on the Pubchem MCP Server listing page.

@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.

10 Tools • 6 Resources

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework

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

ToolDescription
pubchem_search_compoundsSearch for compounds by name, SMILES, InChI, InChIKey, formula, substructure, superstructure, or 2D similarity, or list the compounds a bioassay found active.
pubchem_get_compound_detailsGet physicochemical properties, descriptions, synonyms, drug-likeness, and classification for compounds by CID.
pubchem_get_compound_imageFetch a 2D structure diagram (PNG) for a compound by CID.
pubchem_get_compound_3d_structureFetch a 3D conformer (atomic coordinates and bonds) for a compound by CID, as parsed JSON or raw SDF.
pubchem_get_compound_xrefsGet external database cross-references (PubMed, patents, genes, CAS and EC numbers, etc.).
pubchem_get_compound_safetyGet GHS hazard classification and safety data for one or more compounds by CID (batch).
pubchem_get_bioactivityGet 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_interactionsGet 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_assaysFind bioassays by biological target (gene symbol, protein, Gene ID, UniProt accession).
pubchem_get_summaryGet 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.

ResourceDescription
pubchem://compound/{cid}Core physicochemical properties (JSON).
pubchem://compound/{cid}/safetyGHS hazard classification (JSON).
pubchem://compound/{cid}/image2D structure diagram (PNG).
pubchem://compound/{cid}/xrefsExternal cross-references (JSON).
pubchem://compound/{cid}/bioactivityBioassay 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 in unprocessed), formula (formula, Hill notation, optional allowOtherElements), 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 with activeOnly: false)
  • Up to 200 CIDs per page (default 20; a larger maxResults is served at 200 and reported in cappedInputs); offset pages identifier and assay searches to the end of their matches, and formula, substructure, superstructure, and similarity searches to 10,000 (deeper fails with offset_ceiling_exceeded). Optional properties saves a follow-up pubchem_get_compound_details call
  • Reports totalFound, or a totalFoundAtLeast floor when an upstream search saturated, and unresolvedIdentifiers for inputs with no CID. Fails with aid_not_found for an unknown AID and search_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 in skippedCids. Optional synonyms (synonymOffset/maxSynonyms, default 20, max 100) and a Lipinski/Veber drug-likeness check cover every found CID
  • Per-CID found: false separates a nonexistent CID from a compound with no data

pubchem_get_compound_image tool

  • Single CID; size is "small" (100x100) or "large" (300x300, default)
  • Returns base64-encoded PNG plus width/height
  • Typed cid_not_found error 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/maxBonds cap the JSON preview (default 200 each); atomCount/bondCount always report the full totals, with any capping disclosed via enrichment
  • includeRawSdf bypasses the default 500-line cap on the raw SDF text
  • Optional includeAlternateConformerIds lists conformer IDs beyond the default
  • Typed no_3d_structure error 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
  • CAS and EC are the numbers deposited for the compound, every distinct value ordered by how many source records deposited it, with those counts in sourceCounts. RN is PubChem's unranked registry-number list: the CAS number sits beside deprecated and related CAS RNs and EC numbers
  • ProteinGI currently returns no data from PubChem; use GeneID for gene/protein associations
  • maxPerType up to 500 (default 50), with one offset applied to every type; each type reports totalAvailable and truncated. PubMedID comes newest first
  • A type that fails to load is named in failedTypes and 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 a decoded flag
  • Per-CID status: ok, not_classified (notifiers found no GHS hazard), no_ghs_data (no deposited classification), or cid_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, default all), targetGeneId/targetAccession, and assayType (any of confirmatory/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 assayType and the PubMed IDs it cites (pmids, newest first)
  • Up to 100 results per page (default 20); offset reaches the rest. A multi-target assay reports the matched target and lists the others under otherTargets
  • Reports totalAssays/activeCount/inactiveCount for the whole compound and filteredCount/returnedCount for the page; notices say whether there is no data, the filter excluded everything, or offset is 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 at target
  • target gives 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 as accession:P04626 and as GeneID:2064). Each entry names the target from a batched gene or protein summary lookup, plus the GeneID: or accession: value pubchem_get_bioactivity filters 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 so
  • maxEntries per kind per page (max 50, default 10); offset counts records (targets, for target), up to 2,147,483,646. Each kind pages independently, reported in paging[] (totalRecords/nextOffset/truncated)
  • A kind that fails is named in failedKinds and the others still return. drug-drug comes from PubChem's SDQ service, which refuses automated requests, so it lands in failedKinds and 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); offset pages through the results
  • Rejects a blank targetQuery and a non-numeric geneid
  • Reports totalFound and distinguishes "no match" from "offset past the end"

pubchem_get_summary tool

  • entityType: assay (AID), gene (NCBI Gene ID), protein (UniProt accession), or taxonomy (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 as too_many_identifiers
  • AIDs, Gene IDs, and Tax IDs must be positive integers up to 2147483647; other pieces get an invalid: true row, and the call fails as invalid_identifier only when none is usable
  • Per-identifier found flag; fields depend on entityType (taxonomy: ordered lineage; gene: symbol/taxonomy; assay: method, the first 25 targets with targetCount, tested/active compound counts, lastDataChange)

pubchem://compound/{cid} resource

  • Core physicochemical properties (the same default 14-property set as pubchem_get_compound_details), as application/json
  • Throws a typed not-found when the CID doesn't exist in PubChem
  • Use pubchem_get_compound_details to 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-source ghs.classifications and the contributor source list
  • 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_image for the 100x100 size option

pubchem://compound/{cid}/xrefs resource

  • Focused default set — RN, RegistryID, PubMedID, CAS (with sourceCounts) — up to 25 IDs per type, as application/json; the PubMed IDs are the 25 highest (most recently assigned), highest first. A type that fails to load is named in failedTypes
  • Use pubchem_get_compound_xrefs for 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 its assayType and pmids, plus totalAssays/activeCount for the whole compound; a multi-target assay lists its further targets under otherTargets
  • Use pubchem_get_bioactivity to 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 with RequestCancelled
  • 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 status and found flags let callers branch on data instead of matching error strings
  • Batch tools return per-item results alongside unresolvedIdentifiers, skippedCids, failedKinds, and failedTypes rather than failing the whole call
  • Capped lists disclose truncated, shown/cap, and nextOffset; oversized batches are served up to the cap, with cappedInputs and unprocessed reporting 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:

config.json
{
  "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.

config.json
{
  "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):

config.json
{
  "mcpServers": {
    "pubchem-mcp-server": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@cyanheads/pubchem-mcp-server@latest"],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio"
      }
    }
  }
}

Or with Docker:

config.json
{
  "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:

sh
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

  1. Clone the repository:
sh
git clone https://github.com/cyanheads/pubchem-mcp-server.git
  1. Navigate into the directory:
sh
cd pubchem-mcp-server
  1. Install dependencies:
sh
bun install
  1. Configure environment (optional):
sh
cp .env.example .env
# edit .env to override transport, session mode, storage, or logging defaults

Configuration

VariableDescriptionDefault
MCP_TRANSPORT_TYPETransport: stdio or http.stdio
MCP_HTTP_PORTPort for HTTP server.3010
MCP_HTTP_HOSTHost for HTTP server.127.0.0.1
MCP_SESSION_MODEstateless, stateful, or auto.stateless
MCP_AUTH_MODEAuth mode: none, jwt, or oauth.none
MCP_LOG_LEVELLog level (RFC 5424).info
STORAGE_PROVIDER_TYPEStorage backend.in-memory
OTEL_ENABLEDEnable OpenTelemetry.false

See .env.example for the full list of optional overrides.

Running the server

Local development

  • Build and run:

    sh
    # One-time build
    bun run rebuild
    
    # Run the built server
    bun run start:stdio
    # or
    bun run start:http
    
  • Run checks and tests:

    sh
    bun run devcheck   # Lint, format, typecheck, security
    bun run test       # Vitest test suite
    bun run lint:mcp   # Validate MCP definitions against spec
    

Docker

Terminal
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

DirectoryPurpose
src/index.tscreateApp() 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/catch in tool logic
  • Use ctx.log for 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.ts barrel files

Contributing

Issues are welcome. Run checks before submitting:

sh
bun run devcheck
bun run test

License

Apache-2.0 — see LICENSE for details.