mcp server
clinicaltrialsgov-mcp-server
Search ClinicalTrials.gov trials, retrieve study details and results, and match patients to eligible trials via MCP. STDIO or Streamable HTTP.
Set up
Runs on your computer. Pick your agent and operating system.
Before you start
- Node.js 20 or newer
{
"mcpServers": {
"clinicaltrialsgov-mcp-server": {
"command": "npx",
"args": [
"-y",
"clinicaltrialsgov-mcp-server"
]
}
}
}same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12
Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs
README
synced Oct 14, 2026Public Hosted Server: https://clinicaltrials.caseyjhand.com/mcp
Overview
Clinical trial data from the ClinicalTrials.gov REST API v2 — the US National Library of Medicine's registry of 600K+ clinical trial studies. Search trials, fetch full study records and posted results, discover field names and valid values, and match patient demographics to eligible recruiting trials. Public, read-only, no authentication required. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
Tools
| Tool | Description |
|---|---|
clinicaltrials_search_studies | Search studies with full-text and field-specific queries, status/phase/geographic filters, pagination, sorting, and field selection |
clinicaltrials_get_study_record | Fetch a single study by NCT ID — full protocol record with optional location/outcome/reference caps |
clinicaltrials_get_study_count | Fast total study count for a query, without fetching data |
clinicaltrials_get_field_values | Discover valid values for API fields, with per-value study counts |
clinicaltrials_get_field_definitions | Resolve valid field names — keyword search, path drill-down, or top-level overview |
clinicaltrials_get_study_results | Fetch posted results — outcomes, adverse events, participant flow, baseline — for completed studies |
clinicaltrials_find_eligible | Match patient demographics and conditions to eligible recruiting trials |
Resources
| Resource | Description |
|---|---|
clinicaltrials://{nctId} | Fetch a single clinical study by NCT ID as JSON, with capped lists and results replaced by counts |
Prompts
| Prompt | Description |
|---|---|
analyze_trial_landscape | Guides a data-driven clinical trial landscape analysis using the count and search tools |
Capability reference
clinicaltrials_search_studies tool
- Free-text
queryplus field-specificconditionQuery/interventionQuery/locationQuery/sponsorQuery/titleQuery/outcomeQuery,statusFilter/phaseFilterenums,advancedFilter(AREA[FieldName]value/RANGE[min, max]), andgeoFilter(distance(lat,lon,radius)with ami/kmsuffix);pageSize1–200 (default 10),pageTokencursor,sorton up to 2 fields - Returns a compact per-study index by default (
nctId,briefTitle,overallStatus,phases,enrollmentCount,hasResults, …);fields(PascalCase leaves) selects a full-fidelity projection — full records run ~70KB - Typed errors:
blank_value,ids_not_found,field_invalid,enum_invalid,query_parse_error,geo_invalid,sort_invalid,rate_limited
clinicaltrials_get_study_record tool
- Full protocol record by
nctId, with optionallocationLimit(≤500),outcomeLimit/referenceLimit(≤100), andnearLocation(lat,lon,radiusMi) to bound and sort locations filtersAppliedreports upstream totals when a cap trims a list;resultsSectionis replaced byresultsSummarycounts. Typed errors:study_not_found,rate_limited
clinicaltrials_get_study_count tool
- Same query and filter surface as
clinicaltrials_search_studies, returning onlytotalCount— no study data fetched - Typed errors:
blank_value,field_invalid,enum_invalid,query_parse_error,rate_limited
clinicaltrials_get_field_values tool
- One or more PascalCase
fields(e.g.OverallStatus,Phase) — returns each field's type and top values with study counts (capped at 250 by the API), ormin/max/avgfor numeric and date fields andtrueCount/falseCountfor booleans multiValuedflags fields whose per-value counts can sum above the study total. Typed errors:blank_value,field_invalid,rate_limited
clinicaltrials_get_field_definitions tool
mode:search(keywordquery,limit≤100, default 20),drill(dot-notationpath), oroverview— resolves the PascalCase names accepted byfields,advancedFilter,sort, andclinicaltrials_get_field_values- Typed errors:
blank_value,mode_mismatch,mode_requires,path_not_found,rate_limited
clinicaltrials_get_study_results tool
- Up to 20
nctIdsper call;sections(outcomes,adverseEvents,participantFlow,baseline,moreInfo),summaryto condense a result set that can exceed 500KB per study, andoutcomeLimit(≤100) /adverseEventLimit(≤500) with resumableoutcomeOffset/seriousEventOffset/otherEventOffset - Per-study
fetchErrors/studiesWithoutResultsinstead of a whole-batch failure, andcanonicalNctIdwhen an alias ID resolves to another study. Typed errors:blank_value,offset_not_applicable,rate_limited
clinicaltrials_find_eligible tool
age,sex(FEMALE/MALE/ALL),conditions[],location(countryrequired,state/cityoptional),healthyVolunteer,recruitingOnly(default true),maxResults(≤50),locationLimit(≤500)- Each candidate's locations are bounded to the sites matching the requested location, plus one recruiting site when none of those is open;
funnelreports match counts per filter stage (condition → +location → +demographics) - Typed errors:
blank_value,rate_limited
clinicaltrials://{nctId} resource
- Full protocol record as
application/json, with locations, secondary/other outcomes, and references each capped at 50 — fixed server-side, no arguments - Results data is replaced by
resultsSummarycounts;truncatedandfiltersApplieddisclose what was capped, withretrievalnaming the tools that fetch the full data - Typed errors:
study_not_found,rate_limited
analyze_trial_landscape prompt
- Arguments:
topicrequired;focusAreas(comma-separated) optional - Returns one user message pointing the agent at the count, search, field-discovery, and results tools for a data-driven landscape analysis
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.
ClinicalTrials.gov-specific:
- Type-safe client for the ClinicalTrials.gov REST API v2 — public, no authentication or API keys required
- Serialized request queue enforcing ClinicalTrials.gov's ~1 req/sec rate limit, with retry and exponential backoff on 429/5xx responses
- Auto-corrects field names passed to
fields/sort— case/whitespace fixes and known legacy aliases (e.g.RecruitmentStatus→OverallStatus) — before validating, logging every correction - Detects upstream HTML error pages returned with a JSON content-type and retries rather than parsing them as data
- Geographic proximity search and nearest-site re-ranking, with no geocoding dependency
- Excludes ClinicalTrials.gov's "unknown" enrollment sentinel (
99999999) from searches and counts by default —includeUnknownEnrollmentbrings it back, and annctIdslookup never filters it
Agent-friendly output:
- Provenance —
clinicaltrials_search_studies/clinicaltrials_get_study_count/clinicaltrials_find_eligibleechosearchCriteriaon every call, includingsentinelFilterActivewhen the default unknown-enrollment exclusion applies, andclinicaltrials_get_study_resultsnamescanonicalNctIdwhen a previous (alias) ID resolves to a different study - Graceful partial failure —
clinicaltrials_get_study_resultsreturns per-studyfetchErrors/studiesWithoutResultsrows instead of failing the whole batch when one ID is malformed or lacks results - Discriminated output — typed error
reasoncodes per tool (study_not_found,blank_value,offset_not_applicable, …), and bounded lists (filtersApplied,locationSummary) carry anext*Offsetonly when more remains, so callers branch on presence instead of parsing text - Response shaping —
clinicaltrials_search_studiesandclinicaltrials_find_eligiblereturn a compact per-study index or location-bounded set by default instead of the ~70KB full record, escalating to full fidelity only viafieldsorclinicaltrials_get_study_record
Getting started
Public Hosted Instance
A public instance is available at https://clinicaltrials.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"clinicaltrialsgov-mcp-server": {
"type": "streamable-http",
"url": "https://clinicaltrials.caseyjhand.com/mcp"
}
}
}
Self-Hosted / Local
Add the following to your MCP client configuration file.
{
"mcpServers": {
"clinicaltrialsgov-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["clinicaltrialsgov-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"clinicaltrialsgov-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "clinicaltrialsgov-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"clinicaltrialsgov-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/clinicaltrialsgov-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+).
Installation
- Clone the repository:
git clone https://github.com/cyanheads/clinicaltrialsgov-mcp-server.git
- Navigate into the directory:
cd clinicaltrialsgov-mcp-server
- Install dependencies:
bun install
Configuration
All configuration is optional — the server works with defaults and no API keys.
| Variable | Description | Default |
|---|---|---|
CT_API_BASE_URL | ClinicalTrials.gov API base URL. | https://clinicaltrials.gov/api/v2 |
CT_REQUEST_TIMEOUT_MS | Per-request timeout in milliseconds. | 30000 |
CT_MAX_PAGE_SIZE | Maximum page size cap. | 200 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for HTTP server. | 3010 |
MCP_SESSION_MODE | HTTP 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 |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
OTEL_ENABLED | Enable OpenTelemetry tracing. | false |
OTEL_EXPORTER_OTLP_ENDPOINT | OTLP base URL; traces export to /v1/traces, metrics to /v1/metrics. Unset exports nothing. | — |
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT | Opt-in OTLP log export; the base endpoint never enables it. | — |
LOG_TOOL_FAILURE_PAYLOADS | Log each failed tool call's arguments and result, redacted by key name and capped at LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES (default 16384). A secret inside a free-form value is not redacted. | 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:http # or bun run start:stdio -
Run checks and tests:
bun run devcheck # Lint, format, typecheck, and security audit bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec
Docker
docker build -t clinicaltrialsgov-mcp-server .
docker run --rm -p 3010:3010 clinicaltrialsgov-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/clinicaltrialsgov-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/prompts and inits the ClinicalTrials.gov service. |
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/mcp-server/prompts | Prompt definitions (*.prompt.ts). |
src/services/clinical-trials | ClinicalTrials.gov REST API v2 client — retry, rate limiting, field search, types. |
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, noconsolecalls - Register new tools and resources via the barrels in
src/mcp-server/*/definitions/index.ts - Validate raw API responses, normalize to domain types, and never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.