Skip to content

cyanheads/clinicaltrialsgov-mcp-server

v2.10.2Apache-2.0

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

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework

Public Hosted Server: https://clinicaltrials.caseyjhand.com/mcp


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

ToolDescription
clinicaltrials_search_studiesSearch studies with full-text and field-specific queries, status/phase/geographic filters, pagination, sorting, and field selection
clinicaltrials_get_study_recordFetch a single study by NCT ID — full protocol record with optional location/outcome/reference caps
clinicaltrials_get_study_countFast total study count for a query, without fetching data
clinicaltrials_get_field_valuesDiscover valid values for API fields, with per-value study counts
clinicaltrials_get_field_definitionsResolve valid field names — keyword search, path drill-down, or top-level overview
clinicaltrials_get_study_resultsFetch posted results — outcomes, adverse events, participant flow, baseline — for completed studies
clinicaltrials_find_eligibleMatch patient demographics and conditions to candidate recruiting trials, without checking inclusion/exclusion criteria

Resources

ResourceDescription
clinicaltrials://{nctId}Fetch a single clinical study by NCT ID as JSON, with capped lists and results replaced by counts

Prompts

PromptDescription
analyze_trial_landscapeGuides a data-driven clinical trial landscape analysis using the count and search tools

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
  • Typed errors: blank_value, ids_not_found, field_invalid, enum_invalid, query_parse_error, geo_invalid, sort_invalid, page_token_invalid, 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, 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, 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, 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: 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 and HTML error pages
  • Auto-corrects field names passed to fields/sort (case, whitespace, legacy aliases like RecruitmentStatus → 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:

{
  "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

Installation

  1. Clone the repository:
git clone https://github.com/cyanheads/clinicaltrialsgov-mcp-server.git
  1. Navigate into the directory:
cd clinicaltrialsgov-mcp-server
  1. Install dependencies:
bun install

Configuration

All configuration is optional — the server works with defaults and no API keys.

VariableDescriptionDefault
CT_API_BASE_URLClinicalTrials.gov API base URL.https://clinicaltrials.gov/api/v2
CT_REQUEST_TIMEOUT_MSPer-request timeout in milliseconds; a positive integer.30000
CT_MAX_PAGE_SIZEMost studies one clinicaltrials_search_studies page returns; an integer ≥ 1, values above 1000 clamp to 1000.200
MCP_TRANSPORT_TYPETransport: stdio or http.stdio
MCP_HTTP_PORTPort for HTTP server.3010
MCP_SESSION_MODEHTTP session mode: stateless, stateful, or auto.stateless
MCP_AUTH_MODEAuth mode: none, jwt, or oauth.none
MCP_LOG_LEVELLog level (RFC 5424).info
LOGS_DIRDirectory for log files (Node.js only).<project-root>/logs
OTEL_ENABLEDEnable OpenTelemetry tracing.false
OTEL_EXPORTER_OTLP_ENDPOINTOTLP base URL for traces and metrics.—
OTEL_EXPORTER_OTLP_LOGS_ENDPOINTOTLP endpoint for log export.—
LOG_TOOL_FAILURE_PAYLOADSLog 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

DirectoryPurpose
src/index.tscreateApp() entry point — registers tools/resources/prompts and inits the ClinicalTrials.gov service.
src/configServer-specific environment variable parsing and validation with Zod.
src/mcp-server/toolsTool definitions (*.tool.ts).
src/mcp-server/resourcesResource definitions (*.resource.ts).
src/mcp-server/promptsPrompt definitions (*.prompt.ts).
src/services/clinical-trialsClinicalTrials.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/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:

bun run devcheck
bun run test

License

Apache-2.0 — see LICENSE for details.