diff --git a/mcp-servers/zhuyuan-gateway/zhuyuan-gateway-mcp.js b/mcp-servers/zhuyuan-gateway/zhuyuan-gateway-mcp.js new file mode 100644 index 0000000..a52c2ba --- /dev/null +++ b/mcp-servers/zhuyuan-gateway/zhuyuan-gateway-mcp.js @@ -0,0 +1,432 @@ +#!/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(''); +});