/** * Example MCP Remote Client * Demonstrates SSE connection, Last-Event-ID resumption, and tool invocation * * Usage: node tests/fixtures/remote-client.js */ import { EventSource } from 'eventsource'; const MCP_SERVER_URL = process.env.MCP_SERVER_URL || 'http://127.0.0.1:3000'; const MCP_PROTOCOL_VERSION = '2025-06-18'; /** * Example: Connect to MCP server and invoke searchFlights tool */ async function exampleClient() { console.log(`Connecting to MCP server at ${MCP_SERVER_URL}...`); // 1. Initialize connection - send initialize request // Per MCP spec: MCP-Protocol-Version header is NOT required during initialization const initRequest = { jsonrpc: '2.0', id: 0, method: 'initialize', params: { protocolVersion: MCP_PROTOCOL_VERSION, capabilities: {}, clientInfo: { name: 'gds-mock-test-client', version: '1.0.0' } } }; console.log('Sending initialize request (without MCP-Protocol-Version header)...'); const initResponse = await fetch(`${MCP_SERVER_URL}/mcp`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json, text/event-stream' // Note: MCP-Protocol-Version header NOT included during initialization }, body: JSON.stringify(initRequest) }); const initResult = await initResponse.json(); console.log('Server initialized:', JSON.stringify(initResult, null, 2)); // Note: Server runs in stateless mode (no MCP-Session-Id header will be present) // Each client can initialize independently without server-side session management const sessionId = initResponse.headers.get('MCP-Session-Id'); if (sessionId) { console.log('Session ID:', sessionId); } else { console.log('Server running in stateless mode (no session ID)'); } // 2. Open SSE stream for server messages (now with MCP-Protocol-Version header) const eventSource = new EventSource(`${MCP_SERVER_URL}/mcp`, { headers: { 'MCP-Protocol-Version': MCP_PROTOCOL_VERSION, ...(sessionId && { 'MCP-Session-Id': sessionId }) } }); let lastEventId = null; // Handle SSE events eventSource.addEventListener('message', (event) => { console.log('Received SSE event:', event.data); lastEventId = event.lastEventId; try { const data = JSON.parse(event.data); // Extract session ID from InitializeResult if (data.result && data.result.protocolVersion) { console.log('Server initialized:', data.result); } } catch { // Empty data or non-JSON - expected per SSE polling pattern } }); eventSource.addEventListener('error', (error) => { console.error('SSE error:', error); }); // Wait for connection await new Promise(resolve => setTimeout(resolve, 1000)); // 2. Send tool invocation via POST const searchRequest = { jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'searchFlights', arguments: { origin: 'JFK', destination: 'LAX', departureDate: '2026-05-01', passengers: 1, cabin: 'economy' } } }; console.log('Sending searchFlights request...'); const response = await fetch(`${MCP_SERVER_URL}/mcp`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'MCP-Protocol-Version': MCP_PROTOCOL_VERSION, ...(sessionId && { 'MCP-Session-Id': sessionId }), ...(lastEventId && { 'Last-Event-ID': lastEventId }) }, body: JSON.stringify(searchRequest) }); const result = await response.json(); console.log('Search results:', JSON.stringify(result, null, 2)); // 3. Demonstrate Last-Event-ID resumption if (lastEventId) { console.log(`\nReconnecting with Last-Event-ID: ${lastEventId}...`); const resumedEventSource = new EventSource(`${MCP_SERVER_URL}/mcp`, { headers: { 'MCP-Protocol-Version': MCP_PROTOCOL_VERSION, 'Last-Event-ID': lastEventId } }); resumedEventSource.addEventListener('message', (event) => { console.log('Resumed stream event:', event.data); }); // Close after demonstration setTimeout(() => { resumedEventSource.close(); eventSource.close(); console.log('\nClient demonstration complete'); process.exit(0); }, 2000); } } // Run example exampleClient().catch(err => { console.error('Client error:', err); process.exit(1); });