Agent Skills

ClinicalTrials.gov

Search and retrieve clinical trial data from the official ClinicalTrials.gov API.

Install

Install and configure the MCP from https://github.com/cyanheads/clinicaltrialsgov-mcp-server now. Follow the repository's installation instructions, ask me for anything you can't complete yourself, and verify its tools load.
README

clinicaltrialsgov-mcp-server

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

7 Tools • 1 Resource • 1 Prompt


Overview

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.

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 eligible recruiting trials

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 (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-ranking
  • Returns a compact per-study index by default (nctId, 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 ~70KB
  • pageSize 1–CT_MAX_PAGE_SIZE (default 10; the cap is 200 unless overridden), cursor pagination via pageToken, sort on up to 2 fields
  • Excludes the upstream "unknown" enrollment sentinel (99999999) by default — includeUnknownEnrollment to include it, or automatically lifted when nctIds is 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), and nearLocation (lat, lon, radiusMi default 50) to bound and sort locations; upstream totals reported in filtersApplied only when a cap actually trims the list
  • resultsSection is replaced by compact resultsSummary counts — fetch full results via clinicaltrials_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 only totalCount — no study data fetched
  • Excludes the unknown-enrollment sentinel by default (includeUnknownEnrollment to 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 / formats instead of top values; boolean fields report trueCount / falseCount
  • multiValued flags 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, limit up to 100, default 20), drill (dot-notation path into a section), overview (top-level sections, no other args)
  • Resolves the canonical PascalCase field 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 NCT IDs per call; only returns data for studies where hasResults is 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 supports outcomeLimit (≤100) and adverseEventLimit (≤500), resumable via outcomeOffset / seriousEventOffset / otherEventOffset
  • sections filters to outcomes, 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 (country required, state / city optional), 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 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 missing
  • funnel reports 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 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
  • 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_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 study
  • Graceful partial failure — clinicaltrials_get_study_results returns per-study fetchErrors / studiesWithoutResults rows instead of failing the whole batch when one ID is malformed or lacks results
  • Discriminated output — typed error reason 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 text
  • Response shaping — clinicaltrials_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_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.30000
CT_MAX_PAGE_SIZEMaximum page size cap.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

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.

Search skills and MCP servers

Search across 31,816 skills and MCPs