guanghulab/mcp-servers/zhuyuan-gateway/zhuyuan-gateway-mcp.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

433 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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('');
});