Skip to main content
Glama
clinicaltrialsgov-mcp-server

clinicaltrialsgov-mcp-server

by cyanheads

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

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

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.

Related MCP server: ClinicalTrials.gov MCP Server

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 (default 10, no upper bound; 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 date or numeric fields (or @relevance)

  • Each query and filter parameter also accepts its ClinicalTrials.gov REST name (query.cond or query_cond, filter.overallStatus, filter.geo, …), and condition, intervention, sponsor, phase, limit, and max_results work as aliases too

  • Returns a compact per-study index by default (nctId, briefTitle, overallStatus, phases, enrollmentCount, hasResults, …); fields (PascalCase piece names, or full paths as clinicaltrials_get_field_definitions prints them) 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, retry_deadline_exceeded, upstream_timeout


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, and 0 leaves that list out

  • filtersApplied reports upstream totals when a cap trims a list; resultsSection is replaced by resultsSummary counts. Typed errors: study_not_found, rate_limited, retry_deadline_exceeded, upstream_timeout


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, retry_deadline_exceeded, upstream_timeout


clinicaltrials_get_field_values tool

  • One or more PascalCase leaf fields (e.g. OverallStatus, Phase; a group such as LeadSponsor is refused with its leaf pieces named) — 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, retry_deadline_exceeded, upstream_timeout


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 (date and numeric fields), and clinicaltrials_get_field_values (leaf fields)

  • Typed errors: blank_value, mode_mismatch, mode_requires, path_not_found, rate_limited, retry_deadline_exceeded, upstream_timeout


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 for IDs that fail on their own, while a failed batch request, a rate limit, the call's time budget, or a cancellation ends the call; canonicalNctId when an alias ID resolves to another study. Typed errors: blank_value, offset_not_applicable, rate_limited, retry_deadline_exceeded, upstream_timeout


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; country synonyms such as USA, UK, and Czech Republic, and US state abbreviations, rank sites as the registry's spelling does), healthyVolunteer, recruitingOnly (default true), maxResults (or limit; no upper bound, 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, enum_invalid, query_parse_error, request_too_long, rate_limited, retry_deadline_exceeded, upstream_timeout


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, retry_deadline_exceeded, upstream_timeout


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, all inside one 50-second budget per tool call or resource read — a hung upstream ends the call with a typed retry_deadline_exceeded error before an MCP client's 60-second request timeout

  • Auto-corrects field names passed to fields/sort (case, whitespace, legacy aliases like RecruitmentStatus → OverallStatus, a full path such as protocolSection.statusModule.overallStatus → OverallStatus in fields, a bare relevance → @relevance in sort) and the case of AREA[…] field names in advancedFilter and the query parameters (AREA[overallstatus] → AREA[OverallStatus]), index-only pieces and search areas included (AREA[numlocations], AREA[conditionsearch]; sort takes the index-only counts such as NumLocations too), 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 for IDs that fail on their own 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.

Variable

Description

Default

CT_API_BASE_URL

ClinicalTrials.gov API base URL.

https://clinicaltrials.gov/api/v2

CT_REQUEST_TIMEOUT_MS

Timeout in milliseconds for each attempt at a ClinicalTrials.gov request; a positive integer. An attempt ends sooner when its tool call or resource read runs out of its 50-second budget, which covers every request, retry, and wait the call makes.

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).

<project-root>/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 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

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 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.

Available Tools

7 tools
clinicaltrials_find_eligibleClinicaltrials Find EligibleA
Read-onlyIdempotent
Inspect

Find candidate clinical trials for a patient profile. Provide age, sex, conditions, and location to get studies with their demographic limits, contact information, and recruiting locations. Candidates are matched on age, sex, location, recruitment status, and ClinicalTrials.gov's fuzzy condition search only — inclusion/exclusion criteria are not checked, and a candidate can exclude the requested condition, so review each one's criteria with clinicaltrials_get_study_record before treating it as a match. Results are re-ranked so studies whose own condition matches a requested condition surface above tangential matches from ClinicalTrials.gov's fuzzy condition search. Each candidate returns only the sites matching the requested location (capped by locationLimit), not the study's full registered site list — a large trial can register hundreds of sites worldwide. When none of a candidate's matched sites is recruiting, one recruiting site is added so an enrollable site is never hidden behind a closer closed one: the one nearest the matched sites by their published coordinates, in the requested country whenever a site there recruits, carrying distanceMi, or the first in match order when coordinates are missing. Fetch a study's complete record with clinicaltrials_get_study_record.

Inputs: age (required), sex (required), location (required), conditions (required), +5 more. Returns: cap, error, shown, funnel, +5 more

clinicaltrials_get_field_definitionsClinicaltrials Get Field DefinitionsA
Read-onlyIdempotent
Inspect

Resolve valid field names from the ClinicalTrials.gov data model — the canonical PascalCase identifiers (OverallStatus, EnrollmentCount, LeadSponsorName) the other tools take. Every piece works in clinicaltrials_search_studies fields; a leaf piece (sourceType other than STRUCT) also works in AREA[…] expressions in advancedFilter and the query parameters, and as input to clinicaltrials_get_field_values (LocationGeoPoint aside); a leaf of a date or integer type (NormalizedDate, PartialDate, DateTimeMinutes, integer, long, short) also works in sort. Select a mode: "search" — keyword search returning ranked matches (pass query, e.g. "enrollment", "sponsor", "adverse events"); "drill" — drill into a specific section by dot-notation path (pass path, e.g. "protocolSection.designModule"); "overview" — top-level summary of all sections (no additional args).

Inputs: mode (required), path, limit, query, +1 more. Returns: cap, error, shown, fields, +6 more

clinicaltrials_get_field_valuesClinicaltrials Get Field ValuesA
Read-onlyIdempotent
Inspect

Discover valid values for ClinicalTrials.gov fields with study counts per value. Use to explore available filter options before building a search — e.g., valid OverallStatus, Phase, InterventionType, StudyType, or LeadSponsorClass values.

Inputs: fields (required). Returns: error, notice, fieldStats

clinicaltrials_get_study_countClinicaltrials Get Study CountA
Read-onlyIdempotent
Inspect

Get total clinical trial study count from ClinicalTrials.gov matching a query, without fetching study data. Fast and lightweight. Use for quick statistics or to build breakdowns by calling multiple times with different filters (e.g., count by phase, count by status, count recruiting vs completed for a condition).

Inputs: query, titleQuery, phaseFilter, outcomeQuery, +7 more. Returns: error, notice, totalCount, searchCriteria

clinicaltrials_get_study_recordClinicaltrials Get Study RecordA
Read-onlyIdempotent
Inspect

Fetch a single clinical trial study by NCT ID from ClinicalTrials.gov. Returns the full study record including protocol details, eligibility criteria, outcomes, arms, interventions, contacts, and locations. Optional locationLimit / outcomeLimit / referenceLimit / nearLocation parameters trim locations, outcomes, and references — original totals are preserved in filtersApplied only when a cap actually trims the set.

Inputs: nctId (required), nearLocation, outcomeLimit, locationLimit, +1 more. Returns: error, study, filtersApplied, resultsSummary

clinicaltrials_get_study_resultsClinicaltrials Get Study ResultsA
Read-onlyIdempotent
Inspect

Fetch clinical trial results data from ClinicalTrials.gov for completed studies — outcome measures with statistics, adverse events, participant flow, baseline characteristics, and results metadata (limitations & caveats, certain-agreement disclosure restrictions, results point of contact). Only available for studies where hasResults is true. Use clinicaltrials_search_studies first to find studies with results. Each call fetches up to 20 distinct NCT IDs and returns any further IDs in deferredNctIds for a follow-up call. A results-rich record can exceed 500KB per study in full mode — bound it with summary=true, narrower sections, or the outcomeLimit / adverseEventLimit caps; outcomeLimit / outcomeOffset also page summarized outcomes. A bounded list is resumable: outcomeOffset / seriousEventOffset / otherEventOffset start the next window, and each study's filtersApplied reports what was trimmed and the next offset for every list left short. A previous (alias) NCT ID resolves to its canonical study, named in canonicalNctId.

Inputs: nctIds (required), summary, sections, outcomeLimit, +4 more. Returns: error, results, truncated, fetchErrors, +2 more

clinicaltrials_search_studiesClinicaltrials Search StudiesA
Read-onlyIdempotent
Inspect

Search for clinical trial studies from ClinicalTrials.gov. Supports full-text and field-specific queries, status/phase/geographic filters, pagination, sorting, and field selection. Returns a compact per-study index by default; pass the fields parameter to get specific leaves at full fidelity — full study records are ~70KB each.

Inputs: sort, query, fields, nctIds, +14 more. Returns: cap, error, shown, notice, +8 more

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.2/5.0

    Scored across 7 tools

    Disambiguation5/5

    Each tool targets a distinct operation: aggregate counts, search index retrieval, full single-record fetch, results fetch, valid field values, valid field names, and patient eligibility matching. Descriptions explicitly delineate near-neighbors such as get_study_count vs search_studies and get_field_values vs get_field_definitions.

    Naming Consistency5/5

    All names share the clinicaltrials_ prefix and follow a consistent snake_case verb_noun pattern (get_study_count, search_studies, get_study_record, find_eligible). Predictable and uniform throughout the set.

    Tool Count5/5

    Seven tools is well-scoped for a read-only data API wrapper, with each tool earning its place by covering a distinct query mode. No redundant or filler tools are present.

    Completeness4/5

    The surface covers discovery, full records, results data, field metadata introspection, and patient matching, which is the appropriate scope for a read-only public dataset with no write operations. A minor gap is batch full-record fetching—get_study_record accepts only a single NCT ID—though the search index partially mitigates this.

    Maintenance

    ActivityActive
    ResponsivenessLimited data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      A
      maintenance
      Provides LLMs with structured access to critical biomedical databases including PubTator3 (PubMed/PMC), ClinicalTrials.gov, and MyVariant.info through the Model Context Protocol.
      46
      35
      765 PyPI
      674
      MIT
    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables AI agents to search and analyze clinical trial data from ClinicalTrials.gov using both structured SQL queries for filtering trials by status, phase, and conditions, and semantic vector search for exploring detailed protocol information like exclusion criteria.
      -
    • A
      license
      A
      quality
      D
      maintenance
      Enables conversational access to 400,000+ clinical trials on ClinicalTrials.gov, allowing users to search, compare, and retrieve trial details using plain English through MCP-compatible clients like Cursor and Claude Desktop.
      5
      3
      MIT