'use strict'; const WebSocket = require('ws'); const HUB_URL = 'wss://mcp.arik.work/ws/register'; const HUB_URL_FALLBACK = 'ws://mcp.arik.work/ws/register'; const SERVICE_ID = 'sample-mcp'; const SECRET = 'dev-secret'; let reconnectDelay = 1000; let ws = null; let useTLS = true; function getHubUrl() { return useTLS ? HUB_URL : HUB_URL_FALLBACK; } function connect() { const url = getHubUrl(); console.log(`[sample-mcp] Connecting to hub at ${url}`); ws = new WebSocket(url); ws.on('open', () => { reconnectDelay = 1000; console.log('[sample-mcp] Connected to hub'); const registerMsg = { type: 'register', serviceId: SERVICE_ID, secret: SECRET }; ws.send(JSON.stringify(registerMsg)); console.log(`[sample-mcp] Sent register: serviceId=${SERVICE_ID}`); }); ws.on('message', (data) => { let envelope; try { envelope = JSON.parse(data); } catch (err) { console.error('[sample-mcp] Failed to parse message:', err.message); return; } if (envelope.type !== 'mcp-request') { console.log(`[sample-mcp] Ignoring message type: ${envelope.type}`); return; } const { requestId, clientSessionId, payload } = envelope; console.log(`[sample-mcp] Request received: requestId=${requestId} method=${payload && payload.method}`); const method = payload && payload.method; // Notifications have no id and need no response if (method === 'notifications/initialized') { console.log('[sample-mcp] Got notifications/initialized, no response needed'); return; } let result; switch (method) { case 'initialize': result = { protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: 'sample-mcp', version: '1.0.0' }, }; break; case 'tools/list': result = { tools: [ { name: 'echo', description: 'Echoes back the input', inputSchema: { type: 'object', properties: { message: { type: 'string' } }, required: ['message'], }, }, ], }; break; case 'tools/call': { const toolName = payload.params && payload.params.name; const args = (payload.params && payload.params.arguments) || {}; if (toolName === 'echo') { result = { content: [{ type: 'text', text: `Echo: ${args.message}` }], }; } else { const errorResponse = { jsonrpc: '2.0', id: payload.id, error: { code: -32601, message: `Unknown tool: ${toolName}` }, }; sendResponse(requestId, clientSessionId, errorResponse); return; } break; } default: console.warn(`[sample-mcp] Unknown method: ${method}`); const errorResponse = { jsonrpc: '2.0', id: payload.id, error: { code: -32601, message: `Method not found: ${method}` }, }; sendResponse(requestId, clientSessionId, errorResponse); return; } const jsonrpcResponse = { jsonrpc: '2.0', id: payload.id, result }; sendResponse(requestId, clientSessionId, jsonrpcResponse); }); ws.on('close', (code, reason) => { console.log(`[sample-mcp] Disconnected (code=${code} reason=${reason}), reconnecting in ${reconnectDelay}ms`); scheduleReconnect(); }); ws.on('error', (err) => { console.error(`[sample-mcp] WebSocket error: ${err.message}`); if (useTLS && err.message && (err.message.includes('ECONNREFUSED') || err.message.includes('certificate') || err.message.includes('connect'))) { console.log('[sample-mcp] TLS connection failed, will try ws:// on next attempt'); useTLS = false; } }); } function sendResponse(requestId, clientSessionId, jsonrpcPayload) { if (!ws || ws.readyState !== WebSocket.OPEN) { console.error('[sample-mcp] Cannot send response: not connected'); return; } const envelope = { type: 'mcp-response', requestId, clientSessionId, payload: jsonrpcPayload, }; ws.send(JSON.stringify(envelope)); console.log(`[sample-mcp] Response sent: requestId=${requestId} method=${jsonrpcPayload.result ? 'ok' : 'error'}`); } function scheduleReconnect() { setTimeout(() => { connect(); reconnectDelay = Math.min(reconnectDelay * 2, 30000); }, reconnectDelay); } connect();