mcp-hub-004: Sample MCP backend with echo tool

This commit is contained in:
Agent 2026-03-12 18:21:37 +00:00
parent 9a61026bca
commit 67bbb40830
4 changed files with 202 additions and 1 deletions

View file

@ -4,7 +4,8 @@
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node src/index.js"
"start": "node src/index.js",
"sample-mcp": "node sample-mcp/index.js"
},
"keywords": [],
"author": "",

152
sample-mcp/index.js Normal file
View file

@ -0,0 +1,152 @@
'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();

36
sample-mcp/package-lock.json generated Normal file
View file

@ -0,0 +1,36 @@
{
"name": "sample-mcp",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sample-mcp",
"version": "1.0.0",
"dependencies": {
"ws": "^8.19.0"
}
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}

12
sample-mcp/package.json Normal file
View file

@ -0,0 +1,12 @@
{
"name": "sample-mcp",
"version": "1.0.0",
"description": "Sample MCP backend for testing hub relay",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"ws": "^8.19.0"
}
}