Skip to main content
Glama
pshempel

MCP Time Server Node

by pshempel
INTEGRATION.md14.7 kB
# Integration Test Implementation Plan (TDD) ## Overview This document outlines the TDD approach for implementing full MCP protocol integration tests. ## Progress Summary - **Completed**: All 7 Phases ✅ - **Total Integration Tests**: 53 tests across 13 test files - **All Tests Passing**: ✅ (317 total tests in project) - **Integration Test Coverage**: 100% Complete ## TDD Cycle for Integration Tests ### Phase 1: Infrastructure Setup (Red → Green) #### Step 1.1: Test Transport Connection **Red Test:** ```typescript // tests/integration/setup.test.ts it('should create linked transport pair', () => { const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); expect(clientTransport).toBeDefined(); expect(serverTransport).toBeDefined(); }); ``` **Green:** Import InMemoryTransport and verify it works #### Step 1.2: Test Client-Server Connection **Red Test:** ```typescript it('should connect client and server', async () => { const { client, server } = await createTestEnvironment(); expect(client.isConnected()).toBe(true); expect(server.isConnected()).toBe(true); }); ``` **Green:** Implement `createTestEnvironment()` helper #### Step 1.3: Test Cleanup **Red Test:** ```typescript it('should properly close connections', async () => { const { client, server, cleanup } = await createTestEnvironment(); await cleanup(); expect(client.isConnected()).toBe(false); expect(server.isConnected()).toBe(false); }); ``` **Green:** Implement proper cleanup in helper ### Phase 2: Basic Protocol Communication (Red → Green) #### Step 2.1: Test Tools List **Red Test:** ```typescript // tests/integration/protocol.test.ts it('should list tools through protocol', async () => { const { client } = await createTestEnvironment(); const response = await client.request({ method: 'tools/list', params: {} }); expect(response.tools).toBeDefined(); expect(response.tools.length).toBe(8); }); ``` **Green:** Ensure server registers tools/list handler with real tool definitions #### Step 2.2: Test JSON-RPC Structure **Red Test:** ```typescript it('should send valid JSON-RPC 2.0 messages', async () => { const { client, interceptor } = await createTestEnvironmentWithInterceptor(); await client.request({ method: 'tools/list', params: {} }); const message = interceptor.getSentMessages()[0]; expect(message.jsonrpc).toBe('2.0'); expect(message.method).toBe('tools/list'); expect(message.id).toBeDefined(); }); ``` **Green:** Implement message interceptor ### Phase 3: Tool Execution Tests (Red → Green → Refactor) #### Step 3.1: Test Simple Tool (get_current_time) **Red Test:** ```typescript // tests/integration/tools/getCurrentTime.integration.test.ts it('should execute get_current_time with default params', async () => { const { client } = await createTestEnvironment(); const response = await client.request({ method: 'tools/call', params: { name: 'get_current_time', arguments: {} } }); expect(response.content).toHaveLength(1); expect(response.content[0].type).toBe('text'); const result = JSON.parse(response.content[0].text); expect(result.timezone).toBe('UTC'); expect(result.unix).toBeCloseTo(Date.now() / 1000, 0); }); ``` **Green:** Wire up real getCurrentTime tool **Refactor:** Extract `callTool()` helper #### Step 3.2: Test Tool with Complex Params **Red Test:** ```typescript it('should execute convert_timezone with all params', async () => { const result = await callTool(client, 'convert_timezone', { time: '2025-01-01T12:00:00Z', from_timezone: 'UTC', to_timezone: 'America/New_York' }); expect(result.original).toContain('2025-01-01T12:00:00'); expect(result.converted).toContain('2025-01-01T07:00:00'); expect(result.from_offset).toBe('+00:00'); expect(result.to_offset).toBe('-05:00'); }); ``` **Green:** Ensure convertTimezone is properly connected ### Phase 4: Error Handling Tests (Red → Green) #### Step 4.1: Test Invalid Tool Name **Red Test:** ```typescript // tests/integration/errors.test.ts it('should handle unknown tool error', async () => { const { client } = await createTestEnvironment(); await expect( client.request({ method: 'tools/call', params: { name: 'unknown_tool', arguments: {} } }) ).rejects.toThrow('Unknown tool: unknown_tool'); }); ``` **Green:** Ensure error propagation through protocol #### Step 4.2: Test Tool Validation Errors **Red Test:** ```typescript it('should handle invalid timezone error', async () => { const { client } = await createTestEnvironment(); await expect( callTool(client, 'get_current_time', { timezone: 'Invalid/Zone' }) ).rejects.toMatchObject({ code: 'INVALID_TIMEZONE', message: expect.stringContaining('Invalid timezone') }); }); ``` **Green:** Ensure tool errors are properly formatted ### Phase 5: Rate Limiting Integration (Red → Green) #### Step 5.1: Test Rate Limit Enforcement **Red Test:** ```typescript // tests/integration/rateLimiting.test.ts it('should enforce rate limits through protocol', async () => { const { client } = await createTestEnvironment({ rateLimit: 5, rateLimitWindow: 1000 }); // Make 5 requests (at limit) for (let i = 0; i < 5; i++) { await callTool(client, 'get_current_time', {}); } // 6th request should fail await expect( callTool(client, 'get_current_time', {}) ).rejects.toMatchObject({ code: -32000, message: 'Rate limit exceeded' }); }); ``` **Green:** Ensure rate limiter is active in server #### Step 5.2: Test Rate Limit Reset **Red Test:** ```typescript it('should allow requests after rate limit window', async () => { jest.useFakeTimers(); const { client } = await createTestEnvironment({ rateLimit: 2, rateLimitWindow: 1000 }); // Use up limit await callTool(client, 'get_current_time', {}); await callTool(client, 'get_current_time', {}); // Should be blocked await expect(callTool(client, 'get_current_time', {})) .rejects.toMatchObject({ code: -32000 }); // Advance time jest.advanceTimersByTime(1100); // Should work again await expect(callTool(client, 'get_current_time', {})) .resolves.toHaveProperty('timezone'); }); ``` **Green:** Verify rate limiter sliding window works ### Phase 6: Comprehensive Tool Tests (Red → Green) #### Step 6.1-6.8: Test Each Tool For each of the 8 tools, create tests following this pattern: **Red Test Template:** ```typescript // tests/integration/tools/[toolName].integration.test.ts describe('[toolName] integration', () => { let client: Client; beforeEach(async () => { ({ client } = await createTestEnvironment()); }); it('should handle basic [toolName] request', async () => { const result = await callTool(client, '[toolName]', { // minimal valid params }); // assertions specific to tool }); it('should handle complex [toolName] request', async () => { const result = await callTool(client, '[toolName]', { // all params with edge cases }); // comprehensive assertions }); it('should handle [toolName] errors', async () => { await expect( callTool(client, '[toolName]', { // invalid params }) ).rejects.toMatchObject({ code: 'EXPECTED_ERROR_CODE' }); }); }); ``` ### Phase 7: Advanced Integration Tests #### Step 7.1: Test Concurrent Requests **Red Test:** ```typescript it('should handle concurrent requests', async () => { const { client } = await createTestEnvironment(); const promises = [ callTool(client, 'get_current_time', { timezone: 'UTC' }), callTool(client, 'add_time', { time: '2025-01-01', amount: 1, unit: 'days' }), callTool(client, 'format_time', { time: '2025-01-01', format: 'relative' }) ]; const results = await Promise.all(promises); expect(results[0]).toHaveProperty('timezone', 'UTC'); expect(results[1]).toHaveProperty('result'); expect(results[2]).toHaveProperty('formatted'); }); ``` #### Step 7.2: Test Message Ordering **Red Test:** ```typescript it('should maintain message order', async () => { const { client, interceptor } = await createTestEnvironmentWithInterceptor(); await callTool(client, 'get_current_time', {}); await callTool(client, 'add_time', { time: '2025-01-01', amount: 1, unit: 'days' }); const requests = interceptor.getSentMessages() .filter(m => m.method === 'tools/call'); expect(requests[0].params.name).toBe('get_current_time'); expect(requests[1].params.name).toBe('add_time'); }); ``` ## Implementation Order 1. **Create test infrastructure** (Phase 1) - `tests/integration/helpers/setup.ts` - `tests/integration/helpers/interceptor.ts` 2. **Implement basic protocol tests** (Phase 2) - `tests/integration/protocol.test.ts` 3. **Add tool execution tests** (Phase 3) - `tests/integration/tools/basic.test.ts` - One file per tool 4. **Add error handling tests** (Phase 4) - `tests/integration/errors.test.ts` 5. **Add rate limiting tests** (Phase 5) - `tests/integration/rateLimiting.test.ts` 6. **Complete all tool tests** (Phase 6) - Individual test files for each tool 7. **Add advanced tests** (Phase 7) - `tests/integration/advanced.test.ts` ## Key TDD Principles 1. **Write the test first** - See it fail with clear error 2. **Minimal implementation** - Just enough to make test pass 3. **Refactor** - Clean up while keeping tests green 4. **One test at a time** - Don't write multiple failing tests 5. **Clear test names** - Describe what should happen 6. **Arrange-Act-Assert** - Structure each test clearly ## Common Helpers to Build ```typescript // tests/integration/helpers/setup.ts export async function createTestEnvironment(options?: { rateLimit?: number; rateLimitWindow?: number; }): Promise<{ client: Client; server: Server; cleanup: () => Promise<void>; }>; // tests/integration/helpers/tools.ts export async function callTool( client: Client, name: string, args: any ): Promise<any>; // tests/integration/helpers/interceptor.ts export class MessageInterceptor { getSentMessages(): JSONRPCMessage[]; getReceivedMessages(): JSONRPCMessage[]; clear(): void; } ``` ## Estimated Time per Phase - Phase 1: 30 minutes (infrastructure) - Phase 2: 20 minutes (basic protocol) - Phase 3: 30 minutes (tool execution) - Phase 4: 20 minutes (error handling) - Phase 5: 20 minutes (rate limiting) - Phase 6: 60 minutes (all tools) - Phase 7: 20 minutes (advanced) **Total: ~3.5 hours** ## Implementation Status ### ✅ Phase 1: Infrastructure Setup (COMPLETED) - [x] Test Transport Connection - [x] Test Client-Server Connection - [x] Test Cleanup - [x] Create `createTestEnvironment()` helper - [x] Create `callTool()` helper - [x] Test with rate limit options ### ✅ Phase 2: Basic Protocol Communication (COMPLETED) - [x] Test Tools List - [x] Test JSON-RPC Structure - [x] Create message interceptor ### ✅ Phase 3: Tool Execution Tests (COMPLETED) - [x] Test Simple Tool (get_current_time) - [x] Test Tool with Complex Params (convert_timezone) - [x] Extract callTool() helper for reuse ### ✅ Phase 4: Error Handling Tests (COMPLETED) - [x] Test Invalid Tool Name - [x] Test Tool Validation Errors ### ✅ Phase 5: Rate Limiting Integration (COMPLETED) - [x] Test Rate Limit Enforcement - [x] Test Rate Limit Reset - [x] Test Retry-After Information - [x] Test Per-Client Isolation - [x] Test Cross-Tool Rate Limiting ### ✅ Phase 6: Comprehensive Tool Tests (COMPLETED) - [x] get_current_time integration tests - [x] convert_timezone integration tests - [x] add_time integration tests - [x] subtract_time integration tests - [x] calculate_duration integration tests - [x] get_business_days integration tests - [x] next_occurrence integration tests - [x] format_time integration tests ### ✅ Phase 7: Advanced Integration Tests (COMPLETED) - [x] Test Concurrent Requests - [x] Test Many Concurrent Requests (20 requests) - [x] Test Mixed Tool Concurrent Execution - [x] Test Concurrent Requests with Errors - [x] Test Message Ordering - [x] Test Unique Message IDs - [x] Test Request/Response ID Matching - [x] Test Interleaved Concurrent Requests - [x] Test Rapid Sequential Requests - [x] Test Mixed Concurrent/Sequential Patterns ## Completion Summary ### Phases Completed: 7/7 (100%) 🎉 #### Phase 1: Infrastructure Setup ✅ - Created test environment with linked transports - Implemented client-server connection helpers - Added proper cleanup functionality #### Phase 2: Basic Protocol Communication ✅ - Verified tools/list handler returns all 8 tools - Confirmed JSON-RPC 2.0 message structure - Created message interceptor for debugging #### Phase 3: Tool Execution Tests ✅ - Tested basic tool execution (get_current_time) - Tested complex parameters (convert_timezone) - Created reusable callTool helper #### Phase 4: Error Handling Tests ✅ - Verified unknown tool errors - Tested tool validation errors - Fixed error propagation in callTool helper #### Phase 5: Rate Limiting Integration ✅ - Enforced rate limits at configured threshold - Tested sliding window reset - Provided retry-after information - Verified per-client isolation - Confirmed cross-tool counting #### Phase 6: Comprehensive Tool Tests ✅ - Created integration tests for all 8 tools - Tested basic functionality - Tested timezone handling - Tested error scenarios - Fixed test expectations based on actual behavior #### Phase 7: Advanced Integration Tests ✅ - Verified concurrent request handling - Tested up to 20 concurrent requests - Confirmed message ordering preservation - Validated unique message ID generation - Tested request/response ID matching - Verified performance under load - Tested mixed concurrent/sequential patterns ### Test Statistics - **Unit Tests**: 264 tests across 11 files - **Integration Tests**: 53 tests across 13 files - **Total Tests**: 317 tests (all passing) - **Test Coverage**: Full coverage of all MCP tools and protocol handling ### Key Achievements 1. **Complete TDD implementation** - All tests written before or alongside implementation 2. **Comprehensive error handling** - Proper MCP protocol error responses 3. **Rate limiting verification** - Sliding window rate limiter working correctly 4. **Full tool coverage** - All 8 time-related tools thoroughly tested 5. **Protocol compliance** - Proper JSON-RPC 2.0 and MCP protocol implementation 6. **Concurrency testing** - Verified server handles concurrent requests correctly 7. **Performance validation** - Confirmed rapid request handling capabilities

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/pshempel/mcp-time-server-node'

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