Public 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 ~577K 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]syntax), andgeoFilter(distance(lat,lon,radius)with ami/kmsuffix) for proximity search with nearest-site re-ranking - Returns a compact per-study index by default (
nctId,briefTitle,overallStatus,phases,enrollmentCount,leadSponsor,conditions, a bounded locations summary); passfields(PascalCase leaves) for a full-fidelity projection — full records run ~70KB pageSize1–CT_MAX_PAGE_SIZE(default 200), cursor pagination viapageToken,sorton up to 2 fields- Excludes the upstream "unknown" enrollment sentinel (
99999999) by default —includeUnknownEnrollmentto include it, or automatically lifted whennctIdsis supplied - 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 NCT ID — identification, status, sponsor, conditions, design, arms/interventions, outcomes, eligibility, contacts/locations
- Optional
locationLimit(≤500),outcomeLimit/referenceLimit(≤100), andnearLocation(lat,lon,radiusMidefault 50) to bound and sort locations; upstream totals reported infiltersAppliedonly when a cap actually trims the list resultsSectionis replaced by compactresultsSummarycounts — fetch full results viaclinicaltrials_get_study_results- Typed errors:
study_not_found,rate_limited
clinicaltrials_get_study_count tool
- Same query/filter surface as
clinicaltrials_search_studies(free-text and field-specific queries, status/phase filters,advancedFilter) but returns onlytotalCount— no study data fetched - Excludes the unknown-enrollment sentinel by default (
includeUnknownEnrollmentto include it) - Typed errors:
blank_value,field_invalid,enum_invalid,query_parse_error,rate_limited
clinicaltrials_get_field_values tool
- One or more PascalCase field names (e.g.
OverallStatus,Phase,LeadSponsorClass) — returns each field's type, unique-value count, and top values with study counts (capped at 250 by the API) - Numeric/date fields report
min/max/avg/formatsinstead of top values; boolean fields reporttrueCount/falseCount multiValuedflags fields where a study can carry several values, so per-value study counts can sum above the study total- Typed errors:
blank_value,field_invalid,rate_limited
clinicaltrials_get_field_definitions tool
- Three modes:
search(keyword, ranked matches,limitup to 100, default 20),drill(dot-notationpathinto a section),overview(top-level sections, no other args) - Resolves the canonical PascalCase field names accepted by
fields,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 NCT IDs per call; only returns data for studies where
hasResultsis true — outcome measures, adverse events, participant flow, baseline characteristics, and results metadata summary(default false) condenses a full result set — which can exceed 500KB per study — to a few KB; full mode supportsoutcomeLimit(≤100) andadverseEventLimit(≤500), resumable viaoutcomeOffset/seriousEventOffset/otherEventOffsetsectionsfilters tooutcomes,adverseEvents,participantFlow,baseline,moreInfo- A previous (alias) NCT ID resolves to its canonical study, named in
canonicalNctId - Typed errors:
blank_value,offset_not_applicable,rate_limited
clinicaltrials_find_eligible tool
- Takes
age,sex(FEMALE/MALE/ALL),conditions[],location(countryrequired,state/cityoptional),healthyVolunteer,recruitingOnly(default true),maxResults(≤50) - Re-ranks results so studies whose own condition list names a requested condition surface above tangential MeSH-umbrella matches from the upstream fuzzy search
- Bounds each candidate's locations to the sites matching the requested location (capped by
locationLimit, ≤500) instead of every registered site, adding the nearest recruiting site when none of the matched ones is open funnelreports match counts at each filter stage (condition → +location → +demographics) to show where the query narrowed to zero- 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
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:
json{ "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.
json{ "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):
json{ "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:
json{ "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:
shMCP_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:
shgit clone https://github.com/cyanheads/clinicaltrialsgov-mcp-server.git
- Navigate into the directory:
shcd clinicaltrialsgov-mcp-server
- Install dependencies:
shbun 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 |
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:http # or bun run start:stdio -
Run checks and tests:
shbun run devcheck # Lint, format, typecheck, and security audit bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec
Docker
shdocker 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:
shbun run devcheck bun run test
License
Apache-2.0 — see LICENSE for details.



