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