152 lines
4.4 KiB
JavaScript
152 lines
4.4 KiB
JavaScript
'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();
|