Skip to main content
Glama
chrismannina

PubMed MCP Server

by chrismannina

search_pubmed

Search PubMed for scientific articles using advanced filters like date ranges, article types, authors, journals, and MeSH terms to find relevant research.

Instructions

Search PubMed for articles with advanced filtering options

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesSearch query using PubMed syntax
max_resultsNoMaximum number of results to return
sort_orderNoSort order for resultsrelevance
date_fromNoStart date (YYYY/MM/DD, YYYY/MM, or YYYY)
date_toNoEnd date (YYYY/MM/DD, YYYY/MM, or YYYY)
date_rangeNoPredefined date range
article_typesNoFilter by article types
authorsNoFilter by author names
journalsNoFilter by journal names
mesh_termsNoFilter by MeSH terms
languageNoLanguage filter (e.g., 'eng', 'fre', 'ger')
has_abstractNoOnly include articles with abstracts
has_full_textNoOnly include articles with full text available
humans_onlyNoOnly include human studies

Implementation Reference

  • The main handler function _handle_search_pubmed that processes the tool call arguments, performs validation, calls PubMedClient.search_articles with the parameters, formats the search results into an MCPResponse with summaries and article details.
    async def _handle_search_pubmed(self, arguments: Dict[str, Any]) -> MCPResponse:
        """Handle PubMed search with advanced filtering."""
        try:
            # Parse arguments
            query = arguments.get("query", "")
            if not query:
                return MCPResponse(
                    content=[{"type": "text", "text": "Query parameter is required"}], is_error=True
                )
    
            max_results = arguments.get("max_results", 20)
            # Handle negative max_results
            if max_results < 0:
                max_results = 0
    
            sort_order = SortOrder(arguments.get("sort_order", "relevance"))
            date_from = arguments.get("date_from")
            date_to = arguments.get("date_to")
            date_range = (
                DateRange(arguments.get("date_range")) if arguments.get("date_range") else None
            )
    
            # Parse article types
            article_types = None
            if arguments.get("article_types"):
                article_types = [ArticleType(at) for at in arguments["article_types"]]
    
            # Perform search
            search_result = await self.pubmed_client.search_articles(
                query=query,
                max_results=max_results,
                sort_order=sort_order,
                date_from=date_from,
                date_to=date_to,
                date_range=date_range,
                article_types=article_types,
                authors=arguments.get("authors"),
                journals=arguments.get("journals"),
                mesh_terms=arguments.get("mesh_terms"),
                language=arguments.get("language"),
                has_abstract=arguments.get("has_abstract"),
                has_full_text=arguments.get("has_full_text"),
                humans_only=arguments.get("humans_only"),
                cache=self.cache,
            )
    
            # Format response
            content = []
    
            # Summary
            content.append(
                {
                    "type": "text",
                    "text": f"**PubMed Search Results**\n\n"
                    f"Query: {query}\n"
                    f"Total Results: {search_result.total_results:,}\n"
                    f"Returned: {search_result.returned_results}\n"
                    f"Search Time: {search_result.search_time:.2f}s\n",
                }
            )
    
            # Articles
            if search_result.articles:
                for i, article_data in enumerate(search_result.articles, 1):
                    article_text = self._format_article_summary(article_data, i)
                    content.append({"type": "text", "text": article_text})
            else:
                content.append({"type": "text", "text": "No articles found for this query."})
    
            return MCPResponse(
                content=content,
                metadata={
                    "total_results": search_result.total_results,
                    "search_time": search_result.search_time,
                },
            )
    
        except Exception as e:
            logger.error(f"Error in search_pubmed: {e}")
            return MCPResponse(
                content=[{"type": "text", "text": f"Search error: {str(e)}"}], is_error=True
            )
  • The schema definition for the 'search_pubmed' tool, including inputSchema with all parameters, types, descriptions, and required fields. Part of the TOOL_DEFINITIONS list used by get_tools().
    {
        "name": "search_pubmed",
        "description": ("Search PubMed for articles with advanced filtering options"),
        "inputSchema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search query using PubMed syntax"},
                "max_results": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 200,
                    "default": 20,
                    "description": "Maximum number of results to return",
                },
                "sort_order": {
                    "type": "string",
                    "enum": ["relevance", "pub_date", "author", "journal", "title"],
                    "default": "relevance",
                    "description": "Sort order for results",
                },
                "date_from": {
                    "type": "string",
                    "description": "Start date (YYYY/MM/DD, YYYY/MM, or YYYY)",
                },
                "date_to": {
                    "type": "string",
                    "description": "End date (YYYY/MM/DD, YYYY/MM, or YYYY)",
                },
                "date_range": {
                    "type": "string",
                    "enum": ["1y", "5y", "10y", "all"],
                    "description": "Predefined date range",
                },
                "article_types": {
                    "type": "array",
                    "items": {
                        "type": "string",
                        "enum": [
                            "Journal Article",
                            "Review",
                            "Systematic Review",
                            "Meta-Analysis",
                            "Clinical Trial",
                            "Randomized Controlled Trial",
                            "Case Reports",
                            "Letter",
                            "Editorial",
                            "Comment",
                        ],
                    },
                    "description": "Filter by article types",
                },
                "authors": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Filter by author names",
                },
                "journals": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Filter by journal names",
                },
                "mesh_terms": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Filter by MeSH terms",
                },
                "language": {
                    "type": "string",
                    "description": ("Language filter (e.g., 'eng', 'fre', 'ger')"),
                },
                "has_abstract": {
                    "type": "boolean",
                    "description": "Only include articles with abstracts",
                },
                "has_full_text": {
                    "type": "boolean",
                    "description": ("Only include articles with full text available"),
                },
                "humans_only": {"type": "boolean", "description": "Only include human studies"},
            },
            "required": ["query"],
        },
  • The handler_map dictionary in handle_tool_call method that registers and routes 'search_pubmed' tool calls to the _handle_search_pubmed function.
    handler_map = {
        "search_pubmed": self._handle_search_pubmed,
        "get_article_details": self._handle_get_article_details,
        "search_by_author": self._handle_search_by_author,
        "find_related_articles": self._handle_find_related_articles,
        "export_citations": self._handle_export_citations,
        "search_mesh_terms": self._handle_search_mesh_terms,
        "search_by_journal": self._handle_search_by_journal,
        "get_trending_topics": self._handle_get_trending_topics,
        "analyze_research_trends": self._handle_analyze_research_trends,
        "compare_articles": self._handle_compare_articles,
        "get_journal_metrics": self._handle_get_journal_metrics,
        "advanced_search": self._handle_advanced_search,
    }
  • Core helper method PubMedClient.search_articles that executes the PubMed API search using ESearch, fetches details with EFetch, handles caching, and returns a SearchResult. Directly called by the tool handler.
    async def search_articles(
        self,
        query: str,
        max_results: int = 20,
        sort_order: SortOrder = SortOrder.RELEVANCE,
        date_from: Optional[str] = None,
        date_to: Optional[str] = None,
        date_range: Optional[DateRange] = None,
        article_types: Optional[List[ArticleType]] = None,
        authors: Optional[List[str]] = None,
        journals: Optional[List[str]] = None,
        mesh_terms: Optional[List[str]] = None,
        language: Optional[str] = None,
        has_abstract: Optional[bool] = None,
        has_full_text: Optional[bool] = None,
        humans_only: Optional[bool] = None,
        cache: Optional[CacheManager] = None,
    ) -> SearchResult:
        """
        Search PubMed with advanced filtering.
    
        Args:
            query: Search query
            max_results: Maximum results to return
            sort_order: Sort order for results
            date_from: Start date filter
            date_to: End date filter
            date_range: Predefined date range
            article_types: Article type filters
            authors: Author filters
            journals: Journal filters
            mesh_terms: MeSH term filters
            language: Language filter
            has_abstract: Only articles with abstracts
            has_full_text: Only articles with full text
            humans_only: Only human studies
            cache: Cache manager instance
    
        Returns:
            SearchResult containing articles and metadata
        """
        start_time = time.time()
    
        # Check cache first
        if cache:
            cache_key = cache.generate_key(
                "search",
                query=query,
                max_results=max_results,
                sort_order=sort_order.value,
                date_from=date_from,
                date_to=date_to,
                date_range=date_range.value if date_range else None,
                article_types=[at.value for at in article_types] if article_types else None,
                authors=authors,
                journals=journals,
                mesh_terms=mesh_terms,
                language=language,
                has_abstract=has_abstract,
                has_full_text=has_full_text,
                humans_only=humans_only,
            )
            cached_result = cache.get(cache_key)
            if cached_result:
                # Convert cached article dicts back to Article objects
                cached_articles = [
                    Article(**article_data) for article_data in cached_result["articles"]
                ]
                cached_result["articles"] = cached_articles
                return SearchResult(**cached_result)
    
        # Handle date range shortcuts
        if date_range and not (date_from or date_to):
            date_to = datetime.now().strftime("%Y/%m/%d")
            if date_range == DateRange.LAST_YEAR:
                date_from = (datetime.now() - timedelta(days=365)).strftime("%Y/%m/%d")
            elif date_range == DateRange.LAST_5_YEARS:
                date_from = (datetime.now() - timedelta(days=365 * 5)).strftime("%Y/%m/%d")
            elif date_range == DateRange.LAST_10_YEARS:
                date_from = (datetime.now() - timedelta(days=365 * 10)).strftime("%Y/%m/%d")
    
        # Build complex search query
        search_query = build_search_query(
            query,
            authors=authors,
            journals=journals,
            mesh_terms=mesh_terms,
            article_types=([at.value for at in article_types] if article_types else None),
            date_from=date_from,
            date_to=date_to,
            language=language,
            has_abstract=has_abstract,
            has_full_text=has_full_text,
            humans_only=humans_only,
        )
    
        logger.info(f"Executing PubMed search: {search_query}")
    
        # Search for article IDs
        search_params = self._build_params(
            db="pubmed",
            term=search_query,
            retmax=str(max_results),
            retmode="json",
            sort=sort_order.value,
        )
    
        search_response = await self._make_request("esearch.fcgi", search_params)
        search_data = search_response.json()
    
        search_result = search_data.get("esearchresult", {})
        pmids = search_result.get("idlist", [])
        total_results = int(search_result.get("count", 0))
    
        # Get detailed article information
        articles = []
        if pmids:
            articles = await self._fetch_article_details(pmids, include_full_details=True)
    
        # Build result
        result_data = {
            "query": query,
            "total_results": total_results,
            "returned_results": len(articles),
            "articles": articles,  # Store Article objects directly
            "search_time": time.time() - start_time,
            "suggestions": [],  # Could implement spelling suggestions
        }
    
        # Cache the result (store as dicts for serialization)
        if cache:
            cache_data = {**result_data, "articles": [article.model_dump() for article in articles]}
            cache.set(cache_key, cache_data)
    
        return SearchResult(**result_data)

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

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