Skip to main content
Glama
andybrandt

mcp-simple-pubmed

by andybrandt

Search articles about medical and life sciences research available on PubMed.

search_pubmed
Read-only

Search PubMed for medical and life sciences research articles using keywords, field-specific queries, date ranges, and Boolean operators to find relevant studies.

Instructions

Search PubMed for medical and life sciences research articles.

You can use these search features:

  • Simple keyword search: "covid vaccine"

  • Field-specific search:

    • Title search: [Title]

    • Author search: [Author]

    • MeSH terms: [MeSH Terms]

    • Journal: [Journal]

  • Date ranges: Add year or date range like "2020:2024[Date - Publication]"

  • Combine terms with AND, OR, NOT

  • Use quotation marks for exact phrases

Examples:

  • "covid vaccine" - basic search

  • "breast cancer"[Title] AND "2023"[Date - Publication]

  • "Smith J"[Author] AND "diabetes"

  • "RNA"[MeSH Terms] AND "therapy"

The search will return:

  • Paper titles

  • Authors

  • Publication details

  • Abstract preview (when available)

  • Links to full text (when available)

  • DOI when available

  • Keywords and MeSH terms

Note: Use quotes around multi-word terms for best results.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYes
max_resultsNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The primary handler function for the 'search_pubmed' tool. It validates parameters, performs the search via PubMedClient, enriches results with URIs and URLs, and returns formatted JSON.
    async def search_pubmed(query: str, max_results: int = 10) -> str:
        """Search PubMed for medical and life sciences research articles.
    
        You can use these search features:
        - Simple keyword search: "covid vaccine"
        - Field-specific search:
          - Title search: [Title]
          - Author search: [Author]
          - MeSH terms: [MeSH Terms]
          - Journal: [Journal]
        - Date ranges: Add year or date range like "2020:2024[Date - Publication]"
        - Combine terms with AND, OR, NOT
        - Use quotation marks for exact phrases
    
        Examples:
        - "covid vaccine" - basic search
        - "breast cancer"[Title] AND "2023"[Date - Publication]
        - "Smith J"[Author] AND "diabetes"
        - "RNA"[MeSH Terms] AND "therapy"
    
        The search will return:
        - Paper titles
        - Authors
        - Publication details
        - Abstract preview (when available)
        - Links to full text (when available)
        - DOI when available
        - Keywords and MeSH terms
    
        Note: Use quotes around multi-word terms for best results.
        """
        try:
            # Validate and constrain max_results
            max_results = min(max(1, max_results), 50)
            
            logger.info(f"Processing search with query: {query}, max_results: {max_results}")
    
            # Perform the search
            results = await pubmed_client.search_articles(
                query=query,
                max_results=max_results
            )
            
            # Create resource URIs for articles
            articles_with_resources = []
            for article in results:
                pmid = article["pmid"]
                # Add original URIs
                article["abstract_uri"] = f"pubmed://{pmid}/abstract"
                article["full_text_uri"] = f"pubmed://{pmid}/full_text"
                
                # Add DOI URL if DOI exists
                if "doi" in article:
                    article["doi_url"] = f"https://doi.org/{article['doi']}"
                    
                # Add PubMed URLs
                article["pubmed_url"] = f"https://pubmed.ncbi.nlm.nih.gov/{pmid}/"
    
                # Add PMC URL only if PMCID is available
                if "pmcid" in article:
                    article["pmc_url"] = f"https://www.ncbi.nlm.nih.gov/pmc/articles/{article['pmcid']}/"
                
                articles_with_resources.append(article)
    
            # Format the response
            formatted_results = json.dumps(articles_with_resources, indent=2)
            logger.info(f"Search completed successfully, found {len(results)} results")
    
            return formatted_results
            
        except Exception as e:
            logger.exception(f"Error in search_pubmed")
            raise ValueError(f"Error processing search request: {str(e)}")
  • Registers the 'search_pubmed' tool with the FastMCP app, including metadata annotations for title and API hints.
    @app.tool(
        annotations={
            "title": "Search articles about medical and life sciences research available on PubMed.",
            "readOnlyHint": True,
            "openWorldHint": True  # Calls external PubMed API
        }
    )
  • Function signature defines input schema (query: str, max_results: int=10) and output str. Docstring provides detailed usage instructions serving as schema documentation.
    async def search_pubmed(query: str, max_results: int = 10) -> str:
        """Search PubMed for medical and life sciences research articles.
    
        You can use these search features:
        - Simple keyword search: "covid vaccine"
        - Field-specific search:
          - Title search: [Title]
          - Author search: [Author]
          - MeSH terms: [MeSH Terms]
          - Journal: [Journal]
        - Date ranges: Add year or date range like "2020:2024[Date - Publication]"
        - Combine terms with AND, OR, NOT
        - Use quotation marks for exact phrases
    
        Examples:
        - "covid vaccine" - basic search
        - "breast cancer"[Title] AND "2023"[Date - Publication]
        - "Smith J"[Author] AND "diabetes"
        - "RNA"[MeSH Terms] AND "therapy"
    
        The search will return:
        - Paper titles
        - Authors
        - Publication details
        - Abstract preview (when available)
        - Links to full text (when available)
        - DOI when available
        - Keywords and MeSH terms
    
        Note: Use quotes around multi-word terms for best results.
        """
  • Core helper method in PubMedClient that executes Entrez.esearch for PMIDs and fetches details using get_article_details.
    async def search_articles(self, query: str, max_results: int = 10) -> List[Dict[str, Any]]:
        """Search for articles matching the query.
    
        Args:
            query: Search query string
            max_results: Maximum number of results to return
    
        Returns:
            List of article metadata dictionaries
        """
        try:
            logger.info(f"Searching PubMed with query: {query}")
            results = []
            
            # Step 1: Search for article IDs
            handle = Entrez.esearch(db="pubmed", term=query, retmax=str(max_results))
            if not handle:
                logger.error("Got None handle from esearch")
                return []
                
            if isinstance(handle, http.client.HTTPResponse):
                logger.info("Got valid HTTP response from esearch")
                xml_content = handle.read()
                handle.close()
                
                # Parse XML to get IDs
                root = ET.fromstring(xml_content)
                id_list = root.findall('.//Id')
                
                if not id_list:
                    logger.info("No results found")
                    return []
                    
                pmids = [id_elem.text for id_elem in id_list]
                logger.info(f"Found {len(pmids)} articles")
                
                # Step 2: Get details for each article
                for pmid in pmids:
                    article = await self.get_article_details(pmid)
                    if article:
                        results.append(article)
            
            return results
    
        except Exception as e:
            logger.exception(f"Error in search_articles: {str(e)}")
            raise
  • Helper method that parses PubMed efetch XML response to extract structured article metadata including title, abstract, authors, publication details, identifiers, and MeSH terms.
    async def get_article_details(self, pmid: str) -> Optional[Dict[str, Any]]:
        """Get details for a specific article by PMID.
    
        Args:
            pmid: PubMed ID of the article
    
        Returns:
            Dictionary with article metadata or None if not found
        """
        try:
            logger.info(f"Fetching details for PMID {pmid}")
            detail_handle = Entrez.efetch(db="pubmed", id=pmid, rettype="xml")
            
            if detail_handle and isinstance(detail_handle, http.client.HTTPResponse):
                article_xml = detail_handle.read()
                detail_handle.close()
                
                # Parse article details
                article_root = ET.fromstring(article_xml)
                
                # Get basic article data
                article = {
                    "pmid": pmid,
                    "title": self._get_xml_text(article_root, './/ArticleTitle') or "No title",
                    "abstract": self._get_full_abstract(article_root) or "No abstract available",
                    "journal": self._get_xml_text(article_root, './/Journal/Title') or "",
                    "authors": [],
                    "keywords": [],
                    "mesh_terms": []
                }
                
                # Get authors
                author_list = article_root.findall('.//Author')
                for author in author_list:
                    last_name = self._get_xml_text(author, 'LastName') or ""
                    fore_name = self._get_xml_text(author, 'ForeName') or ""
                    if last_name or fore_name:
                        article["authors"].append(f"{last_name} {fore_name}".strip())
                
                # Get publication date
                pub_date = article_root.find('.//PubDate')
                if pub_date is not None:
                    year = self._get_xml_text(pub_date, 'Year')
                    month = self._get_xml_text(pub_date, 'Month')
                    day = self._get_xml_text(pub_date, 'Day')
                    article["publication_date"] = {
                        "year": year,
                        "month": month,
                        "day": day
                    }
                    
                # Get DOI and PMCID if available
                # Important: Only get ArticleIds from the main ArticleIdList, not from references
                pubmed_data = article_root.find('.//PubmedData')
                if pubmed_data is not None:
                    # Use direct child path to avoid getting IDs from ReferenceList
                    article_id_list_elem = pubmed_data.find('ArticleIdList')
                    if article_id_list_elem is not None:
                        for article_id in article_id_list_elem:
                            id_type = article_id.get('IdType')
                            if id_type == 'doi':
                                article["doi"] = article_id.text
                            elif id_type == 'pmc':
                                article["pmcid"] = article_id.text
    
                # Get Keywords
                keyword_list = article_root.findall('.//Keyword')
                for keyword in keyword_list:
                    if keyword.text:
                        # Clean up keyword text (remove trailing periods, etc.)
                        clean_keyword = keyword.text.strip().rstrip('.')
                        if clean_keyword:
                            article["keywords"].append(clean_keyword)
    
                # Get MeSH terms
                mesh_heading_list = article_root.findall('.//MeshHeading')
                for mesh_heading in mesh_heading_list:
                    descriptor = mesh_heading.find('DescriptorName')
                    if descriptor is not None and descriptor.text:
                        mesh_term = {
                            "descriptor": descriptor.text,
                            "ui": descriptor.get('UI', ''),
                            "qualifiers": []
                        }
    
                        # Get qualifiers if present
                        qualifiers = mesh_heading.findall('QualifierName')
                        for qualifier in qualifiers:
                            if qualifier.text:
                                mesh_term["qualifiers"].append({
                                    "name": qualifier.text,
                                    "ui": qualifier.get('UI', '')
                                })
    
                        article["mesh_terms"].append(mesh_term)
    
                return article
                
            return None
    
        except Exception as e:
            logger.exception(f"Error getting article details for PMID {pmid}: {str(e)}")
            return None
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations indicate readOnlyHint=true and openWorldHint=true, which the description aligns with by describing a search operation. The description adds valuable behavioral context beyond annotations, such as available search features, return format details (e.g., paper titles, authors, abstract preview), and performance tips, though it doesn't mention rate limits or authentication needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and front-loaded with the core purpose, followed by bullet points and examples that efficiently convey usage without unnecessary details. Every sentence adds value, making it concise and easy to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (search with multiple features), low schema coverage (0%), and presence of an output schema, the description is complete enough. It thoroughly explains search capabilities, return values, and usage, compensating for the lack of schema descriptions and leveraging the output schema for return format details.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With schema description coverage at 0%, the description compensates well by explaining the 'query' parameter through search features and examples, and it implies the 'max_results' parameter by mentioning return details. However, it doesn't explicitly define 'max_results' or its default value, leaving some gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Search PubMed for medical and life sciences research articles.' This specifies the verb ('Search'), resource ('PubMed'), and domain ('medical and life sciences research articles'), distinguishing it from the sibling tool 'get_paper_fulltext' which presumably retrieves full text rather than searching.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool by detailing search features and examples, and it implicitly distinguishes from the sibling tool 'get_paper_fulltext' by focusing on search functionality rather than full-text retrieval. It also includes a note on best practices ('Use quotes around multi-word terms for best results').

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/andybrandt/mcp-simple-pubmed'

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