mcp-hub-004: Sample MCP backend with echo tool
This commit is contained in:
parent
9a61026bca
commit
67bbb40830
4 changed files with 202 additions and 1 deletions
152
sample-mcp/index.js
Normal file
152
sample-mcp/index.js
Normal 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
36
sample-mcp/package-lock.json
generated
Normal 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
12
sample-mcp/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue