Search ClinicalTrials.gov — find studies, retrieve results, match patients to eligible trials.
Search ClinicalTrials.gov trials, retrieve study details and results, and match patients to eligible trials via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://clinicaltrials.caseyjhand.com/mcp
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.
| 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 |
| Resource | Description |
|---|---|
clinicaltrials://{nctId} | Fetch a single clinical study by NCT ID as JSON, with capped lists and results replaced by counts |
| Prompt | Description |
|---|---|
analyze_trial_landscape | Guides a data-driven clinical trial landscape analysis using the count and search tools |
clinicaltrials_search_studies toolquery plus field-specific conditionQuery / interventionQuery / locationQuery / sponsorQuery / titleQuery / outcomeQuery; statusFilter (case- and separator-insensitive, registry display labels included: "Active, not recruiting" works) / phaseFilter enums, advancedFilter (AREA[FieldName]value / RANGE[min, max] syntax), and geoFilter (distance(lat,lon,radius) with a mi/km suffix) for proximity search with nearest-site re-rankingnctId, briefTitle, overallStatus, phases, enrollmentCount, leadSponsor, conditions, hasResults, startDate, primaryCompletionDate, a bounded locations summary); pass fields (PascalCase leaves) for a full-fidelity projection — full records run ~70KBpageSize 1–CT_MAX_PAGE_SIZE (default 10; the cap is 200 unless overridden), cursor pagination via pageToken, sort on up to 2 fields99999999) by default — includeUnknownEnrollment to include it, or automatically lifted when nctIds is suppliedblank_value, ids_not_found, field_invalid, enum_invalid, query_parse_error, geo_invalid, sort_invalid, rate_limitedclinicaltrials_get_study_record toollocationLimit (≤500), outcomeLimit / referenceLimit (≤100), and nearLocation (lat, lon, radiusMi default 50) to bound and sort locations; upstream totals reported in filtersApplied only when a cap actually trims the listresultsSection is replaced by compact resultsSummary counts — fetch full results via clinicaltrials_get_study_resultsstudy_not_found, rate_limitedclinicaltrials_get_study_count toolclinicaltrials_search_studies (free-text and field-specific queries, status/phase filters, advancedFilter) but returns only totalCount — no study data fetchedincludeUnknownEnrollment to include it)blank_value, field_invalid, enum_invalid, query_parse_error, rate_limitedclinicaltrials_get_field_values toolOverallStatus, Phase, LeadSponsorClass) — returns each field's type, unique-value count, and top values with study counts (capped at 250 by the API)min / max / avg / formats instead of top values; boolean fields report trueCount / falseCountmultiValued flags fields where a study can carry several values, so per-value study counts can sum above the study totalblank_value, field_invalid, rate_limitedclinicaltrials_get_field_definitions toolsearch (keyword, ranked matches, limit up to 100, default 20), drill (dot-notation path into a section), overview (top-level sections, no other args)fields, advancedFilter, sort, and clinicaltrials_get_field_valuesblank_value, mode_mismatch, mode_requires, path_not_found, rate_limitedclinicaltrials_get_study_results toolhasResults is true — outcome measures, adverse events, participant flow, baseline characteristics, and results metadatasummary (default false) condenses a full result set — which can exceed 500KB per study — to a few KB; full mode supports outcomeLimit (≤100) and adverseEventLimit (≤500), resumable via outcomeOffset / seriousEventOffset / otherEventOffsetsections filters to outcomes, adverseEvents, participantFlow, baseline, moreInfocanonicalNctIdblank_value, offset_not_applicable, rate_limitedclinicaltrials_find_eligible toolage, sex (FEMALE / MALE / ALL), conditions[], location (country required, state / city optional), healthyVolunteer, recruitingOnly (default true), maxResults (≤50)locationLimit, ≤500) instead of every registered site, adding one recruiting site when none of the matched ones is open — the one nearest the matched sites by published coordinates (with distanceMi), kept to the requested country when a site there recruits, or the first in match order when coordinates are missingfunnel reports match counts at each filter stage (condition → +location → +demographics) to show where the query narrowed to zeroblank_value, rate_limitedclinicaltrials://{nctId} resourceapplication/json, with locations, secondary/other outcomes, and references each capped at 50 — fixed server-side, no argumentsresultsSummary counts; truncated and filtersApplied disclose what was capped, with retrieval naming the tools that fetch the full datastudy_not_found, rate_limitedanalyze_trial_landscape prompttopic required; focusAreas (comma-separated) optionalBuilt 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:
fields/sort — case/whitespace fixes and known legacy aliases (e.g. RecruitmentStatus → OverallStatus) — before validating, logging every correctionAgent-friendly output:
clinicaltrials_search_studies / clinicaltrials_get_study_count / clinicaltrials_find_eligible echo searchCriteria on every call, including sentinelFilterActive when the default unknown-enrollment exclusion applies, and clinicaltrials_get_study_results names canonicalNctId when a previous (alias) ID resolves to a different studyclinicaltrials_get_study_results returns per-study fetchErrors / studiesWithoutResults rows instead of failing the whole batch when one ID is malformed or lacks resultsreason codes per tool (study_not_found, blank_value, offset_not_applicable, …), and bounded lists (filtersApplied, locationSummary) carry a next*Offset only when more remains, so callers branch on presence instead of parsing textclinicaltrials_search_studies and clinicaltrials_find_eligible return a compact per-study index or location-bounded set by default instead of the ~70KB full record, escalating to full fidelity only via fields or clinicaltrials_get_study_recordA 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"
}
}
}
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
git clone https://github.com/cyanheads/clinicaltrialsgov-mcp-server.git
cd clinicaltrialsgov-mcp-server
bun install
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.
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 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.
| 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. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, no console callssrc/mcp-server/*/definitions/index.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y clinicaltrialsgov-mcp-serverMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-cyanheads-clinicaltrialsgov-mcp-server": {
"command": "npx",
"args": [
"-y",
"clinicaltrialsgov-mcp-server"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceio.github.cyanheads/clinicaltrialsgov-mcp-server works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.