Skip to main content
Glama

ClinicalTrials.gov MCP Server

clinicaltrials-analyze-trends.tool.test.ts•12.9 kB
/** * @fileoverview Tests for the clinicaltrials_analyze_trends tool definition. * @module tests/mcp-server/tools/definitions/clinicaltrials-analyze-trends.tool.test */ import { container } from 'tsyringe'; import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest'; import { AppConfig, ClinicalTrialsProvider, } from '../../../../src/container/tokens.js'; import { analyzeTrendsTool } from '../../../../src/mcp-server/tools/definitions/clinicaltrials-analyze-trends.tool.js'; import type { IClinicalTrialsProvider } from '../../../../src/services/clinical-trials-gov/core/IClinicalTrialsProvider.js'; import type { Study } from '../../../../src/services/clinical-trials-gov/types.js'; import { McpError } from '../../../../src/types-global/errors.js'; import { requestContextService } from '../../../../src/utils/index.js'; describe('analyzeTrendsTool', () => { let mockProvider: IClinicalTrialsProvider; let mockConfig: any; beforeEach(() => { mockProvider = { fetchStudy: vi.fn(), listStudies: vi.fn(), getStudyMetadata: vi.fn(), getApiStats: vi.fn(), } as unknown as IClinicalTrialsProvider; mockConfig = { maxStudiesForAnalysis: 5000, }; container.clearInstances(); container.registerInstance(ClinicalTrialsProvider, mockProvider); container.registerInstance(AppConfig, mockConfig); }); it('should have the correct name, title, and description', () => { expect(analyzeTrendsTool.name).toBe('clinicaltrials_analyze_trends'); expect(analyzeTrendsTool.title).toBe('Analyze Clinical Trial Trends'); expect(analyzeTrendsTool.description).toContain('statistical analysis'); }); it('should have correct annotations', () => { expect(analyzeTrendsTool.annotations).toEqual({ readOnlyHint: true, idempotentHint: true, openWorldHint: true, }); }); it('should analyze studies by status', async () => { const mockStudies: Partial<Study>[] = [ { protocolSection: { statusModule: { overallStatus: 'Recruiting' }, }, }, { protocolSection: { statusModule: { overallStatus: 'Recruiting' }, }, }, { protocolSection: { statusModule: { overallStatus: 'Completed' }, }, }, ]; (mockProvider.listStudies as Mock) .mockResolvedValueOnce({ studies: [], totalCount: 3, }) .mockResolvedValueOnce({ studies: mockStudies as Study[], totalCount: 3, }); const input = { analysisType: 'countByStatus' }; const parsedInput = analyzeTrendsTool.inputSchema.parse(input); const context = requestContextService.createRequestContext(); const sdkContext = {} as any; const result = await analyzeTrendsTool.logic( parsedInput, context, sdkContext, ); expect(result.analysis).toHaveLength(1); expect(result.analysis[0]!.analysisType).toBe('countByStatus'); expect(result.analysis[0]!.totalStudies).toBe(3); expect(result.analysis[0]!.results).toEqual({ Recruiting: 2, Completed: 1, }); }); it('should analyze studies by country', async () => { const mockStudies: Partial<Study>[] = [ { protocolSection: { contactsLocationsModule: { locations: [ { country: 'United States' }, { country: 'United States' }, ], }, }, }, { protocolSection: { contactsLocationsModule: { locations: [{ country: 'Canada' }], }, }, }, ]; (mockProvider.listStudies as Mock) .mockResolvedValueOnce({ studies: [], totalCount: 2 }) .mockResolvedValueOnce({ studies: mockStudies as Study[], totalCount: 2, }); const input = { analysisType: 'countByCountry' }; const parsedInput = analyzeTrendsTool.inputSchema.parse(input); const context = requestContextService.createRequestContext(); const sdkContext = {} as any; const result = await analyzeTrendsTool.logic( parsedInput, context, sdkContext, ); expect(result.analysis[0]!.results).toEqual({ 'United States': 2, Canada: 1, }); }); it('should analyze studies by phase', async () => { const mockStudies: Partial<Study>[] = [ { protocolSection: { designModule: { phases: ['Phase 1', 'Phase 2'], }, }, }, { protocolSection: { designModule: { phases: ['Phase 2'], }, }, }, ]; (mockProvider.listStudies as Mock) .mockResolvedValueOnce({ studies: [], totalCount: 2 }) .mockResolvedValueOnce({ studies: mockStudies as Study[], totalCount: 2, }); const input = { analysisType: 'countByPhase' }; const parsedInput = analyzeTrendsTool.inputSchema.parse(input); const context = requestContextService.createRequestContext(); const sdkContext = {} as any; const result = await analyzeTrendsTool.logic( parsedInput, context, sdkContext, ); expect(result.analysis[0]!.results).toEqual({ 'Phase 1': 1, 'Phase 2': 2, }); }); it('should analyze studies by year', async () => { const mockStudies: Partial<Study>[] = [ { protocolSection: { statusModule: { startDateStruct: { date: '2023-01-15' }, }, }, }, { protocolSection: { statusModule: { startDateStruct: { date: '2023-06-20' }, }, }, }, { protocolSection: { statusModule: { startDateStruct: { date: '2024-03-10' }, }, }, }, ]; (mockProvider.listStudies as Mock) .mockResolvedValueOnce({ studies: [], totalCount: 3 }) .mockResolvedValueOnce({ studies: mockStudies as Study[], totalCount: 3, }); const input = { analysisType: 'countByYear' }; const parsedInput = analyzeTrendsTool.inputSchema.parse(input); const context = requestContextService.createRequestContext(); const sdkContext = {} as any; const result = await analyzeTrendsTool.logic( parsedInput, context, sdkContext, ); expect(result.analysis[0]!.results).toEqual({ '2023': 2, '2024': 1, }); }); it('should analyze studies by month', async () => { const mockStudies: Partial<Study>[] = [ { protocolSection: { statusModule: { startDateStruct: { date: '2023-01-15' }, }, }, }, { protocolSection: { statusModule: { startDateStruct: { date: '2023-01-20' }, }, }, }, { protocolSection: { statusModule: { startDateStruct: { date: '2023-02-10' }, }, }, }, ]; (mockProvider.listStudies as Mock) .mockResolvedValueOnce({ studies: [], totalCount: 3 }) .mockResolvedValueOnce({ studies: mockStudies as Study[], totalCount: 3, }); const input = { analysisType: 'countByMonth' }; const parsedInput = analyzeTrendsTool.inputSchema.parse(input); const context = requestContextService.createRequestContext(); const sdkContext = {} as any; const result = await analyzeTrendsTool.logic( parsedInput, context, sdkContext, ); expect(result.analysis[0]!.results).toEqual({ '2023-01': 2, '2023-02': 1, }); }); it('should analyze studies by sponsor type', async () => { const mockStudies: Partial<Study>[] = [ { protocolSection: { sponsorCollaboratorsModule: { leadSponsor: { class: 'INDUSTRY' }, }, }, }, { protocolSection: { sponsorCollaboratorsModule: { leadSponsor: { class: 'INDUSTRY' }, }, }, }, { protocolSection: { sponsorCollaboratorsModule: { leadSponsor: { class: 'NIH' }, }, }, }, ]; (mockProvider.listStudies as Mock) .mockResolvedValueOnce({ studies: [], totalCount: 3 }) .mockResolvedValueOnce({ studies: mockStudies as Study[], totalCount: 3, }); const input = { analysisType: 'countBySponsorType' }; const parsedInput = analyzeTrendsTool.inputSchema.parse(input); const context = requestContextService.createRequestContext(); const sdkContext = {} as any; const result = await analyzeTrendsTool.logic( parsedInput, context, sdkContext, ); expect(result.analysis[0]!.results).toEqual({ INDUSTRY: 2, NIH: 1, }); }); it('should support multiple analysis types', async () => { const mockStudies: Partial<Study>[] = [ { protocolSection: { statusModule: { overallStatus: 'Recruiting' }, designModule: { phases: ['Phase 2'] }, }, }, ]; (mockProvider.listStudies as Mock) .mockResolvedValueOnce({ studies: [], totalCount: 1 }) .mockResolvedValueOnce({ studies: mockStudies as Study[], totalCount: 1, }); const input = { analysisType: ['countByStatus', 'countByPhase'] }; const parsedInput = analyzeTrendsTool.inputSchema.parse(input); const context = requestContextService.createRequestContext(); const sdkContext = {} as any; const result = await analyzeTrendsTool.logic( parsedInput, context, sdkContext, ); expect(result.analysis).toHaveLength(2); expect(result.analysis[0]!.analysisType).toBe('countByStatus'); expect(result.analysis[1]!.analysisType).toBe('countByPhase'); }); it('should throw when study count exceeds limit', async () => { (mockProvider.listStudies as Mock).mockResolvedValue({ studies: [], totalCount: 10000, }); const input = { analysisType: 'countByStatus' }; const parsedInput = analyzeTrendsTool.inputSchema.parse(input); const context = requestContextService.createRequestContext(); const sdkContext = {} as any; await expect( analyzeTrendsTool.logic(parsedInput, context, sdkContext), ).rejects.toThrow(McpError); await expect( analyzeTrendsTool.logic(parsedInput, context, sdkContext), ).rejects.toThrow('exceeds the limit'); }); it('should handle empty result sets', async () => { (mockProvider.listStudies as Mock) .mockResolvedValueOnce({ studies: [], totalCount: 0 }) .mockResolvedValueOnce({ studies: [], totalCount: 0, }); const input = { analysisType: 'countByStatus' }; const parsedInput = analyzeTrendsTool.inputSchema.parse(input); const context = requestContextService.createRequestContext(); const sdkContext = {} as any; const result = await analyzeTrendsTool.logic( parsedInput, context, sdkContext, ); expect(result.analysis[0]!.totalStudies).toBe(0); expect(result.analysis[0]!.results).toEqual({}); }); it('should format response with analysis summaries', () => { const mockOutput = { analysis: [ { analysisType: 'countByStatus' as const, totalStudies: 100, results: { Recruiting: 60, Completed: 30, 'Not yet recruiting': 10, }, }, ], }; const formatted = analyzeTrendsTool.responseFormatter!(mockOutput); expect(formatted).toHaveLength(1); expect(formatted[0]!.type).toBe('text'); const text = (formatted[0]! as any).text; expect(text).toContain('Completed 1 analysis'); expect(text).toContain('Total Studies: 100'); expect(text).toContain('Recruiting: 60 (60.0%)'); expect(text).toContain('Completed: 30 (30.0%)'); }); it('should support query and filter parameters', async () => { (mockProvider.listStudies as Mock) .mockResolvedValueOnce({ studies: [], totalCount: 5 }) .mockResolvedValueOnce({ studies: [], totalCount: 5, }); const input = { query: 'cancer', filter: 'AREA[OverallStatus]Recruiting', analysisType: 'countByPhase', }; const parsedInput = analyzeTrendsTool.inputSchema.parse(input); const context = requestContextService.createRequestContext(); const sdkContext = {} as any; await analyzeTrendsTool.logic(parsedInput, context, sdkContext); expect(mockProvider.listStudies).toHaveBeenCalledWith( expect.objectContaining({ query: 'cancer', filter: 'AREA[OverallStatus]Recruiting', pageSize: 1, }), context, ); }); });

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/cyanheads/clinicaltrialsgov-mcp-server'

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