guanghulab/mcp-servers/zhuyuan-gateway/zhuyuan-gateway-mcp.js

433 lines
13 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* 铸渊网关 MCP Server
*
* 这是铸渊的"电话"通过它联系每一台服务器上的 gatekeeper
* 暴露给AI客户端的MCP工具集
*
* 这个MCP Server运行在本地通过HTTP连接各服务器的gatekeeper
* 可以在WorkBuddy中使用以后换了模型也能直接用
*
* 启动方式
* node zhuyuan-gateway-mcp.js
*
* 依赖需要配置 servers.json 文件
* 格式见 servers.template.json
*/
const http = require('http');
const https = require('https');
const fs = require('fs');
const path = require('path');
// ============================================================
// 配置
// ============================================================
const CONFIG_PATH = path.join(__dirname, 'servers.json');
const MCP_PORT = parseInt(process.env.MCP_PORT || process.argv[2] || '3920', 10);
// ============================================================
// 加载服务器配置
// ============================================================
let servers = {};
try {
if (fs.existsSync(CONFIG_PATH)) {
servers = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
console.log(`[网关] 已加载 ${Object.keys(servers).length} 台服务器配置`);
} else {
console.log('[网关] ⚠️ 未找到 servers.json使用空配置');
console.log('[网关] 请复制 servers.template.json 为 servers.json 并填入服务器信息');
}
} catch (err) {
console.error('[网关] ❌ 加载配置失败:', err.message);
}
// ============================================================
// 工具发送请求到gatekeeper
// ============================================================
function callGatekeeper(serverName, endpoint, body, timeout) {
return new Promise((resolve) => {
const cfg = servers[serverName];
if (!cfg) {
resolve({
ok: false,
error: `未找到服务器: ${serverName}。可用服务器: ${Object.keys(servers).join(', ')}`,
});
return;
}
const postData = JSON.stringify(body || {});
const t = timeout || 30000;
const options = {
hostname: cfg.host,
port: cfg.port,
path: endpoint,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData),
'Authorization': `Bearer ${cfg.key}`,
},
timeout: t,
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', chunk => { data += chunk; });
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch(e) {
resolve({ ok: false, error: '解析响应失败: ' + e.message, raw: data.slice(0, 500) });
}
});
});
req.on('error', (err) => {
resolve({ ok: false, error: `连接失败: ${err.message}` });
});
req.on('timeout', () => {
req.destroy();
resolve({ ok: false, error: `请求超时 (${t}ms)` });
});
req.write(postData);
req.end();
});
}
// ============================================================
// MCP 工具实现
// ============================================================
const tools = {
/**
* 列出所有已注册的服务器
*/
async server_list() {
const names = Object.keys(servers);
if (names.length === 0) {
return { servers: [], message: '暂未配置任何服务器。请在 servers.json 中添加服务器信息。' };
}
const list = names.map(name => ({
name,
host: servers[name].host,
port: servers[name].port,
label: servers[name].label || name,
}));
return { servers: list, count: list.length };
},
/**
* 检查某台服务器的健康状态
*/
async server_health(serverName) {
return await callGatekeeper(serverName, '/health', {});
},
/**
* 获取某台服务器的完整系统状态
*/
async server_status(serverName) {
return await callGatekeeper(serverName, '/status', {});
},
/**
* 在服务器上执行命令
*/
async server_exec(serverName, cmd, timeout) {
return await callGatekeeper(serverName, '/exec', { cmd, timeout }, timeout || 30000);
},
/**
* 读取服务器上的文件
*/
async server_file_read(serverName, filePath) {
return await callGatekeeper(serverName, '/file/read', { path: filePath });
},
/**
* 写入文件到服务器
*/
async server_file_write(serverName, filePath, content) {
return await callGatekeeper(serverName, '/file/write', { path: filePath, content });
},
/**
* 列出服务器上的目录
*/
async server_file_list(serverName, dirPath) {
return await callGatekeeper(serverName, '/file/list', { path: dirPath });
},
/**
* 扫描所有服务器状态
*/
async server_scan_all() {
const results = {};
for (const name of Object.keys(servers)) {
try {
const health = await callGatekeeper(name, '/health', {});
results[name] = health.ok ? '在线' : '异常';
} catch(e) {
results[name] = '不可达';
}
}
return { results };
},
};
// ============================================================
// MCP 协议实现Streamable HTTP
// ============================================================
const server = http.createServer(async (req, res) => {
// CORS
if (req.method === 'OPTIONS') {
res.writeHead(204, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS, GET',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
});
res.end();
return;
}
// 只接受 POST /mcp
if (req.method !== 'POST' || req.url !== '/mcp') {
res.writeHead(404);
res.end('Not Found');
return;
}
// 解析 body
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', async () => {
try {
const msg = JSON.parse(body);
await handleMCPMessage(msg, res);
} catch (err) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid JSON: ' + err.message }));
}
});
});
async function handleMCPMessage(msg, res) {
const headers = { 'Content-Type': 'application/json' };
if (msg.method === 'initialize') {
res.writeHead(200, headers);
res.end(JSON.stringify({
jsonrpc: '2.0',
id: msg.id,
result: {
protocolVersion: '2025-03-26',
capabilities: {
tools: {},
resources: {},
},
serverInfo: {
name: 'zhuyuan-gateway-mcp',
version: '1.0.0',
},
},
}));
return;
}
if (msg.method === 'tools/list') {
res.writeHead(200, headers);
res.end(JSON.stringify({
jsonrpc: '2.0',
id: msg.id,
result: {
tools: [
{
name: 'server_list',
description: '列出所有已注册的铸渊服务器及其状态',
inputSchema: { type: 'object', properties: {}, required: [] },
},
{
name: 'server_health',
description: '检查指定服务器的健康状态',
inputSchema: {
type: 'object',
properties: {
server: { type: 'string', description: '服务器名称(如 brain、face、sg-module 等)' },
},
required: ['server'],
},
},
{
name: 'server_status',
description: '获取指定服务器的完整系统状态CPU、内存、磁盘、运行时间等',
inputSchema: {
type: 'object',
properties: {
server: { type: 'string', description: '服务器名称' },
},
required: ['server'],
},
},
{
name: 'server_exec',
description: '在指定服务器上执行shell命令并返回输出。这是铸渊管理服务器的主要方式。',
inputSchema: {
type: 'object',
properties: {
server: { type: 'string', description: '服务器名称' },
cmd: { type: 'string', description: '要执行的shell命令' },
timeout: { type: 'number', description: '超时时间毫秒默认30000' },
},
required: ['server', 'cmd'],
},
},
{
name: 'server_file_read',
description: '读取服务器上指定文件的内容',
inputSchema: {
type: 'object',
properties: {
server: { type: 'string', description: '服务器名称' },
path: { type: 'string', description: '文件路径' },
},
required: ['server', 'path'],
},
},
{
name: 'server_file_write',
description: '写入内容到服务器的指定文件',
inputSchema: {
type: 'object',
properties: {
server: { type: 'string', description: '服务器名称' },
path: { type: 'string', description: '文件路径' },
content: { type: 'string', description: '文件内容' },
},
required: ['server', 'path', 'content'],
},
},
{
name: 'server_file_list',
description: '列出服务器上指定目录的内容',
inputSchema: {
type: 'object',
properties: {
server: { type: 'string', description: '服务器名称' },
path: { type: 'string', description: '目录路径,默认为根目录' },
},
required: ['server'],
},
},
{
name: 'server_scan_all',
description: '快速扫描所有注册服务器的在线状态',
inputSchema: { type: 'object', properties: {}, required: [] },
},
],
},
}));
return;
}
if (msg.method === 'tools/call') {
const toolName = msg.params.name;
const args = msg.params.arguments || {};
try {
let result;
switch (toolName) {
case 'server_list':
result = await tools.server_list();
break;
case 'server_health':
result = await tools.server_health(args.server);
break;
case 'server_status':
result = await tools.server_status(args.server);
break;
case 'server_exec':
result = await tools.server_exec(args.server, args.cmd, args.timeout);
break;
case 'server_file_read':
result = await tools.server_file_read(args.server, args.path);
break;
case 'server_file_write':
result = await tools.server_file_write(args.server, args.path, args.content);
break;
case 'server_file_list':
result = await tools.server_file_list(args.server, args.path);
break;
case 'server_scan_all':
result = await tools.server_scan_all();
break;
default:
result = { error: `未知工具: ${toolName}` };
}
res.writeHead(200, headers);
res.end(JSON.stringify({
jsonrpc: '2.0',
id: msg.id,
result: {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
},
}));
} catch (err) {
res.writeHead(200, headers);
res.end(JSON.stringify({
jsonrpc: '2.0',
id: msg.id,
result: {
content: [{ type: 'text', text: JSON.stringify({ error: err.message }, null, 2) }],
isError: true,
},
}));
}
return;
}
// 未知方法
res.writeHead(200, headers);
res.end(JSON.stringify({
jsonrpc: '2.0',
id: msg.id || null,
error: { code: -32601, message: `Method not found: ${msg.method}` },
}));
}
// ============================================================
// 启动
// ============================================================
server.listen(MCP_PORT, '127.0.0.1', () => {
console.log('');
console.log(' ⚔️ 铸渊网关 MCP Server');
console.log(' ─────────────────────────────');
console.log(` 监听: 127.0.0.1:${MCP_PORT}`);
console.log(` 服务器: ${Object.keys(servers).length} 台已注册`);
console.log('');
console.log(' 📋 可用工具:');
console.log(' server_list — 列出所有服务器');
console.log(' server_health — 检查服务器健康');
console.log(' server_status — 查看系统状态');
console.log(' server_exec — 执行命令');
console.log(' server_file_read — 读取文件');
console.log(' server_file_write — 写入文件');
console.log(' server_file_list — 列出目录');
console.log(' server_scan_all — 扫描全部');
console.log('');
});