Skip to main content
Glama

PubMed MCP Server

by ncukondo
index.test.ts9.34 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { createSearchHandler } from '../handlers/search.js'; import { createFetchSummaryHandler } from '../handlers/fetch-summary.js'; import { createGetFullTextHandler } from '../handlers/get-fulltext.js'; // Mock the handlers vi.mock('../handlers/search.js', () => ({ createSearchHandler: vi.fn((pubmedOptions) => ({ search: vi.fn(), })), })); vi.mock('../handlers/fetch-summary.js', () => ({ createFetchSummaryHandler: vi.fn((pubmedOptions) => ({ fetchSummary: vi.fn(), })), })); vi.mock('../handlers/get-fulltext.js', () => ({ createGetFullTextHandler: vi.fn((pubmedOptions) => ({ getFullText: vi.fn(), })), })); describe('MCP PubMed Server', () => { let server: McpServer; let mockSearchHandler: { search: ReturnType<typeof vi.fn> }; let mockFetchSummaryHandler: { fetchSummary: ReturnType<typeof vi.fn> }; let mockGetFullTextHandler: { getFullText: ReturnType<typeof vi.fn> }; beforeEach(() => { // Reset mocks vi.clearAllMocks(); // Create mock handlers mockSearchHandler = { search: vi.fn(), }; mockFetchSummaryHandler = { fetchSummary: vi.fn(), }; mockGetFullTextHandler = { getFullText: vi.fn(), }; (createSearchHandler as any).mockReturnValue(mockSearchHandler); (createFetchSummaryHandler as any).mockReturnValue(mockFetchSummaryHandler); (createGetFullTextHandler as any).mockReturnValue(mockGetFullTextHandler); server = new McpServer( { name: 'mcp-server-pubmed', version: '1.0.0', }, { capabilities: { tools: {}, resources: {}, }, } ); // Register resources server.registerResource( "article", new ResourceTemplate("articles://pmid/{pmid}", { list: undefined }), { title: "PubMed Article", description: "Detailed information about a specific PubMed article" }, async (uri, { pmid }) => { try { const articles = await mockFetchSummaryHandler.fetchSummary([pmid as string]); if (articles.length === 0) { throw new Error(`Article with PMID ${pmid} not found`); } const article = articles[0]; return { contents: [{ uri: uri.href, text: JSON.stringify(article, null, 2), mimeType: "application/json" }] }; } catch (error) { return { contents: [{ uri: uri.href, text: `Error fetching article ${pmid}: ${error instanceof Error ? error.message : 'Unknown error'}`, mimeType: "text/plain" }] }; } } ); server.registerResource( "search-results", new ResourceTemplate("search://query/{encodedQuery}", { list: undefined }), { title: "PubMed Search Results", description: "Search results from PubMed for a specific query" }, async (uri, { encodedQuery }) => { try { const query = decodeURIComponent(encodedQuery as string); const results = await mockSearchHandler.search(query, { retMax: 20 }); return { contents: [{ uri: uri.href, text: JSON.stringify(results, null, 2), mimeType: "application/json" }] }; } catch (error) { return { contents: [{ uri: uri.href, text: `Error searching PubMed for "${decodeURIComponent(encodedQuery as string)}": ${error instanceof Error ? error.message : 'Unknown error'}`, mimeType: "text/plain" }] }; } } ); // Register tools server.registerTool( 'search_pubmed', { title: 'PubMed Search', description: 'Search PubMed for scientific articles.', inputSchema: { query: z.string().describe('Search query for PubMed'), searchOptions: z.object({ retMax: z.number().optional().describe('Maximum number of results to return'), retStart: z.number().optional().describe('Starting index for results'), sort: z.enum(['relevance', 'pub_date', 'author', 'journal']).optional().describe('Sort order for results'), dateFrom: z.string().optional().describe('Start date filter (YYYY/MM/DD format)'), dateTo: z.string().optional().describe('End date filter (YYYY/MM/DD format)'), }).optional().describe('Optional search parameters') } }, async ({ query, searchOptions }) => { try { const results = await mockSearchHandler.search(query, searchOptions); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error searching PubMed: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], }; } } ); server.registerTool( 'fetch_summary', { title: 'PubMed Article Summary', description: 'Fetch detailed article information from PubMed using PMIDs.', inputSchema: { pmids: z.array(z.string()).describe('Array of PubMed IDs (PMIDs) to fetch') } }, async ({ pmids }) => { try { const results = await mockFetchSummaryHandler.fetchSummary(pmids); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching article summaries: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], }; } } ); server.registerTool( 'get_fulltext', { title: 'PubMed Full Text', description: 'Get full text content of PubMed articles using PMIDs.', inputSchema: { pmids: z.array(z.string()).describe('Array of PubMed IDs (PMIDs) to get full text for') } }, async ({ pmids }) => { try { const results = await mockGetFullTextHandler.getFullText(pmids); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching full text: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], }; } } ); }); describe('Server Creation', () => { it('should create McpServer with resources and tools capabilities', () => { expect(server).toBeDefined(); expect(server).toBeInstanceOf(McpServer); }); it('should register resources and tools without errors', () => { // If we got here without errors in beforeEach, registration worked expect(true).toBe(true); }); }); describe('Mock Handler Integration', () => { it('should use mocked search handler', async () => { const mockResults = [{ pmid: '12345', title: 'Test Article' }]; mockSearchHandler.search.mockResolvedValue(mockResults); const result = await mockSearchHandler.search('test query'); expect(result).toEqual(mockResults); expect(mockSearchHandler.search).toHaveBeenCalledWith('test query'); }); it('should use mocked fetch summary handler', async () => { const mockResults = [{ pmid: '12345', title: 'Test Article', authors: [] }]; mockFetchSummaryHandler.fetchSummary.mockResolvedValue(mockResults); const result = await mockFetchSummaryHandler.fetchSummary(['12345']); expect(result).toEqual(mockResults); expect(mockFetchSummaryHandler.fetchSummary).toHaveBeenCalledWith(['12345']); }); it('should use mocked get full text handler', async () => { const mockResults = [{ pmid: '12345', fullText: 'Full text content' }]; mockGetFullTextHandler.getFullText.mockResolvedValue(mockResults); const result = await mockGetFullTextHandler.getFullText(['12345']); expect(result).toEqual(mockResults); expect(mockGetFullTextHandler.getFullText).toHaveBeenCalledWith(['12345']); }); it('should handle errors in search handler', async () => { const errorMessage = 'Search failed'; mockSearchHandler.search.mockRejectedValue(new Error(errorMessage)); await expect(mockSearchHandler.search('invalid query')).rejects.toThrow(errorMessage); }); }); });

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

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