@cyanheads/chembl-mcp-server
Powers the DataCanvas backend (via CANVAS_PROVIDER_TYPE=duckdb) that stages bioactivity rows spilled from large ChEMBL query results, enabling read-only SQL SELECT queries over those rows.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@cyanheads/chembl-mcp-serverShow me bioactivities for molecule CHEMBL25"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Public Hosted Server: https://chembl.caseyjhand.com/mcp
Overview
Drug-discovery data over ChEMBL (EBI) — the curated link between compounds, protein targets, and measured bioactivity (IC50/Ki/EC50), plus drug mechanisms and indications. Search compounds by name, ID, or structure, resolve protein targets, rank bioactivity measurements, and look up drug mechanisms and indications from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
Tools
Tool | Description |
| Find compounds by name / ChEMBL ID / InChIKey, or run a structure search (exact | similarity | substructure) from a SMILES. |
| The flagship compound↔target bridge: bioactivity measurements for a molecule, a target, or both (the compound×target pair), ranked on |
| Resolve a protein / gene symbol / UniProt accession to the ChEMBL target ID |
| Drug pharmacology — mechanism(s) of action, molecular target(s), action type, first-approval year, and clinical indications. |
| Assay provenance behind a bioactivity row — type, target, organism, and ChEMBL's 1–9 confidence score. |
| Run a read-only SQL |
| List the tables and columns staged on a canvas, so you can write correct SQL before querying. |
| Drop a named staged table from a canvas. Opt-in via |
Resources
Resource | Description |
| A molecule record by ChEMBL ID — the same shape a |
| A target record by ChEMBL target ID — preferred name, type, organism, and component UniProt accessions + gene symbols. |
All resource data is also reachable via the tools, so tool-only MCP clients lose nothing. There are no prompts — the canonical workflows are short tool chains an agent composes directly, and the cross-server chain guidance ships as server-level instructions instead.
Related MCP server: mcp-chembl
Capability reference
chembl_search_molecules tool
Default
search_type=namematches drug names, synonyms, ChEMBL IDs, and InChIKeys in one query; a query that is exactly a ChEMBL ID or InChIKey routes to ChEMBL's single-record lookup instead of the fuzzy text index (totalCount: 1)Structure search via
search_type:exact,similarity(Tanimoto ≥similarity_threshold, integer 40–100, default 70), orsubstructure— supplystructureas a SMILES;max_phase_min(name search only) restricts to compounds at or above a max clinical phaseEvery row carries
max_phase, MW, AlogP, Lipinski rule-of-five violations, and QED; onlysearch_type=similarityresults carry a Tanimotosimilaritypercent — absent, not null, on other modesPaginated via
nextCursor/cursor, omitted (not null) on the last page — redeem a cursor with the same filters that minted itChain
molecule_chembl_idintochembl_get_bioactivitiesorchembl_get_drug_info
chembl_get_bioactivities tool
Supply at least one of
molecule_chembl_idortarget_chembl_id; supplying both narrows to that compound–target pair — neither is amissing_filtererrorFilter by
standard_type(IC50/Ki/EC50/…),pchembl_value_min,assay_type,organism; ranked onpchembl_value— comparable only within onestandard_typepotency_viewselectspotency_ranked(default, measurements with a derivablepchembl_value) ornull_potency(the excluded rows);pchembl_value_minwithnull_potencyis acontradictory_potency_filtererror, andtotalCountspans both viewsNumerics are coerced to
number | nullat the service boundary — a missing potency reads asnull, never0Large sets spill to a DataCanvas table per view (
bioactivities/bioactivities_null_potency), capped atCHEMBL_MAX_SPILL_ROWS(default 50,000) and reportedtruncated: true+staged_row_countwhen hit; requiresCANVAS_PROVIDER_TYPE=duckdbThe inline preview is always capped at
limit(default 25) regardless of spill status; the optionalcanvas_idreuses a canvas, but re-querying the same view replaces its prior rows
chembl_search_targets tool
Supply at least one of
accession(UniProt, e.g.P00533),gene_symbol, orquery(free-text); narrow withorganismandtarget_type— none supplied is amissing_inputerrorA UniProt accession is the most precise input — chain it from a
uniprot/proteinserverEach row carries target type, organism, and component UniProt accessions + gene symbols, flattened from ChEMBL's nested component synonyms
Paginated via
nextCursor/cursor, the same contract aschembl_search_moleculesChain
target_chembl_idintochembl_get_bioactivities
chembl_get_drug_info tool
Supply
molecule_chembl_id; returns mechanism(s) of action, molecular target(s), action type, first-approval year, and clinical indications with the max phase reached for eachMechanisms and indications are fetched with
Promise.allSettled, so a rejected list degrades to a disclosed partial result rather than failing the callEach list carries its own
mechanisms_status/indications_status(complete/truncated/failed) next to a*_total_count— an empty array is authoritative only when the status iscompleteA mechanism's
target_chembl_idchains intochembl_get_bioactivities
chembl_get_assay tool
Supply
assay_chembl_idfrom achembl_get_bioactivitiesrowReturns description, assay type (binding / functional / ADMET / toxicity), the target measured, organism, and ChEMBL's 1–9 confidence score (9 = direct assay on the protein target, lower = homologous or indirect)
Call it to judge whether two measurements are comparable before ranking them together
chembl_dataframe_query tool
Accepts a single read-only
SELECTagainst acanvas_idfrom a spilledchembl_get_bioactivitiescall; writes, DDL, and non-SELECT statements are rejected by the framework SQL gateReference each staged table by the name
chembl_get_bioactivitiesreturned —bioactivities(potency_ranked) orbioactivities_null_potency(null_potency); discover columns withchembl_dataframe_describefirstTwo independent bounds, each disclosed:
truncatedis the canvas engine's own query-result cap;rendered_rowsis how many rows thecontent[]markdown table holds under its character budget — either can trip without the other; page past both with SQLLIMIT/OFFSETstructuredContent.rowsalways carries the full materialized result regardless of the render boundRequires
CANVAS_PROVIDER_TYPE=duckdb, else acanvas_disablederror
chembl_dataframe_describe tool
Supply a
canvas_idfrom a spilledchembl_get_bioactivitiescallReturns each staged table/view with its row count, kind, and column names + types
Requires
CANVAS_PROVIDER_TYPE=duckdb, else acanvas_disablederror
chembl_dataframe_drop tool
Opt-in — registered only when
CHEMBL_DATAFRAME_DROP_ENABLED=true; absent fromtools/listwhen off, though it still appears in the server manifest carrying the enable hintDrops a named staged table by
canvas_id+table_name; returnsdropped: trueif it existed,falseif already goneRarely needed — per-table and per-canvas TTL already reclaim staged tables; reach for it only to free a large table early in a long session
Requires
CANVAS_PROVIDER_TYPE=duckdb
chembl://molecule/{chemblId} resource
Molecule record as
application/json— the same shape achembl_search_moleculesrow carries (ID, names, structures, properties, max clinical phase)chemblIdis validated against theCHEMBL\d+patternFully covered by the tool surface — a convenience injectable-context mirror of the per-record fetch
chembl://target/{chemblId} resource
Target record as
application/json— preferred name, type, organism, and component UniProt accessions + gene symbolschemblIdis validated against theCHEMBL\d+patternFully covered by the tool surface — a convenience injectable-context mirror of the per-record fetch
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.
ChEMBL-specific:
Bidirectional bioactivity bridge — one tool serves both compound→target and target→compound, ranked on
pchembl_valueStructure search (exact / similarity / substructure) consolidated under one discovery tool via a
search_typeenumString →
number | nullnumeric coercion at the service boundary — a missing potency becomesnull, never0DataCanvas spill on the flagship tool — tens-of-thousands-of-row activity sets stream to a DuckDB table you inspect with
chembl_dataframe_describeand query viachembl_dataframe_queryServer-level
instructionscarry the cross-server chain guidance and the ChEMBL CC BY-SA 3.0 attribution requirement
Agent-friendly output:
Provenance on every response — total-found counts, applied-filter echo, and a spill notice so agents know whether the preview is the full set or a slice of a canvas table
Truncation disclosure — capped searches report
shown/cap/totalCount, and spilled tables reporttruncated+staged_row_count, so a page or slice is never mistaken for the complete setTyped, recoverable errors —
missing_filter/missing_input/contradictory_potency_filter/canvas_disabledcarry recovery hints, so callers correct the call without parsing proseNever fabricates — normalization and
format()preservenullpotency / units; a missing measurement renders as "not reported", never0
Getting started
Public Hosted Instance
A public instance is available at https://chembl.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"chembl-mcp-server": {
"type": "streamable-http",
"url": "https://chembl.caseyjhand.com/mcp"
}
}
}Self-Hosted / Local
ChEMBL is keyless — no API key or account is required.
Add the following to your MCP client configuration file:
{
"mcpServers": {
"chembl-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/chembl-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}Or with npx (no Bun required):
{
"mcpServers": {
"chembl-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/chembl-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}Or with Docker:
{
"mcpServers": {
"chembl-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/chembl-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/mcpTo unlock the analytical SQL path (the bioactivities spill and the chembl_dataframe_* tools), add "CANVAS_PROVIDER_TYPE": "duckdb" to the env.
Prerequisites
Bun v1.4.0 or higher (or Node.js v24+).
Optional: set
CANVAS_PROVIDER_TYPE=duckdbto enable the DataCanvas SQL path for large bioactivity sets.
Installation
Clone the repository:
git clone https://github.com/cyanheads/chembl-mcp-server.gitNavigate into the directory:
cd chembl-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env
# edit .env to override any defaults (all optional)Configuration
All configuration is validated at startup via Zod schemas in src/config/server-config.ts. ChEMBL is keyless, so every variable is optional.
Variable | Description | Default |
| Set to |
|
| Base URL for the ChEMBL REST data API. Override for a private mirror or pinned host. |
|
| Per-request timeout in milliseconds for upstream ChEMBL fetches. |
|
| ChEMBL per-page cap when streaming activity pages for the spill (max 1000). |
|
| Default result limit applied when callers omit it. |
|
| Ceiling on rows |
|
| Register the opt-in |
|
| Transport: |
|
| Port for the HTTP server. |
|
| HTTP session posture; |
|
| Auth mode: |
|
| Log level (RFC 5424). |
|
| Directory for log files (Node.js only). |
|
| Log failed calls' arguments and results with key-name redaction; secrets inside free-form values remain visible. |
|
| UTF-8 byte cap per logged input/result payload. |
|
| Enable OpenTelemetry instrumentation. |
|
| Base URL for traces and metrics, using | — |
| Explicit full endpoint for OTLP log export. Requires the optional log peers; included in the default Docker build. | — |
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:httpRun checks and tests:
bun run devcheck # Lint, format, typecheck, security, changelog sync bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec
Docker
docker build -t chembl-mcp-server .
docker run --rm -e MCP_TRANSPORT_TYPE=stdio chembl-mcp-serverThe Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/chembl-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them. Dependency installation and security scanning run on the builder's native platform, with Bun's --cpu and --os selecting DuckDB bindings for the target linux/amd64 or linux/arm64 image. The slim runtime receives that production dependency tree, so CANVAS_PROVIDER_TYPE=duckdb works on either image architecture.
Project structure
Directory | Purpose |
|
|
| Server-specific environment variable parsing and validation with Zod. |
| Tool definitions ( |
| Resource definitions ( |
| The single ChEMBL upstream client — URL builder, pagination, numeric coercion, nested-structure flattening, activity page stream. |
| Module-level holder for the optional |
| Unit and integration tests mirroring |
Development guide
See CLAUDE.md/AGENTS.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storageRegister new tools and resources in the
createApp()arraysWrap the ChEMBL API: validate raw → normalize to the flat domain type → return the output schema; never fabricate missing fields (absent numerics become
null, never0)
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testLicense
Apache-2.0 — see LICENSE for details.
This server cannot be deployed
Maintenance
Related MCP Connectors
ChEMBL MCP — drug discovery database (EBI).
Pharma Intel MCP — Compound tools that chain ClinicalTrials.gov,
Open Targets MCP — disease/target/drug knowledge graph.
PubChem MCP — NIH chemistry compound database (no auth)
Related MCP Servers
- FlicenseBqualityDmaintenanceA comprehensive Model Context Protocol (MCP) server providing advanced access to the ChEMBL chemical database.2789-
- AlicenseNot gradedqualityBmaintenanceEnables querying ChEMBL drug discovery database for molecules, targets, activities, and drug indications. Part of Pipeworx MCP gateway.255 npmMIT
- FlicenseNot gradedqualityDmaintenanceA FastMCP wrapper server that provides API access to the ChEMBL database, enabling users to query drug discovery data such as activities, assays, targets, and molecules.2-
- AlicenseNot gradedqualityDmaintenanceProvides chemical informatics endpoints for converting between chemical names and SMILES, processing molecule structures, and comparing molecules, with MCP compatibility.5MIT