clinicaltrialsgov-mcp-server

Search ClinicalTrials.gov trials, retrieve study details and results, and match patients to candidate trials via MCP. STDIO or Streamable HTTP.

7 Tools • 1 Resource • 1 Prompt • 1 Skill

[![Version](https://img.shields.io/badge/Version-2.11.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/clinicaltrialsgov-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.4.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/clinicaltrialsgov-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/clinicaltrialsgov-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.4-blueviolet.svg?style=flat-square)](https://bun.sh/)
[![Install in Claude Desktop](https://img.shields.io/badge/Install_in-Claude_Desktop-D97757?style=for-the-badge&logo=anthropic&logoColor=white)](https://github.com/cyanheads/clinicaltrialsgov-mcp-server/releases/latest/download/clinicaltrialsgov-mcp-server.mcpb) [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=clinicaltrialsgov-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImNsaW5pY2FsdHJpYWxzZ292LW1jcC1zZXJ2ZXIiXX0=) [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22clinicaltrialsgov-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22clinicaltrialsgov-mcp-server%22%5D%7D) [![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-67E8F9?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
**Public Hosted Server:** [https://clinicaltrials.caseyjhand.com/mcp](https://clinicaltrials.caseyjhand.com/mcp)
--- ## Overview Search 600K+ studies on [ClinicalTrials.gov](https://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`](./skills/clinicaltrialsgov-guide/SKILL.md) | 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` tool - Free-text `query` plus field-specific `conditionQuery` / `interventionQuery` / `locationQuery` / `sponsorQuery` / `titleQuery` / `outcomeQuery`, `statusFilter` / `phaseFilter` enums (case and spacing variants accepted), `advancedFilter` (`AREA[FieldName]value` / `RANGE[min, max]`), and `geoFilter` (`distance(lat,lon,radius)` with a `mi`/`km` suffix); `pageSize` 1–1000 (default 10; above the server's per-page cap, 200 by default, a page is served at the cap with `truncated` / `shown` / `cap`), `pageToken` cursor, `sort` on 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 - `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 in `deferredNctIds`, 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` --- ### `clinicaltrials_get_study_record` tool - Full protocol record by `nctId`, with optional `locationLimit`, `outcomeLimit` / `referenceLimit`, and `nearLocation` (`lat`, `lon`, `radiusMi`) to bound and sort locations — none has an upper bound; a value past the list returns all of it - `filtersApplied` reports upstream totals when a cap trims a list; `resultsSection` is replaced by `resultsSummary` counts. Typed errors: `study_not_found`, `rate_limited` --- ### `clinicaltrials_get_study_count` tool - Same query and filter surface as `clinicaltrials_search_studies`, returning only `totalCount` — no study data fetched - Typed errors: `blank_value`, `field_invalid`, `enum_invalid`, `query_parse_error`, `request_too_long`, `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), or `min` / `max` / `avg` for numeric and date fields and `trueCount` / `falseCount` for booleans - STRING fields add their `longest` value; past 2,000 characters it is a preview flagged `truncated`, with the full `length` and a notice naming the `clinicaltrials_search_studies` call that returns the whole text. Free-text fields the API does not count distinct values on carry `uniqueValuesCounted: false`, marking their `uniqueValuesCount` of 0 as no count - `multiValued` flags fields whose per-value counts can sum above the study total. Typed errors: `blank_value`, `field_invalid`, `request_too_long`, `rate_limited` --- ### `clinicaltrials_get_field_definitions` tool - `mode`: `search` (keyword `query`, `limit` default 20, no upper bound), `drill` (dot-notation `path`), or `overview` — resolves the PascalCase names accepted by `fields`, `advancedFilter`, `sort`, and `clinicaltrials_get_field_values` - Typed errors: `blank_value`, `mode_mismatch`, `mode_requires`, `path_not_found`, `rate_limited` --- ### `clinicaltrials_get_study_results` tool - Up to 20 distinct `nctIds` fetched per call, with any further IDs (up to 500 entries) returned in `deferredNctIds`; `sections` (`outcomes`, `adverseEvents`, `participantFlow`, `baseline`, `moreInfo`), `summary` to condense a result set that can exceed 500KB per study, and `outcomeLimit` / `adverseEventLimit` with resumable `outcomeOffset` / `seriousEventOffset` / `otherEventOffset` - Per-study `fetchErrors` / `studiesWithoutResults` instead of a whole-batch failure, and `canonicalNctId` when an alias ID resolves to another study. Typed errors: `blank_value`, `offset_not_applicable`, `rate_limited` --- ### `clinicaltrials_find_eligible` tool - `age` (whole number 0–120) with optional `ageUnit` (`years` default, `months`, `weeks`, `days`) for infants and young children, `sex` (`FEMALE` / `MALE` / `ALL`), `conditions[]`, `location` (`country` required, `state` / `city` optional), `healthyVolunteer`, `recruitingOnly` (default true), `maxResults` (up to 1000; each call returns at most 50, disclosing the rest via `truncated` / `shown` / `cap` and a replayable `searchCriteria`), `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; `funnel` reports match counts per filter stage, and a `notice` says inclusion/exclusion criteria were not checked (use `clinicaltrials_get_study_record` to review them) - Typed errors: `blank_value`, `field_invalid`, `query_parse_error`, `request_too_long`, `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 `resultsSummary` counts; `truncated` and `filtersApplied` disclose what was capped, with `retrieval` naming the tools that fetch the full data - Typed errors: `study_not_found`, `rate_limited` --- ### `analyze_trial_landscape` prompt - Arguments: `topic` required; `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`](https://github.com/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](https://clinicaltrials.gov/data-api/api) — 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 and HTML error pages - Auto-corrects field names passed to `fields`/`sort` (case, whitespace, legacy aliases like `RecruitmentStatus` → `OverallStatus`) and the case of `AREA[…]` field names in `advancedFilter` and the query parameters (`AREA[overallstatus]` → `AREA[OverallStatus]`), 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; `includeUnknownEnrollment` brings it back Agent-friendly output: - Provenance — search, count, and eligibility tools echo `searchCriteria`; `clinicaltrials_get_study_results` names `canonicalNctId` when an alias ID resolves to another study - Graceful partial failure — `clinicaltrials_get_study_results` returns per-study `fetchErrors` / `studiesWithoutResults` instead of failing the whole batch - Typed errors — each tool returns `reason` codes (`study_not_found`, `blank_value`, …), and bounded lists carry a `next*Offset` only 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: ```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: ```sh MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http # Server listens at http://localhost:3010/mcp ``` ### Prerequisites - [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+). ### Installation 1. **Clone the repository:** ```sh git clone https://github.com/cyanheads/clinicaltrialsgov-mcp-server.git ``` 2. **Navigate into the directory:** ```sh cd clinicaltrialsgov-mcp-server ``` 3. **Install dependencies:** ```sh 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; a positive integer. | `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). | `/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`](./.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:** ```sh 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 ```sh 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`](./CLAUDE.md) for development guidelines and architectural rules. The short version: - Handlers throw, framework catches — no `try/catch` in tool logic - Use `ctx.log` for request-scoped logging, no `console` calls - 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: ```sh bun run devcheck bun run test ``` ## License Apache-2.0 — see [LICENSE](LICENSE) for details.