Skip to main content
Glama

search

Search biomedical literature, clinical trials, genetic variants, genes, drugs, and diseases using a unified query language or domain-specific filters. Access data from PubMed/PubTator3, ClinicalTrials.gov, MyVariant.info, and BioThings suite for precise research insights.

Instructions

Search biomedical literature, clinical trials, genetic variants, genes, drugs, and diseases.

⚠️ IMPORTANT: Have you used the 'think' tool first? If not, STOP and use it NOW! The 'think' tool is REQUIRED for proper research planning and should be your FIRST step. This tool provides access to biomedical data from PubMed/PubTator3, ClinicalTrials.gov, MyVariant.info, and the BioThings suite (MyGene.info, MyChem.info, MyDisease.info). It supports two search modes: ## 1. UNIFIED QUERY LANGUAGE Use the 'query' parameter with field-based syntax for precise cross-domain searches. Syntax: - Basic: "gene:BRAF" - AND logic: "gene:BRAF AND disease:melanoma" - OR logic: "gene:PTEN AND (R173 OR Arg173 OR 'position 173')" - Domain-specific: "trials.condition:melanoma AND trials.phase:3" Common fields: - Cross-domain: gene, disease, variant, chemical/drug - Articles: pmid, title, abstract, journal, author - Trials: trials.condition, trials.intervention, trials.phase, trials.status - Variants: variants.hgvs, variants.rsid, variants.significance Example: ``` await search( query="gene:BRAF AND disease:melanoma AND trials.phase:3", max_results_per_domain=20 ) ``` ## 2. DOMAIN-SPECIFIC SEARCH Use the 'domain' parameter with specific filters for targeted searches. Domains: - "article": Search PubMed/PubTator3 for research articles and preprints ABOUT genes, variants, diseases, or chemicals - "trial": Search ClinicalTrials.gov for clinical studies - "variant": Search MyVariant.info for genetic variant DATABASE RECORDS (population frequency, clinical significance, etc.) - NOT for articles about variants! - "gene": Search MyGene.info for gene information (symbol, name, function, aliases) - "drug": Search MyChem.info for drug/chemical information (names, formulas, indications) - "disease": Search MyDisease.info for disease information (names, definitions, synonyms) - "nci_organization": Search NCI database for cancer centers, hospitals, and research sponsors (requires API key) - "nci_intervention": Search NCI database for drugs, devices, procedures used in cancer trials (requires API key) - "nci_biomarker": Search NCI database for biomarkers used in trial eligibility criteria (requires API key) - "nci_disease": Search NCI controlled vocabulary for cancer conditions and terms (requires API key) Example: ``` await search( domain="article", genes=["BRAF", "NRAS"], diseases=["melanoma"], page_size=50 ) ``` ## DOMAIN SELECTION EXAMPLES: - To find ARTICLES about BRAF V600E mutation: domain="article", genes=["BRAF"], variants=["V600E"] - To find VARIANT DATA for BRAF mutations: domain="variant", gene="BRAF" - To find articles about ERBB2 p.D277Y: domain="article", genes=["ERBB2"], variants=["p.D277Y"] - Common mistake: Using domain="variant" when you want articles about a variant ## IMPORTANT NOTES: - For complex research questions, use the separate 'think' tool for systematic analysis - The tool returns results in OpenAI MCP format: {"results": [{"id", "title", "text", "url"}, ...]} - Search results do NOT include metadata (per OpenAI MCP specification) - Use the fetch tool to get detailed metadata for specific records - Use get_schema=True to explore available search fields - Use explain_query=True to understand query parsing (unified mode) - Domain-specific searches use AND logic for multiple values - For OR logic, use the unified query language - NEW: Article search keywords support OR with pipe separator: "R173|Arg173|p.R173" - Remember: domain="article" finds LITERATURE, domain="variant" finds DATABASE RECORDS ## RETURN FORMAT: All search modes return results in this format: ```json { "results": [ { "id": "unique_identifier", "title": "Human-readable title", "text": "Summary or snippet of content", "url": "Link to full resource" } ] } ```

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
api_keyNoNCI API key for searching NCI domains (nci_organization, nci_intervention, nci_biomarker, nci_disease). Required for NCI searches. Get a free key at: https://clinicaltrialsapi.cancer.gov/
call_benefitNoBrief explanation of why this search is being performed and expected benefit. Helps improve search accuracy and provides context for analytics. Highly recommended for better results.
chemicalsNo
conditionsNo
diseasesNo
distanceNo
domainNoDomain to search: 'article' for papers/literature ABOUT genes/variants/diseases, 'trial' for clinical studies, 'variant' for genetic variant DATABASE RECORDS, 'gene' for gene information from MyGene.info, 'drug' for drug/chemical information from MyChem.info, 'disease' for disease information from MyDisease.info, 'nci_organization' for NCI cancer centers/sponsors, 'nci_intervention' for NCI drugs/devices/procedures, 'nci_biomarker' for NCI trial eligibility biomarkers, 'nci_disease' for NCI cancer vocabulary, 'fda_adverse' for FDA adverse event reports, 'fda_label' for FDA drug labels, 'fda_device' for FDA device events, 'fda_approval' for FDA drug approvals, 'fda_recall' for FDA drug recalls, 'fda_shortage' for FDA drug shortages
explain_queryNo
genesNo
get_schemaNo
interventionsNo
keywordsNo
latNo
longNo
max_results_per_domainNo
pageNo
page_sizeNo
phaseNo
queryYes
recruiting_statusNo
significanceNo
variantsNo

Implementation Reference

  • Primary handler function for the 'search' MCP tool. Handles unified queries and domain-specific searches across biomedical domains like articles, trials, variants, genes, drugs, diseases, and FDA/NCI data sources. Formats results per OpenAI MCP spec.
    @mcp_app.tool() @track_performance("biomcp.search") async def search( # noqa: C901 query: Annotated[ str, "Unified search query (e.g., 'gene:BRAF AND trials.condition:melanoma'). If provided, other parameters are ignored.", ], call_benefit: Annotated[ str | None, Field( description="Brief explanation of why this search is being performed and expected benefit. Helps improve search accuracy and provides context for analytics. Highly recommended for better results." ), ] = None, domain: Annotated[ Literal[ "article", "trial", "variant", "gene", "drug", "disease", "nci_organization", "nci_intervention", "nci_biomarker", "nci_disease", "fda_adverse", "fda_label", "fda_device", "fda_approval", "fda_recall", "fda_shortage", ] | None, Field( description="Domain to search: 'article' for papers/literature ABOUT genes/variants/diseases, 'trial' for clinical studies, 'variant' for genetic variant DATABASE RECORDS, 'gene' for gene information from MyGene.info, 'drug' for drug/chemical information from MyChem.info, 'disease' for disease information from MyDisease.info, 'nci_organization' for NCI cancer centers/sponsors, 'nci_intervention' for NCI drugs/devices/procedures, 'nci_biomarker' for NCI trial eligibility biomarkers, 'nci_disease' for NCI cancer vocabulary, 'fda_adverse' for FDA adverse event reports, 'fda_label' for FDA drug labels, 'fda_device' for FDA device events, 'fda_approval' for FDA drug approvals, 'fda_recall' for FDA drug recalls, 'fda_shortage' for FDA drug shortages" ), ] = None, genes: Annotated[list[str] | str | None, "Gene symbols"] = None, diseases: Annotated[list[str] | str | None, "Disease terms"] = None, variants: Annotated[list[str] | str | None, "Variant strings"] = None, chemicals: Annotated[list[str] | str | None, "Drug/chemical terms"] = None, keywords: Annotated[list[str] | str | None, "Free-text keywords"] = None, conditions: Annotated[list[str] | str | None, "Trial conditions"] = None, interventions: Annotated[ list[str] | str | None, "Trial interventions" ] = None, recruiting_status: Annotated[ str | None, "Trial status filter (OPEN, CLOSED, or ANY)" ] = None, phase: Annotated[str | None, "Trial phase filter"] = None, significance: Annotated[ str | None, "Variant clinical significance" ] = None, lat: Annotated[ float | None, "Latitude for trial location search. AI agents should geocode city names (e.g., 'Cleveland' → 41.4993) before using.", ] = None, long: Annotated[ float | None, "Longitude for trial location search. AI agents should geocode city names (e.g., 'Cleveland' → -81.6944) before using.", ] = None, distance: Annotated[ int | None, "Distance in miles from lat/long for trial search (default: 50 miles if lat/long provided)", ] = None, page: Annotated[int, "Page number (minimum: 1)"] = DEFAULT_PAGE_NUMBER, page_size: Annotated[int, "Results per page (1-100)"] = DEFAULT_PAGE_SIZE, max_results_per_domain: Annotated[ int | None, "Max results per domain (unified search only)" ] = None, explain_query: Annotated[ bool, "Return query explanation (unified search only)" ] = False, get_schema: Annotated[ bool, "Return searchable fields schema instead of results" ] = False, api_key: Annotated[ str | None, Field( description="NCI API key for searching NCI domains (nci_organization, nci_intervention, nci_biomarker, nci_disease). Required for NCI searches. Get a free key at: https://clinicaltrialsapi.cancer.gov/" ), ] = None, ) -> dict: """Search biomedical literature, clinical trials, genetic variants, genes, drugs, and diseases. ⚠️ IMPORTANT: Have you used the 'think' tool first? If not, STOP and use it NOW! The 'think' tool is REQUIRED for proper research planning and should be your FIRST step. This tool provides access to biomedical data from PubMed/PubTator3, ClinicalTrials.gov, MyVariant.info, and the BioThings suite (MyGene.info, MyChem.info, MyDisease.info). It supports two search modes: ## 1. UNIFIED QUERY LANGUAGE Use the 'query' parameter with field-based syntax for precise cross-domain searches. Syntax: - Basic: "gene:BRAF" - AND logic: "gene:BRAF AND disease:melanoma" - OR logic: "gene:PTEN AND (R173 OR Arg173 OR 'position 173')" - Domain-specific: "trials.condition:melanoma AND trials.phase:3" Common fields: - Cross-domain: gene, disease, variant, chemical/drug - Articles: pmid, title, abstract, journal, author - Trials: trials.condition, trials.intervention, trials.phase, trials.status - Variants: variants.hgvs, variants.rsid, variants.significance Example: ``` await search( query="gene:BRAF AND disease:melanoma AND trials.phase:3", max_results_per_domain=20 ) ``` ## 2. DOMAIN-SPECIFIC SEARCH Use the 'domain' parameter with specific filters for targeted searches. Domains: - "article": Search PubMed/PubTator3 for research articles and preprints ABOUT genes, variants, diseases, or chemicals - "trial": Search ClinicalTrials.gov for clinical studies - "variant": Search MyVariant.info for genetic variant DATABASE RECORDS (population frequency, clinical significance, etc.) - NOT for articles about variants! - "gene": Search MyGene.info for gene information (symbol, name, function, aliases) - "drug": Search MyChem.info for drug/chemical information (names, formulas, indications) - "disease": Search MyDisease.info for disease information (names, definitions, synonyms) - "nci_organization": Search NCI database for cancer centers, hospitals, and research sponsors (requires API key) - "nci_intervention": Search NCI database for drugs, devices, procedures used in cancer trials (requires API key) - "nci_biomarker": Search NCI database for biomarkers used in trial eligibility criteria (requires API key) - "nci_disease": Search NCI controlled vocabulary for cancer conditions and terms (requires API key) Example: ``` await search( domain="article", genes=["BRAF", "NRAS"], diseases=["melanoma"], page_size=50 ) ``` ## DOMAIN SELECTION EXAMPLES: - To find ARTICLES about BRAF V600E mutation: domain="article", genes=["BRAF"], variants=["V600E"] - To find VARIANT DATA for BRAF mutations: domain="variant", gene="BRAF" - To find articles about ERBB2 p.D277Y: domain="article", genes=["ERBB2"], variants=["p.D277Y"] - Common mistake: Using domain="variant" when you want articles about a variant ## IMPORTANT NOTES: - For complex research questions, use the separate 'think' tool for systematic analysis - The tool returns results in OpenAI MCP format: {"results": [{"id", "title", "text", "url"}, ...]} - Search results do NOT include metadata (per OpenAI MCP specification) - Use the fetch tool to get detailed metadata for specific records - Use get_schema=True to explore available search fields - Use explain_query=True to understand query parsing (unified mode) - Domain-specific searches use AND logic for multiple values - For OR logic, use the unified query language - NEW: Article search keywords support OR with pipe separator: "R173|Arg173|p.R173" - Remember: domain="article" finds LITERATURE, domain="variant" finds DATABASE RECORDS ## RETURN FORMAT: All search modes return results in this format: ```json { "results": [ { "id": "unique_identifier", "title": "Human-readable title", "text": "Summary or snippet of content", "url": "Link to full resource" } ] } ``` """ logger.info(f"Search called with domain={domain}, query={query}") # Return schema if requested if get_schema: parser = QueryParser() return parser.get_schema() # Determine search mode if query and query.strip(): # Check if this is a unified query (contains field syntax like "gene:" or "AND") is_unified_query = any( marker in query for marker in [":", " AND ", " OR "] ) # Check if this is an NCI domain nci_domains = [ "nci_biomarker", "nci_organization", "nci_intervention", "nci_disease", ] is_nci_domain = domain in nci_domains if domain else False if not domain or (domain and is_unified_query and not is_nci_domain): # Use unified query mode if: # 1. No domain specified, OR # 2. Domain specified but query has field syntax AND it's not an NCI domain logger.info(f"Using unified query mode: {query}") return await _unified_search( query=query, max_results_per_domain=max_results_per_domain or MAX_RESULTS_PER_DOMAIN_DEFAULT, domains=None, explain_query=explain_query, ) elif domain: # Domain-specific search with query as keyword logger.info( f"Domain-specific search with query as keyword: domain={domain}, query={query}" ) # Convert query to keywords parameter for domain-specific search keywords = [query] # Legacy domain-based search if not domain: raise InvalidParameterError( "query or domain", None, ERROR_DOMAIN_REQUIRED ) # Validate pagination parameters try: page, page_size = ParameterParser.validate_page_params(page, page_size) except InvalidParameterError as e: logger.error(f"Invalid pagination parameters: {e}") raise # Parse parameters using ParameterParser genes = ParameterParser.parse_list_param(genes, "genes") diseases = ParameterParser.parse_list_param(diseases, "diseases") variants = ParameterParser.parse_list_param(variants, "variants") chemicals = ParameterParser.parse_list_param(chemicals, "chemicals") keywords = ParameterParser.parse_list_param(keywords, "keywords") conditions = ParameterParser.parse_list_param(conditions, "conditions") interventions = ParameterParser.parse_list_param( interventions, "interventions" ) logger.debug( f"Parsed parameters for domain {domain}: " f"genes={genes}, diseases={diseases}, variants={variants}" ) if domain == "article": from .router_handlers import handle_article_search items, total = await handle_article_search( genes=genes, diseases=diseases, variants=variants, chemicals=chemicals, keywords=keywords, page=page, page_size=page_size, ) return format_results( items, domain="article", page=page, page_size=page_size, total=total, ) elif domain == "trial": logger.info("Executing trial search") # Build the trial search parameters search_params: dict[str, Any] = {} if conditions: search_params["conditions"] = conditions if interventions: search_params["interventions"] = interventions if recruiting_status: search_params["recruiting_status"] = recruiting_status if phase: try: search_params["phase"] = ParameterParser.normalize_phase(phase) except InvalidParameterError: raise if keywords: search_params["keywords"] = keywords if lat is not None: search_params["lat"] = lat if long is not None: search_params["long"] = long if distance is not None: search_params["distance"] = distance try: from biomcp.trials.search import TrialQuery, search_trials # Convert search_params to TrialQuery trial_query = TrialQuery(**search_params, page_size=page_size) result_str = await search_trials(trial_query, output_json=True) except Exception as e: logger.error(f"Trial search failed: {e}") raise SearchExecutionError("trial", e) from e # Parse the JSON results try: results = json.loads(result_str) except (json.JSONDecodeError, TypeError) as e: logger.error(f"Failed to parse trial results: {e}") raise ResultParsingError("trial", e) from e # Handle different response formats from the trials API # The API can return either a dict with 'studies' key or a direct list if isinstance(results, dict): # ClinicalTrials.gov API v2 format with studies array if "studies" in results: items = results["studies"] total = len(items) # API doesn't provide total count # Legacy format or error elif "error" in results: logger.warning( f"Trial API returned error: {results.get('error')}" ) return format_results( [], domain="trial", page=page, page_size=page_size, total=0 ) else: # Assume the dict itself is a single result items = [results] total = 1 elif isinstance(results, list): # Direct list of results items = results total = len(items) else: items = [] total = 0 logger.info(f"Trial search returned {total} total results") return format_results( items, domain="trial", page=page, page_size=page_size, total=total ) elif domain == "variant": logger.info("Executing variant search") # Build the variant search parameters # Note: variant searcher expects single gene, not list gene = genes[0] if genes else None # Use keywords to search for significance if provided keyword_list = keywords or [] if significance: keyword_list.append(significance) try: from biomcp.variants.search import VariantQuery, search_variants variant_query = VariantQuery( gene=gene, significance=significance, size=page_size, offset=(page - 1) * page_size, ) result_str = await search_variants(variant_query, output_json=True) except Exception as e: logger.error(f"Variant search failed: {e}") raise SearchExecutionError("variant", e) from e # Parse the JSON results try: all_results = json.loads(result_str) except (json.JSONDecodeError, TypeError) as e: logger.error(f"Failed to parse variant results: {e}") raise ResultParsingError("variant", e) from e # For variants, the results are already paginated by the API # We need to estimate total based on whether we got a full page items = all_results if isinstance(all_results, list) else [] # Rough estimate: if we got a full page, there might be more total = len(items) + ( ESTIMATED_ADDITIONAL_RESULTS if len(items) == page_size else 0 ) logger.info(f"Variant search returned {len(items)} results") return format_results( items, domain="variant", page=page, page_size=page_size, total=total, ) elif domain == "gene": logger.info("Executing gene search") # Build the gene search query query_str = keywords[0] if keywords else genes[0] if genes else "" if not query_str: raise InvalidParameterError( "keywords or genes", None, "a gene symbol or search term" ) try: client = BioThingsClient() # For search, query by symbol/name results = await client._query_gene(query_str) if not results: items = [] total = 0 else: # Fetch full details for each result (limited by page_size) items = [] for result in results[:page_size]: gene_id = result.get("_id") if gene_id: full_gene = await client._get_gene_by_id(gene_id) if full_gene: items.append(full_gene.model_dump()) total = len(results) except Exception as e: logger.error(f"Gene search failed: {e}") raise SearchExecutionError("gene", e) from e logger.info(f"Gene search returned {len(items)} results") return format_results( items, domain="gene", page=page, page_size=page_size, total=total, ) elif domain == "drug": logger.info("Executing drug search") # Build the drug search query query_str = ( keywords[0] if keywords else chemicals[0] if chemicals else "" ) if not query_str: raise InvalidParameterError( "keywords or chemicals", None, "a drug name or search term" ) try: client = BioThingsClient() # For search, query by name results = await client._query_drug(query_str) if not results: items = [] total = 0 else: # Fetch full details for each result (limited by page_size) items = [] for result in results[:page_size]: drug_id = result.get("_id") if drug_id: full_drug = await client._get_drug_by_id(drug_id) if full_drug: items.append(full_drug.model_dump(by_alias=True)) total = len(results) except Exception as e: logger.error(f"Drug search failed: {e}") raise SearchExecutionError("drug", e) from e logger.info(f"Drug search returned {len(items)} results") return format_results( items, domain="drug", page=page, page_size=page_size, total=total, ) elif domain == "disease": logger.info("Executing disease search") # Build the disease search query query_str = ( keywords[0] if keywords else diseases[0] if diseases else "" ) if not query_str: raise InvalidParameterError( "keywords or diseases", None, "a disease name or search term" ) try: client = BioThingsClient() # For search, query by name results = await client._query_disease(query_str) if not results: items = [] total = 0 else: # Fetch full details for each result (limited by page_size) items = [] for result in results[:page_size]: disease_id = result.get("_id") if disease_id: full_disease = await client._get_disease_by_id( disease_id ) if full_disease: items.append( full_disease.model_dump(by_alias=True) ) total = len(results) except Exception as e: logger.error(f"Disease search failed: {e}") raise SearchExecutionError("disease", e) from e logger.info(f"Disease search returned {len(items)} results") return format_results( items, domain="disease", page=page, page_size=page_size, total=total, ) elif domain == "nci_organization": from .router_handlers import handle_nci_organization_search # Extract NCI-specific parameters organization_type = keywords[0] if keywords else None city = None state = None name = keywords[0] if keywords else None # Try to parse location from keywords if keywords and len(keywords) >= 2: # Assume last two keywords might be city, state city = keywords[-2] state = keywords[-1] if len(state) == 2 and state.isupper(): # Likely a state code name = " ".join(keywords[:-2]) if len(keywords) > 2 else None else: # Not a state code, use all as name city = None state = None name = " ".join(keywords) items, total = await handle_nci_organization_search( name=name, organization_type=organization_type, city=city, state=state, api_key=api_key, page=page, page_size=page_size, ) return format_results( items, domain="nci_organization", page=page, page_size=page_size, total=total, ) elif domain == "nci_intervention": from .router_handlers import handle_nci_intervention_search # Extract parameters name = keywords[0] if keywords else None intervention_type = None # Could be parsed from additional params items, total = await handle_nci_intervention_search( name=name, intervention_type=intervention_type, synonyms=True, api_key=api_key, page=page, page_size=page_size, ) return format_results( items, domain="nci_intervention", page=page, page_size=page_size, total=total, ) elif domain == "nci_biomarker": from .router_handlers import handle_nci_biomarker_search # Extract parameters name = keywords[0] if keywords else None gene = genes[0] if genes else None items, total = await handle_nci_biomarker_search( name=name, gene=gene, biomarker_type=None, assay_type=None, api_key=api_key, page=page, page_size=page_size, ) return format_results( items, domain="nci_biomarker", page=page, page_size=page_size, total=total, ) elif domain == "nci_disease": from .router_handlers import handle_nci_disease_search # Extract parameters name = diseases[0] if diseases else keywords[0] if keywords else None items, total = await handle_nci_disease_search( name=name, include_synonyms=True, category=None, api_key=api_key, page=page, page_size=page_size, ) return format_results( items, domain="nci_disease", page=page, page_size=page_size, total=total, ) # OpenFDA domains elif domain == "fda_adverse": from biomcp.openfda import search_adverse_events drug_name = ( chemicals[0] if chemicals else keywords[0] if keywords else None ) skip = (page - 1) * page_size fda_result = await search_adverse_events( drug=drug_name, limit=page_size, skip=skip, api_key=api_key, ) # Parse the markdown result to extract items # For simplicity, return the result as a single item return {"results": [{"content": fda_result}]} elif domain == "fda_label": from biomcp.openfda import search_drug_labels drug_name = ( chemicals[0] if chemicals else keywords[0] if keywords else None ) skip = (page - 1) * page_size fda_result = await search_drug_labels( name=drug_name, limit=page_size, skip=skip, api_key=api_key, ) return {"results": [{"content": fda_result}]} elif domain == "fda_device": from biomcp.openfda import search_device_events device_name = keywords[0] if keywords else None skip = (page - 1) * page_size fda_result = await search_device_events( device=device_name, limit=page_size, skip=skip, api_key=api_key, ) return {"results": [{"content": fda_result}]} elif domain == "fda_approval": from biomcp.openfda import search_drug_approvals drug_name = ( chemicals[0] if chemicals else keywords[0] if keywords else None ) skip = (page - 1) * page_size fda_result = await search_drug_approvals( drug=drug_name, limit=page_size, skip=skip, api_key=api_key, ) return {"results": [{"content": fda_result}]} elif domain == "fda_recall": from biomcp.openfda import search_drug_recalls drug_name = ( chemicals[0] if chemicals else keywords[0] if keywords else None ) skip = (page - 1) * page_size fda_result = await search_drug_recalls( drug=drug_name, limit=page_size, skip=skip, api_key=api_key, ) return {"results": [{"content": fda_result}]} elif domain == "fda_shortage": from biomcp.openfda import search_drug_shortages drug_name = ( chemicals[0] if chemicals else keywords[0] if keywords else None ) skip = (page - 1) * page_size fda_result = await search_drug_shortages( drug=drug_name, limit=page_size, skip=skip, api_key=api_key, ) return {"results": [{"content": fda_result}]} else: raise InvalidDomainError(domain, VALID_DOMAINS)
  • Registers the search function as an MCP tool using the FastMCP decorator.
    @mcp_app.tool()
  • Input schema defined via Pydantic Annotated types and Field descriptions. Includes required 'query', optional 'domain' with enum values (article, trial, variant, etc.), call_benefit, and domain-specific filters.
    query: Annotated[ str, "Unified search query (e.g., 'gene:BRAF AND trials.condition:melanoma'). If provided, other parameters are ignored.", ], call_benefit: Annotated[ str | None, Field( description="Brief explanation of why this search is being performed and expected benefit. Helps improve search accuracy and provides context for analytics. Highly recommended for better results." ), ] = None, domain: Annotated[ Literal[ "article", "trial", "variant", "gene", "drug", "disease", "nci_organization", "nci_intervention", "nci_biomarker", "nci_disease", "fda_adverse", "fda_label", "fda_device", "fda_approval", "fda_recall", "fda_shortage", ] | None, Field( description="Domain to search: 'article' for papers/literature ABOUT genes/variants/diseases, 'trial' for clinical studies, 'variant' for genetic variant DATABASE RECORDS, 'gene' for gene information from MyGene.info, 'drug' for drug/chemical information from MyChem.info, 'disease' for disease information from MyDisease.info, 'nci_organization' for NCI cancer centers/sponsors, 'nci_intervention' for NCI drugs/devices/procedures, 'nci_biomarker' for NCI trial eligibility biomarkers, 'nci_disease' for NCI cancer vocabulary, 'fda_adverse' for FDA adverse event reports, 'fda_label' for FDA drug labels, 'fda_device' for FDA device events, 'fda_approval' for FDA drug approvals, 'fda_recall' for FDA drug recalls, 'fda_shortage' for FDA drug shortages" ), ] = None, genes: Annotated[list[str] | str | None, "Gene symbols"] = None, diseases: Annotated[list[str] | str | None, "Disease terms"] = None, variants: Annotated[list[str] | str | None, "Variant strings"] = None, chemicals: Annotated[list[str] | str | None, "Drug/chemical terms"] = None, keywords: Annotated[list[str] | str | None, "Free-text keywords"] = None, conditions: Annotated[list[str] | str | None, "Trial conditions"] = None, interventions: Annotated[ list[str] | str | None, "Trial interventions" ] = None, recruiting_status: Annotated[ str | None, "Trial status filter (OPEN, CLOSED, or ANY)" ] = None, phase: Annotated[str | None, "Trial phase filter"] = None, significance: Annotated[ str | None, "Variant clinical significance" ] = None, lat: Annotated[ float | None, "Latitude for trial location search. AI agents should geocode city names (e.g., 'Cleveland' → 41.4993) before using.", ] = None, long: Annotated[ float | None, "Longitude for trial location search. AI agents should geocode city names (e.g., 'Cleveland' → -81.6944) before using.", ] = None, distance: Annotated[ int | None, "Distance in miles from lat/long for trial search (default: 50 miles if lat/long provided)", ] = None, page: Annotated[int, "Page number (minimum: 1)"] = DEFAULT_PAGE_NUMBER, page_size: Annotated[int, "Results per page (1-100)"] = DEFAULT_PAGE_SIZE, max_results_per_domain: Annotated[ int | None, "Max results per domain (unified search only)" ] = None, explain_query: Annotated[ bool, "Return query explanation (unified search only)" ] = False, get_schema: Annotated[ bool, "Return searchable fields schema instead of results" ] = False, api_key: Annotated[ str | None, Field( description="NCI API key for searching NCI domains (nci_organization, nci_intervention, nci_biomarker, nci_disease). Required for NCI searches. Get a free key at: https://clinicaltrialsapi.cancer.gov/" ), ] = None, ) -> dict:
  • Helper function to format domain-specific results into standardized MCP search result format ({results: [{id, title, text, url}]})
    def format_results( results: list[dict], domain: str, page: int, page_size: int, total: int ) -> dict: """Format search results according to OpenAI MCP search semantics. Converts domain-specific result formats into a standardized structure with: - id: Unique identifier for the result (required) - title: Human-readable title (required) - text: Brief preview or summary of the content (required) - url: Link to the full resource (optional but recommended for citations) Note: The OpenAI MCP specification does NOT require metadata in search results. Metadata should only be included in fetch results. Args: results: Raw results from domain-specific search domain: Type of results ('article', 'trial', or 'variant') page: Current page number (for internal tracking only) page_size: Number of results per page (for internal tracking only) total: Total number of results available (for internal tracking only) Returns: Dictionary with results array following OpenAI MCP format: {"results": [{"id", "title", "text", "url"}, ...]} Raises: InvalidDomainError: If domain is not recognized """ logger.debug(f"Formatting {len(results)} results for domain: {domain}") formatted_data = [] # Get the appropriate handler try: handler_class = get_domain_handler(domain) except ValueError: raise InvalidDomainError(domain, VALID_DOMAINS) from None # Format each result for result in results: try: formatted_result = handler_class.format_result(result) # Ensure the result has the required OpenAI MCP fields openai_result = { "id": formatted_result.get("id", ""), "title": formatted_result.get("title", DEFAULT_TITLE), "text": formatted_result.get( "snippet", formatted_result.get("text", "") ), "url": formatted_result.get("url", ""), } # Note: OpenAI MCP spec doesn't require metadata in search results # Only include it if explicitly needed for enhanced functionality formatted_data.append(openai_result) except Exception as e: logger.warning(f"Failed to format result in domain {domain}: {e}") # Skip malformed results continue # Add thinking reminder if needed (as first result) reminder = get_thinking_reminder() if reminder and formatted_data: reminder_result = { "id": "thinking-reminder", "title": "⚠️ Research Best Practice Reminder", "text": reminder, "url": "", } formatted_data.insert(0, reminder_result) # Return OpenAI MCP compliant format return {"results": formatted_data}
  • FastMCP application instance where all tools including 'search' are registered via decorators.
    mcp_app = FastMCP( name="BioMCP - Biomedical Model Context Protocol Server", lifespan=lifespan, stateless_http=True, # Enable stateless HTTP for streamable transport )

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/genomoncology/biomcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server