Skip to main content
Glama
index.test.ts11.4 kB
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js' describe('MCP Server', () => { let mockNotifier: any let mockServer: any let handlers: Map<string, any> beforeEach(() => { // Reset module cache vi.resetModules() // Create handlers map handlers = new Map() // Create mock notifier mockNotifier = { sendNotification: vi.fn().mockResolvedValue(undefined), listSessions: vi.fn().mockResolvedValue(['session1', 'session2']), sessionExists: vi.fn().mockResolvedValue(true), getCurrentTmuxInfo: vi .fn() .mockResolvedValue({ session: 'current', window: '1', pane: '0' }), } // Mock the notifier module vi.doMock('../src/notifier', () => ({ TmuxNotifier: vi.fn(() => mockNotifier), })) // Create mock server mockServer = { name: 'macos-notify-mcp', version: '0.1.0', setRequestHandler: vi.fn((schema: any, handler: any) => { handlers.set(schema, handler) }), connect: vi.fn(), } // Mock MCP SDK vi.doMock('@modelcontextprotocol/sdk/server/index.js', () => ({ Server: vi.fn(() => mockServer), })) vi.doMock('@modelcontextprotocol/sdk/server/stdio.js', () => ({ StdioServerTransport: vi.fn(() => ({ start: vi.fn(), close: vi.fn(), })), })) }) afterEach(() => { vi.clearAllMocks() }) async function loadServer() { await import('../src/index') return { handlers } } describe('Tool Registration', () => { it('should register all tools on initialization', async () => { const { handlers } = await loadServer() const listToolsHandler = handlers.get(ListToolsRequestSchema) expect(listToolsHandler).toBeDefined() const response = await listToolsHandler({ method: 'tools/list' }) expect(response.tools).toHaveLength(3) const toolNames = response.tools.map((tool: any) => tool.name) expect(toolNames).toContain('send_notification') expect(toolNames).toContain('list_tmux_sessions') expect(toolNames).toContain('get_current_tmux_info') }) it('should provide correct schema for send_notification tool', async () => { const { handlers } = await loadServer() const listToolsHandler = handlers.get(ListToolsRequestSchema) const response = await listToolsHandler({ method: 'tools/list' }) const sendNotificationTool = response.tools.find( (tool: any) => tool.name === 'send_notification', ) expect(sendNotificationTool).toBeDefined() expect(sendNotificationTool.description).toContain( 'Send a macOS notification', ) expect(sendNotificationTool.inputSchema.type).toBe('object') expect(sendNotificationTool.inputSchema.required).toContain('message') expect( sendNotificationTool.inputSchema.properties.message, ).toBeDefined() expect(sendNotificationTool.inputSchema.properties.title).toBeDefined() expect(sendNotificationTool.inputSchema.properties.sound).toBeDefined() }) }) describe('Tool Execution', () => { let callToolHandler: any beforeEach(async () => { const { handlers } = await loadServer() callToolHandler = handlers.get(CallToolRequestSchema) }) describe('send_notification', () => { it('should send notification with message only', async () => { const request = { method: 'tools/call', params: { name: 'send_notification', arguments: { message: 'Test notification', }, }, } const response = await callToolHandler(request) expect(mockNotifier.sendNotification).toHaveBeenCalledWith({ message: 'Test notification', }) expect(response.content).toHaveLength(1) expect(response.content[0].type).toBe('text') expect(response.content[0].text).toBe('Notification sent: "Test notification"') }) it('should send notification with all parameters', async () => { const request = { method: 'tools/call', params: { name: 'send_notification', arguments: { message: 'Full notification', title: 'Important', sound: 'Glass', session: 'work', window: '2', pane: '1', }, }, } const response = await callToolHandler(request) expect(mockNotifier.sendNotification).toHaveBeenCalledWith({ message: 'Full notification', title: 'Important', sound: 'Glass', session: 'work', window: '2', pane: '1', }) expect(response.content[0].text).toBe('Notification sent: "Full notification" (tmux: work)') }) it('should handle missing required message parameter', async () => { const request = { method: 'tools/call', params: { name: 'send_notification', arguments: { title: 'No message', }, }, } const response = await callToolHandler(request) expect(response.content[0].text).toBe('Error: Message is required') }) it('should handle notification sending errors', async () => { mockNotifier.sendNotification.mockRejectedValue( new Error('Failed to send'), ) const request = { method: 'tools/call', params: { name: 'send_notification', arguments: { message: 'Will fail', }, }, } const response = await callToolHandler(request) expect(response.content[0].text).toBe('Error: Failed to send') }) it('should convert non-string parameters to strings', async () => { const request = { method: 'tools/call', params: { name: 'send_notification', arguments: { message: 123, title: true, window: 456, pane: null, } as any, }, } const response = await callToolHandler(request) expect(mockNotifier.sendNotification).toHaveBeenCalledWith({ message: '123', title: 'true', window: '456', // pane is not included because null is filtered out }) }) }) describe('list_tmux_sessions', () => { it('should list tmux sessions', async () => { const request = { method: 'tools/call', params: { name: 'list_tmux_sessions', arguments: {}, }, } const response = await callToolHandler(request) expect(mockNotifier.listSessions).toHaveBeenCalled() expect(response.content).toHaveLength(1) expect(response.content[0].type).toBe('text') expect(response.content[0].text).toContain('session1') expect(response.content[0].text).toContain('session2') }) it('should handle empty session list', async () => { mockNotifier.listSessions.mockResolvedValue([]) const request = { method: 'tools/call', params: { name: 'list_tmux_sessions', arguments: {}, }, } const response = await callToolHandler(request) expect(response.content[0].text).toBe('No tmux sessions found') }) it('should handle errors when listing sessions', async () => { mockNotifier.listSessions.mockRejectedValue( new Error('Tmux not available'), ) const request = { method: 'tools/call', params: { name: 'list_tmux_sessions', arguments: {}, }, } const response = await callToolHandler(request) expect(response.content[0].text).toBe('Error: Tmux not available') }) }) describe('get_current_tmux_info', () => { it('should get current tmux info', async () => { const request = { method: 'tools/call', params: { name: 'get_current_tmux_info', arguments: {}, }, } const response = await callToolHandler(request) expect(mockNotifier.getCurrentTmuxInfo).toHaveBeenCalled() expect(response.content).toHaveLength(1) expect(response.content[0].type).toBe('text') const text = response.content[0].text expect(text).toContain('Session: current') expect(text).toContain('Window: 1') expect(text).toContain('Pane: 0') }) it('should handle when not in tmux session', async () => { mockNotifier.getCurrentTmuxInfo.mockResolvedValue(null) const request = { method: 'tools/call', params: { name: 'get_current_tmux_info', arguments: {}, }, } const response = await callToolHandler(request) expect(response.content[0].text).toBe('Not in a tmux session') }) it('should handle errors when getting tmux info', async () => { mockNotifier.getCurrentTmuxInfo.mockRejectedValue( new Error('Tmux error'), ) const request = { method: 'tools/call', params: { name: 'get_current_tmux_info', arguments: {}, }, } const response = await callToolHandler(request) expect(response.content[0].text).toBe('Error: Tmux error') }) }) describe('unknown tool', () => { it('should handle unknown tool name', async () => { const request = { method: 'tools/call', params: { name: 'unknown_tool', arguments: {}, }, } const response = await callToolHandler(request) expect(response.content[0].text).toBe('Error: Unknown tool: unknown_tool') }) }) }) describe('Server Lifecycle', () => { it('should create server with correct configuration', async () => { await loadServer() const { Server } = await import('@modelcontextprotocol/sdk/server/index.js') expect(Server).toHaveBeenCalledWith({ name: 'macos-notify-mcp', version: expect.any(String), }, expect.any(Object)) }) it('should connect transport', async () => { await loadServer() const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js') expect(StdioServerTransport).toHaveBeenCalled() }) it('should set up error handling', async () => { const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) await loadServer() // Simulate an error event const errorHandler = mockServer.onerror || mockServer.setRequestHandler.mock.calls.find( (call: any) => call[0] === 'error' )?.[1] if (errorHandler) { const testError = new Error('Test error') errorHandler(testError) expect(consoleErrorSpy).toHaveBeenCalledWith('Server error:', testError) } consoleErrorSpy.mockRestore() }) }) })

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/yuki-yano/macos-notify-mcp'

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