Skip to main content
Glama
handlers.test.ts8.31 kB
import { McpError, ErrorCode as McpErrorCode } from '@modelcontextprotocol/sdk/types.js' import * as errorHandlers from '../../errors/handlers' import { ErrorCode } from '../../errors/types' import * as tools from '../../tools' import { log } from '../../utils/logger' import { handleListTools, handleCallTool } from '../handlers' import type { ErrorResult } from '../../errors/types' import type { Tool } from '@modelcontextprotocol/sdk/types.js' // Mock dependencies jest.mock('../../utils/logger', () => ({ log: jest.fn(), })) jest.mock('../../tools', () => ({ getAllTools: jest.fn(), hasToolByName: jest.fn(), getToolExecutor: jest.fn(), })) jest.mock('../../errors/handlers', () => { const actual = jest.requireActual('../../errors/handlers') return { ...actual, safeExecute: jest.fn((callback) => { // Execute the callback when calling safeExecute to ensure it's covered const callbackResult = callback() return Promise.resolve({ success: true, data: callbackResult, }) }), } }) describe('Server Handlers', function () { afterEach(() => { jest.clearAllMocks() }) describe('handleListTools', function () { it('should return a list of tools successfully', async function () { // Arrange const mockTools: Tool[] = [ { name: 'echo', description: 'Test tool', inputSchema: { type: 'object', properties: {}, }, }, { name: 'other', description: 'Another tool', inputSchema: { type: 'object', properties: {}, }, }, ] jest.spyOn(tools, 'getAllTools').mockReturnValue(mockTools) // Act const result = await handleListTools() // Assert expect(result).toEqual({ tools: mockTools }) expect(errorHandlers.safeExecute).toHaveBeenCalled() expect(log).toHaveBeenCalledWith('DEBUG: Handling list tools request') // Verify that getAllTools was called - this ensures the callback was executed expect(tools.getAllTools).toHaveBeenCalled() }) it('should throw an error when safeExecute fails', async function () { // Arrange const mockError: ErrorResult = { success: false, error: { code: ErrorCode.SERVER_ERROR, message: 'Failed to get tools', }, } jest.spyOn(errorHandlers, 'safeExecute').mockResolvedValue(mockError) // Act & Assert await expect(handleListTools()).rejects.toThrow(McpError) expect(errorHandlers.safeExecute).toHaveBeenCalled() }) }) describe('handleCallTool', function () { it('should successfully call a tool with parameters', async function () { // Arrange const mockRequest = { params: { name: 'echo', parameters: { message: 'test' }, }, } const mockResult = { content: [{ type: 'text', text: 'test' }], } const mockExecutor = jest.fn().mockResolvedValue(mockResult) jest.spyOn(tools, 'hasToolByName').mockReturnValue(true) jest.spyOn(tools, 'getToolExecutor').mockReturnValue(mockExecutor) // Act const result = await handleCallTool(mockRequest) // Assert expect(result).toEqual(mockResult) expect(tools.hasToolByName).toHaveBeenCalledWith('echo') expect(tools.getToolExecutor).toHaveBeenCalledWith('echo') expect(mockExecutor).toHaveBeenCalledWith({ arguments: { message: 'test' } }) expect(log).toHaveBeenCalledWith(expect.stringContaining('Handling call tool request')) }) it('should handle both parameters and arguments', async function () { // Arrange const mockRequest = { params: { name: 'echo', parameters: { param1: 'value1', arg1: 'argvalue1' }, }, } const mockResult = { content: [{ type: 'text', text: 'test' }], } const mockExecutor = jest.fn().mockResolvedValue(mockResult) jest.spyOn(tools, 'hasToolByName').mockReturnValue(true) jest.spyOn(tools, 'getToolExecutor').mockReturnValue(mockExecutor) // Act const result = await handleCallTool(mockRequest) // Assert expect(result).toEqual(mockResult) expect(mockExecutor).toHaveBeenCalledWith({ arguments: { param1: 'value1', arg1: 'argvalue1', }, }) }) it('should handle case with no parameters or arguments', async function () { // Arrange const mockRequest = { params: { name: 'echo', }, } const mockResult = { content: [{ type: 'text', text: 'test' }], } const mockExecutor = jest.fn().mockResolvedValue(mockResult) jest.spyOn(tools, 'hasToolByName').mockReturnValue(true) jest.spyOn(tools, 'getToolExecutor').mockReturnValue(mockExecutor) // Act const result = await handleCallTool(mockRequest) // Assert expect(result).toEqual(mockResult) expect(mockExecutor).toHaveBeenCalledWith({ arguments: {} }) }) it('should successfully call a tool without parameters provided', async function () { // Arrange const mockRequest = { params: { name: 'echo', }, } const mockResult = { content: [{ type: 'text', text: 'test without params' }], } const mockExecutor = jest.fn().mockResolvedValue(mockResult) jest.spyOn(tools, 'hasToolByName').mockReturnValue(true) jest.spyOn(tools, 'getToolExecutor').mockReturnValue(mockExecutor) // Act const result = await handleCallTool(mockRequest) // Assert expect(result).toEqual(mockResult) expect(tools.hasToolByName).toHaveBeenCalledWith('echo') expect(tools.getToolExecutor).toHaveBeenCalledWith('echo') expect(mockExecutor).toHaveBeenCalledWith({ arguments: {} }) expect(log).toHaveBeenCalledWith(expect.stringContaining('Handling call tool request')) }) it('should handle explicitly null parameters', async function () { // Arrange const mockRequest = { params: { name: 'echo', parameters: null, }, } const mockResult = { content: [{ type: 'text', text: 'null params test' }], } const mockExecutor = jest.fn().mockResolvedValue(mockResult) jest.spyOn(tools, 'hasToolByName').mockReturnValue(true) jest.spyOn(tools, 'getToolExecutor').mockReturnValue(mockExecutor) // Act const result = await handleCallTool(mockRequest as any) // Assert expect(result).toEqual(mockResult) expect(mockExecutor).toHaveBeenCalledWith({ arguments: {} }) }) it('should throw an error when the tool is not found', async function () { // Arrange const mockRequest = { params: { name: 'nonexistent', parameters: {}, }, } jest.spyOn(tools, 'hasToolByName').mockReturnValue(false) // Act & Assert await expect(handleCallTool(mockRequest)).rejects.toThrow( new McpError(McpErrorCode.MethodNotFound, 'Tool not found: nonexistent'), ) expect(tools.hasToolByName).toHaveBeenCalledWith('nonexistent') expect(log).toHaveBeenCalledWith(expect.stringContaining('Tool not found')) }) it('should throw an error when the tool executor is not found', async function () { // Arrange const mockRequest = { params: { name: 'echo', parameters: {}, }, } jest.spyOn(tools, 'hasToolByName').mockReturnValue(true) jest.spyOn(tools, 'getToolExecutor').mockReturnValue(null) // Act & Assert await expect(handleCallTool(mockRequest)).rejects.toThrow( new McpError(McpErrorCode.InternalError, 'Tool executor not found'), ) expect(tools.hasToolByName).toHaveBeenCalledWith('echo') expect(tools.getToolExecutor).toHaveBeenCalledWith('echo') expect(log).toHaveBeenCalledWith(expect.stringContaining('Tool executor not found')) }) it('should throw an error when the tool execution fails', async function () { // Arrange const mockRequest = { params: { name: 'echo', parameters: { message: 'test' }, }, } const mockError = new Error('Execution error') const mockExecutor = jest.fn().mockRejectedValue(mockError) jest.spyOn(tools, 'hasToolByName').mockReturnValue(true) jest.spyOn(tools, 'getToolExecutor').mockReturnValue(mockExecutor) // Act & Assert await expect(handleCallTool(mockRequest)).rejects.toThrow( new McpError(McpErrorCode.InternalError, 'Error executing tool: Error: Execution error'), ) expect(mockExecutor).toHaveBeenCalledWith({ arguments: { message: 'test' } }) expect(log).toHaveBeenCalledWith(expect.stringContaining('Error executing tool')) }) }) })

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/tbreeding/jira-mcp'

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