trial_searcher
Search ClinicalTrials.gov for clinical trials by conditions, interventions, location, phase, and eligibility. Filter results efficiently using geolocation, recruiting status, and study type. Returns detailed trial information for medical research.
Instructions
Search ClinicalTrials.gov for clinical studies.
⚠️ PREREQUISITE: Use the 'think' tool FIRST to plan your research strategy!
Comprehensive search tool for finding clinical trials based on multiple criteria.
Supports filtering by conditions, interventions, location, phase, and eligibility.
Location search notes:
- Use either location term OR lat/long coordinates, not both
- For city-based searches, AI agents should geocode to lat/long first
- Distance parameter only works with lat/long coordinates
Returns a formatted list of matching trials with key details.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| age_group | No | Filter by age group | |
| conditions | No | Medical conditions to search for | |
| distance | No | Distance in miles from lat/long coordinates | |
| funder_type | No | Filter by funding source | |
| healthy_volunteers | No | Filter by healthy volunteer eligibility | |
| interventions | No | Treatment interventions to search for | |
| lat | No | Latitude for location-based search. AI agents should geocode city names before using. | |
| location | No | Location term for geographic filtering | |
| long | No | Longitude for location-based search. AI agents should geocode city names before using. | |
| other_terms | No | Additional search terms | |
| page | No | Page number (1-based) | |
| page_size | No | Results per page | |
| phase | No | Filter by clinical trial phase | |
| recruiting_status | No | Filter by recruiting status | |
| sex | No | Filter by biological sex | |
| study_type | No | Filter by study type |
Implementation Reference
- src/biomcp/individual_tools.py:163-301 (handler)Primary MCP tool handler 'trial_searcher' decorated with @mcp_app.tool(). This is the main entrypoint for the tool, defining the schema via Annotated parameters and delegating to domain-specific _trial_searcher.@mcp_app.tool() @track_performance("biomcp.trial_searcher") async def trial_searcher( conditions: Annotated[ list[str] | str | None, Field(description="Medical conditions to search for"), ] = None, interventions: Annotated[ list[str] | str | None, Field(description="Treatment interventions to search for"), ] = None, other_terms: Annotated[ list[str] | str | None, Field(description="Additional search terms"), ] = None, recruiting_status: Annotated[ Literal["OPEN", "CLOSED", "ANY"] | None, Field(description="Filter by recruiting status"), ] = None, phase: Annotated[ Literal[ "EARLY_PHASE1", "PHASE1", "PHASE2", "PHASE3", "PHASE4", "NOT_APPLICABLE", ] | None, Field(description="Filter by clinical trial phase"), ] = None, location: Annotated[ str | None, Field(description="Location term for geographic filtering"), ] = None, lat: Annotated[ float | None, Field( description="Latitude for location-based search. AI agents should geocode city names before using.", ge=-90, le=90, ), ] = None, long: Annotated[ float | None, Field( description="Longitude for location-based search. AI agents should geocode city names before using.", ge=-180, le=180, ), ] = None, distance: Annotated[ int | None, Field( description="Distance in miles from lat/long coordinates", ge=1, ), ] = None, age_group: Annotated[ Literal["CHILD", "ADULT", "OLDER_ADULT"] | None, Field(description="Filter by age group"), ] = None, sex: Annotated[ Literal["FEMALE", "MALE", "ALL"] | None, Field(description="Filter by biological sex"), ] = None, healthy_volunteers: Annotated[ Literal["YES", "NO"] | None, Field(description="Filter by healthy volunteer eligibility"), ] = None, study_type: Annotated[ Literal["INTERVENTIONAL", "OBSERVATIONAL", "EXPANDED_ACCESS"] | None, Field(description="Filter by study type"), ] = None, funder_type: Annotated[ Literal["NIH", "OTHER_GOV", "INDUSTRY", "OTHER"] | None, Field(description="Filter by funding source"), ] = None, page: Annotated[ int, Field(description="Page number (1-based)", ge=1), ] = 1, page_size: Annotated[ int, Field(description="Results per page", ge=1, le=100), ] = 10, ) -> str: """Search ClinicalTrials.gov for clinical studies. ⚠️ PREREQUISITE: Use the 'think' tool FIRST to plan your research strategy! Comprehensive search tool for finding clinical trials based on multiple criteria. Supports filtering by conditions, interventions, location, phase, and eligibility. Location search notes: - Use either location term OR lat/long coordinates, not both - For city-based searches, AI agents should geocode to lat/long first - Distance parameter only works with lat/long coordinates Returns a formatted list of matching trials with key details. """ # Validate location parameters if location and (lat is not None or long is not None): raise ValueError( "Use either location term OR lat/long coordinates, not both" ) if (lat is not None and long is None) or ( lat is None and long is not None ): raise ValueError( "Both latitude and longitude must be provided together" ) if distance is not None and (lat is None or long is None): raise ValueError( "Distance parameter requires both latitude and longitude" ) # Convert single values to lists conditions = ensure_list(conditions) if conditions else None interventions = ensure_list(interventions) if interventions else None other_terms = ensure_list(other_terms) if other_terms else None return await _trial_searcher( call_benefit="Direct clinical trial search for specific criteria", conditions=conditions, interventions=interventions, terms=other_terms, recruiting_status=recruiting_status, phase=phase, lat=lat, long=long, distance=distance, age_group=age_group, study_type=study_type, page_size=page_size, )
- src/biomcp/trials/search.py:803-980 (helper)Domain-specific implementation _trial_searcher. Constructs TrialQuery from parameters and invokes search_trials. This is the core logic wrapped by the public MCP tool.async def _trial_searcher( call_benefit: Annotated[ str, "Define and summarize why this function is being called and the intended benefit", ], conditions: Annotated[ list[str] | str | None, "Condition terms (e.g., 'breast cancer') - list or comma-separated string", ] = None, terms: Annotated[ list[str] | str | None, "General search terms - list or comma-separated string", ] = None, interventions: Annotated[ list[str] | str | None, "Intervention names (e.g., 'pembrolizumab') - list or comma-separated string", ] = None, recruiting_status: Annotated[ RecruitingStatus | str | None, "Study recruitment status (OPEN, CLOSED, ANY)", ] = None, study_type: Annotated[StudyType | str | None, "Type of study"] = None, nct_ids: Annotated[ list[str] | str | None, "Clinical trial NCT IDs - list or comma-separated string", ] = None, lat: Annotated[ float | None, "Latitude for location search. AI agents should geocode city/location names (e.g., 'Cleveland' → 41.4993, -81.6944) before using this parameter.", ] = None, long: Annotated[ float | None, "Longitude for location search. AI agents should geocode city/location names (e.g., 'Cleveland' → 41.4993, -81.6944) before using this parameter.", ] = None, distance: Annotated[ float | None, "Distance from lat/long in miles (default: 50 miles if lat/long provided but distance not specified)", ] = None, min_date: Annotated[ str | None, "Minimum date for filtering (YYYY-MM-DD)" ] = None, max_date: Annotated[ str | None, "Maximum date for filtering (YYYY-MM-DD)" ] = None, date_field: Annotated[ DateField | str | None, "Date field to filter on" ] = None, phase: Annotated[TrialPhase | str | None, "Trial phase filter"] = None, age_group: Annotated[AgeGroup | str | None, "Age group filter"] = None, primary_purpose: Annotated[ PrimaryPurpose | str | None, "Primary purpose of the trial" ] = None, intervention_type: Annotated[ InterventionType | str | None, "Type of intervention" ] = None, sponsor_type: Annotated[ SponsorType | str | None, "Type of sponsor" ] = None, study_design: Annotated[StudyDesign | str | None, "Study design"] = None, sort: Annotated[SortOrder | str | None, "Sort order for results"] = None, next_page_hash: Annotated[ str | None, "Token to retrieve the next page of results" ] = None, prior_therapies: Annotated[ list[str] | str | None, "Prior therapies to search for in eligibility criteria - list or comma-separated string", ] = None, progression_on: Annotated[ list[str] | str | None, "Therapies the patient has progressed on - list or comma-separated string", ] = None, required_mutations: Annotated[ list[str] | str | None, "Required mutations in eligibility criteria - list or comma-separated string", ] = None, excluded_mutations: Annotated[ list[str] | str | None, "Excluded mutations in eligibility criteria - list or comma-separated string", ] = None, biomarker_expression: Annotated[ dict[str, str] | None, "Biomarker expression requirements (e.g., {'PD-L1': '≥50%'})", ] = None, line_of_therapy: Annotated[ LineOfTherapy | str | None, "Line of therapy filter (1L, 2L, 3L+)", ] = None, allow_brain_mets: Annotated[ bool | None, "Whether to allow trials that accept brain metastases", ] = None, return_fields: Annotated[ list[str] | str | None, "Specific fields to return in the response - list or comma-separated string", ] = None, page_size: Annotated[ int | None, "Number of results per page (1-1000)", ] = None, expand_synonyms: Annotated[ bool, "Expand condition searches with disease synonyms from MyDisease.info", ] = True, ) -> str: """ Searches for clinical trials based on specified criteria. Parameters: - call_benefit: Define and summarize why this function is being called and the intended benefit - conditions: Condition terms (e.g., "breast cancer") - list or comma-separated string - terms: General search terms - list or comma-separated string - interventions: Intervention names (e.g., "pembrolizumab") - list or comma-separated string - recruiting_status: Study recruitment status (OPEN, CLOSED, ANY) - study_type: Type of study - nct_ids: Clinical trial NCT IDs - list or comma-separated string - lat: Latitude for location search - long: Longitude for location search - distance: Distance from lat/long in miles - min_date: Minimum date for filtering (YYYY-MM-DD) - max_date: Maximum date for filtering (YYYY-MM-DD) - date_field: Date field to filter on - phase: Trial phase filter - age_group: Age group filter - primary_purpose: Primary purpose of the trial - intervention_type: Type of intervention - sponsor_type: Type of sponsor - study_design: Study design - sort: Sort order for results - next_page_hash: Token to retrieve the next page of results - prior_therapies: Prior therapies to search for in eligibility criteria - list or comma-separated string - progression_on: Therapies the patient has progressed on - list or comma-separated string - required_mutations: Required mutations in eligibility criteria - list or comma-separated string - excluded_mutations: Excluded mutations in eligibility criteria - list or comma-separated string - biomarker_expression: Biomarker expression requirements (e.g., {'PD-L1': '≥50%'}) - line_of_therapy: Line of therapy filter (1L, 2L, 3L+) - allow_brain_mets: Whether to allow trials that accept brain metastases - return_fields: Specific fields to return in the response - list or comma-separated string - page_size: Number of results per page (1-1000) - expand_synonyms: Expand condition searches with disease synonyms from MyDisease.info Returns: Markdown formatted list of clinical trials """ # Convert individual parameters to a TrialQuery object query = TrialQuery( conditions=ensure_list(conditions, split_strings=True), terms=ensure_list(terms, split_strings=True), interventions=ensure_list(interventions, split_strings=True), recruiting_status=recruiting_status, study_type=study_type, nct_ids=ensure_list(nct_ids, split_strings=True), lat=lat, long=long, distance=distance, min_date=min_date, max_date=max_date, date_field=date_field, phase=phase, age_group=age_group, primary_purpose=primary_purpose, intervention_type=intervention_type, sponsor_type=sponsor_type, study_design=study_design, sort=sort, next_page_hash=next_page_hash, prior_therapies=ensure_list(prior_therapies, split_strings=True), progression_on=ensure_list(progression_on, split_strings=True), required_mutations=ensure_list(required_mutations, split_strings=True), excluded_mutations=ensure_list(excluded_mutations, split_strings=True), biomarker_expression=biomarker_expression, line_of_therapy=line_of_therapy, allow_brain_mets=allow_brain_mets, return_fields=ensure_list(return_fields, split_strings=True), page_size=page_size, expand_synonyms=expand_synonyms, ) return await search_trials(query, output_json=False)
- src/biomcp/trials/search.py:256-382 (schema)Pydantic model TrialQuery defining the structured input schema and validation for trial searches, used internally by _trial_searcher.class TrialQuery(BaseModel): """Parameters for querying clinical trial data from ClinicalTrials.gov.""" conditions: list[str] | None = Field( default=None, description="List of condition terms.", ) terms: list[str] | None = Field( default=None, description="General search terms that don't fit specific categories.", ) interventions: list[str] | None = Field( default=None, description="Intervention names.", ) recruiting_status: RecruitingStatus | None = Field( default=None, description="Study recruitment status. Use 'OPEN' for actively recruiting trials, 'CLOSED' for completed/terminated trials, or 'ANY' for all trials. Common aliases like 'recruiting', 'active', 'enrolling' map to 'OPEN'.", ) study_type: StudyType | None = Field( default=None, description="Type of study.", ) nct_ids: list[str] | None = Field( default=None, description="Clinical trial NCT IDs", ) lat: float | None = Field( default=None, description="Latitude for location search. AI agents should geocode city/location names (e.g., 'Cleveland' → 41.4993, -81.6944) before using this parameter.", ) long: float | None = Field( default=None, description="Longitude for location search. AI agents should geocode city/location names (e.g., 'Cleveland' → 41.4993, -81.6944) before using this parameter.", ) distance: int | None = Field( default=None, description="Distance from lat/long in miles (default: 50 miles if lat/long provided but distance not specified)", ) min_date: str | None = Field( default=None, description="Minimum date for filtering", ) max_date: str | None = Field( default=None, description="Maximum date for filtering", ) date_field: DateField | None = Field( default=None, description="Date field to filter on", ) phase: TrialPhase | None = Field( default=None, description="Trial phase filter", ) age_group: AgeGroup | None = Field( default=None, description="Age group filter", ) primary_purpose: PrimaryPurpose | None = Field( default=None, description="Primary purpose of the trial", ) intervention_type: InterventionType | None = Field( default=None, description="Type of intervention", ) sponsor_type: SponsorType | None = Field( default=None, description="Type of sponsor", ) study_design: StudyDesign | None = Field( default=None, description="Study design", ) sort: SortOrder | None = Field( default=None, description="Sort order for results", ) next_page_hash: str | None = Field( default=None, description="Token to retrieve the next page of results", ) # New eligibility-focused fields prior_therapies: list[str] | None = Field( default=None, description="Prior therapies to search for in eligibility criteria", ) progression_on: list[str] | None = Field( default=None, description="Therapies the patient has progressed on", ) required_mutations: list[str] | None = Field( default=None, description="Required mutations in eligibility criteria", ) excluded_mutations: list[str] | None = Field( default=None, description="Excluded mutations in eligibility criteria", ) biomarker_expression: dict[str, str] | None = Field( default=None, description="Biomarker expression requirements (e.g., {'PD-L1': '≥50%'})", ) line_of_therapy: LineOfTherapy | None = Field( default=None, description="Line of therapy filter", ) allow_brain_mets: bool | None = Field( default=None, description="Whether to allow trials that accept brain metastases", ) return_fields: list[str] | None = Field( default=None, description="Specific fields to return in the response", ) page_size: int | None = Field( default=None, description="Number of results per page", ge=1, le=1000, ) expand_synonyms: bool = Field( default=True, description="Expand condition searches with disease synonyms from MyDisease.info", )
- src/biomcp/trials/search.py:744-801 (helper)Main API integration function search_trials that converts query to API parameters and calls ClinicalTrials.gov API.async def search_trials( query: TrialQuery, output_json: bool = False, ) -> str: """Search ClinicalTrials.gov for clinical trials.""" params = await convert_query(query) # Log filter mode if NCT IDs are present if query.nct_ids: # Check if we're using intersection or id-only mode # Only count explicit user-set filters, not defaults has_other_filters = any([ query.conditions, query.terms, query.interventions, query.lat is not None and query.long is not None, query.date_field and (query.min_date or query.max_date), query.primary_purpose, query.study_type, query.intervention_type, query.sponsor_type, query.study_design, query.phase, query.age_group and query.age_group != AgeGroup.ALL, query.recruiting_status not in (None, RecruitingStatus.OPEN), query.prior_therapies, query.progression_on, query.required_mutations, query.excluded_mutations, query.biomarker_expression, query.line_of_therapy, query.allow_brain_mets is not None, ]) if has_other_filters: logger.debug( "Filter mode: intersection (NCT IDs AND other filters)" ) else: logger.debug("Filter mode: id-only (NCT IDs only)") response, error = await http_client.request_api( url=CLINICAL_TRIALS_BASE_URL, request=params, method="GET", tls_version=TLSVersion.TLSv1_2, domain="trial", ) data = response if error: data = {"error": f"Error {error.code}: {error.message}"} if data and not output_json: return render.to_markdown(data) else: return json.dumps(data, indent=2)
- src/biomcp/query_router.py:268-271 (registration)Unified query router dispatches to trial_searcher by name, creating TrialQuery from mapped params and calling search_trials in parallel execution.elif tool_name == "trial_searcher": query = TrialQuery(**params) tasks.append(search_trials(query, output_json=output_json)) task_names.append("trials")