Clinicaltrialsgov MCP Server
Search ClinicalTrials.gov trials, retrieve study details and results, and match patients to eligible trials via MCP. STDIO or Streamable HTTP.
Clinicaltrialsgov MCP Server is an open-source mcp server by cyanheads, written mainly in TypeScript, with 99 GitHub stars and the Apache-2.0 license. Its code was last updated Oct 26, 2026. The latest npm release is clinicaltrialsgov-mcp-server 2.11.3, published Nov 13, 2026, with 3,347 downloads last week.
Public Hosted Server: https://clinicaltrials.caseyjhand.com/mcp
</div>Overview
Search 600K+ studies on ClinicalTrials.gov, the US National Library of Medicine's trial registry. Fetch full study records and posted results, discover fields and valid values, and match a patient's profile to recruiting trials. No API key required.
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 candidate recruiting trials, without checking inclusion/exclusion criteria |
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 |
Skills
| Skill | Description |
|---|---|
clinicaltrialsgov-guide | Gates and query syntax the tool descriptions don't carry: study versus site recruiting status, overlapping phase counts, RANGE and MISSING with partial dates and open-ended age limits, the status vocabulary, and reading posted results |
Plugin installs load it from skills/; MCP clients that support the Skills extension read it as skill://clinicaltrialsgov-guide/SKILL.md. It also ships in the npm package and Docker image.
Capability reference
clinicaltrials_search_studies <sub>tool</sub>
- Free-text
queryplus field-specificconditionQuery/interventionQuery/locationQuery/sponsorQuery/titleQuery/outcomeQuery,statusFilter/phaseFilterenums (case and spacing variants accepted),advancedFilter(AREA[FieldName]value/RANGE[min, max]), andgeoFilter(distance(lat,lon,radius)with ami/kmsuffix);pageSize(default 10, no upper bound; above the server's per-page cap, 200 by default, a page is served at the cap withtruncated/shown/cap),pageTokencursor,sorton up to 2 date or numeric fields (or@relevance) - A
fieldslist namingResultsSection, or a module or group under it (OutcomeMeasuresModule,AdverseEventsModule), or one the field metadata cannot check, caps the page at 10 studies (the server's cap when lower), since one record with posted results can run past 1 MB; the sametruncated/shown/capand anoticedisclose it. A leaf under results (OutcomeMeasureTitle) keeps the server's cap - Each query and filter parameter also accepts its ClinicalTrials.gov REST name (
query.condorquery_cond,filter.overallStatus,filter.geo, …), andcondition,intervention,sponsor,phase,limit, andmax_resultswork as aliases too - Returns a compact per-study index by default (
nctId,briefTitle,overallStatus,phases,enrollmentCount,hasResults, …);fields(PascalCase piece names, or full paths asclinicaltrials_get_field_definitionsprints them) selects a full-fidelity projection — full records run ~70KB nctIds(up to 1000 entries, repeats collapsed) is served up to what fits one ClinicalTrials.gov request beside the other parameters — about 560 IDs on their own — with the rest returned unqueried indeferredNctIds, the same on every page of a walk- Typed errors:
blank_value,ids_not_found,field_invalid,enum_invalid,query_parse_error,geo_invalid,sort_invalid,page_token_invalid,request_too_long,rate_limited,retry_deadline_exceeded,upstream_timeout,upstream_unavailable,upstream_rejected
clinicaltrials_get_study_record <sub>tool</sub>
- Full protocol record by
nctId, with optionallocationLimit,outcomeLimit/referenceLimit, andnearLocation(lat,lon,radiusMi) to bound and sort locations — none has an upper bound; a value past the list returns all of it, and0leaves that list out filtersAppliedreports upstream totals when a cap trims a list;resultsSectionis replaced byresultsSummarycounts. Typed errors:study_not_found,rate_limited,retry_deadline_exceeded,upstream_timeout,upstream_unavailable,upstream_rejected
clinicaltrials_get_study_count <sub>tool</sub>
- 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,request_too_long,rate_limited,retry_deadline_exceeded,upstream_timeout,upstream_unavailable,upstream_rejected
clinicaltrials_get_field_values <sub>tool</sub>
- One or more PascalCase leaf
fields(e.g.OverallStatus,Phase; index-only pieces such asNumLocationsandStudyFirstPostYearcount too, and so do record alt names such asOVERALL-STATUS; a group such asLeadSponsoris refused with its leaf pieces named, and so is a search area such asConditionSearch) — 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 - STRING fields add their
longestvalue; past 2,000 characters it is a preview flaggedtruncated, with the fulllengthand a notice naming theclinicaltrials_search_studiescall that returns the whole text. Free-text fields the API does not count distinct values on carryuniqueValuesCounted: false, marking theiruniqueValuesCountof 0 as no count multiValuedflags fields whose per-value counts can sum above the study total, index-only pieces under a repeated node included (LocationCountryCode). When the field metadata is unavailable or does not list a field, the flag is absent and anoticenames the field as not determined, to be treated as possibly repeated. Typed errors:blank_value,field_invalid,request_too_long,rate_limited,retry_deadline_exceeded,upstream_timeout,upstream_unavailable,upstream_rejected
clinicaltrials_get_field_definitions <sub>tool</sub>
mode:search(keywordquery,limitdefault 20, no upper bound),drill(dot-notationpath), oroverview— resolves the PascalCase names accepted byfields,advancedFilter,sort(date and numeric fields), andclinicaltrials_get_field_values(leaf fields); index-only pieces (drill withincludeIndexedOnly, e.g.NumLocations) work wherever a leaf does exceptfields- Typed errors:
blank_value,mode_mismatch,mode_requires,path_not_found,rate_limited,retry_deadline_exceeded,upstream_timeout,upstream_unavailable,upstream_rejected
clinicaltrials_get_study_results <sub>tool</sub>
- Up to 20 distinct
nctIdsfetched per call, with any further IDs (up to 500 entries) returned indeferredNctIds;sections(outcomes,adverseEvents,participantFlow,baseline,moreInfo),summaryto condense a result set that can exceed 500KB per study, andoutcomeLimit/adverseEventLimitwith resumableoutcomeOffset/seriousEventOffset/otherEventOffset - Per-study
fetchErrors/studiesWithoutResultsfor IDs that fail on their own, while a failed batch request, a rate limit, the call's time budget, or a cancellation ends the call;canonicalNctIdwhen an alias ID resolves to another study. Typed errors:blank_value,offset_not_applicable,rate_limited,retry_deadline_exceeded,upstream_timeout,upstream_unavailable,upstream_rejected
clinicaltrials_find_eligible <sub>tool</sub>
age(whole number 0–120) with optionalageUnit(yearsdefault,months,weeks,days) for infants and young children,sex(FEMALE/MALE/ALL),conditions[],location(countryrequired,state/cityoptional; country synonyms such asUSA,UK, andCzech Republic, and US state abbreviations, rank sites as the registry's spelling does),healthyVolunteer,recruitingOnly(default true),maxResults(orlimit; no upper bound, each call returns at most 50, disclosing the rest viatruncated/shown/capand a replayablesearchCriteria),locationLimit(no upper bound; a value past the matched sites returns them all)- Matches on age, sex, location, recruitment status, and a fuzzy condition search;
funnelreports match counts per filter stage, and anoticesays inclusion/exclusion criteria were not checked (useclinicaltrials_get_study_recordto review them) - Typed errors:
blank_value,field_invalid,enum_invalid,query_parse_error,request_too_long,rate_limited,retry_deadline_exceeded,upstream_timeout,upstream_unavailable,upstream_rejected
clinicaltrials://{nctId} <sub>resource</sub>
- 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,retry_deadline_exceeded,upstream_timeout,upstream_unavailable,upstream_rejected
analyze_trial_landscape <sub>prompt</sub>
- 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 408, 429, 500, 502, 503, and 504 responses, dropped or timed-out connections, an attempt's own timeout, and 200 bodies that are not the JSON the route serves (an HTML page, unparseable text, the wrong shape), all inside one 50-second budget per tool call or resource read — a hung upstream ends the call with a typed
retry_deadline_exceedederror before an MCP client's 60-second request timeout. Any other status (the 400, 404, and 414 it translates itself aside), and a refused or unresolvable connection, is sent once and ends asupstream_unavailable(5xx other than 501, connection failures) orupstream_rejected(501, 3xx, other 4xx, an HTML page in place of the API's own 404), each with a reason code and no upstream body - Auto-corrects field names passed to
fields/sort(case, whitespace, legacy aliases likeRecruitmentStatus→OverallStatus, a full path such asprotocolSection.statusModule.overallStatus→OverallStatusinfields, a barerelevance→@relevanceinsort) and the case ofAREA[…]field names inadvancedFilterand the query parameters (AREA[overallstatus]→AREA[OverallStatus]), index-only pieces and search areas included (AREA[numlocations],AREA[conditionsearch];sorttakes the index-only counts such asNumLocationstoo;clinicaltrials_get_field_valuesalso folds record alt names and index-only pieces,overall-status→OverallStatusandnumlocations→NumLocations), and logs each correction - Geographic proximity search and nearest-site re-ranking, with no geocoding dependency
- Excludes the "unknown" enrollment sentinel (
99999999) from searches and counts by default;includeUnknownEnrollmentbrings it back
Agent-friendly output:
- Provenance — search, count, and eligibility tools echo
searchCriteria;clinicaltrials_get_study_resultsnamescanonicalNctIdwhen an alias ID resolves to another study - Graceful partial failure —
clinicaltrials_get_study_resultsreturns per-studyfetchErrors/studiesWithoutResultsfor IDs that fail on their own instead of failing the whole batch - Typed errors — each tool returns
reasoncodes (study_not_found,blank_value, …), and bounded lists carry anext*Offsetonly when more remains - Response shaping — search and eligibility return a compact index by default instead of the ~70KB full 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.4 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 | Timeout in milliseconds for each attempt at a ClinicalTrials.gov request; a positive integer. An attempt ends sooner when its tool call or resource read runs out of its 50-second budget, which covers every request, retry, and wait the call makes. | 30000 |
CT_MAX_PAGE_SIZE | Most studies one clinicaltrials_search_studies page returns; an integer ≥ 1, values above 1000 clamp to 1000. | 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 for traces and metrics. | — |
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT | OTLP endpoint for log export. | — |
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/skills 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. |
skills/ | The published clinicaltrialsgov-guide skill for agents calling the tools. |
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.
Clinicaltrialsgov MCP Server alternatives
View more →Similar mcp servers in Coding Agents, ranked by GitHub stars. Compare licence, recent activity and scan status before you choose.
| Name | Stars | Licence | Last updated | Scan |
|---|---|---|---|---|
| Clinicaltrialsgov MCP Server (this page) cyanheads | 99 | Apache-2.0 | Oct 26, 2026 | Scanned, no flagged issues |
| Context Mode mksglu | 33k | None found | Nov 1, 2026 | Not scanned yet |
| Serena oraios | 31k | None found | Oct 28, 2026 | Not scanned yet |
| Fastmcp PrefectHQ | 28k | Apache-2.0 | Nov 1, 2026 | Scanned, no flagged issues |
| Nuclear nukeop | 19k | AGPL-3.0 | Nov 1, 2026 | Scanned, no flagged issues |
| MCP For Beginners microsoft | 18k | MIT | Oct 30, 2026 | Scanned, no flagged issues |