/** * ═══════════════════════════════════════════════════════════ * 🧠 AGE OS · MCP Server 主入口 * ═══════════════════════════════════════════════════════════ * * 编号: ZY-MCP-001 * 端口: 3100 * 签发: 铸渊 · ICE-GL-ZY001 * 版权: 国作登字-2026-A-00037559 * * 统一暴露 MCP 工具,供网站 AI 交互后端和 Agent 调用。 * 外部访问通过 Nginx /api/mcp/ 反向代理,需 API Key 鉴权。 * 内部访问(127.0.0.1)免鉴权。 * * 鉴权方式: Authorization: Bearer * 免鉴权端点: /health(监控探针) * * 工具清单: * 节点: createNode / updateNode / deleteNode / queryNodes / getNode * 关系: linkNodes / unlinkNodes / getRelations * 结构: buildPath / scanStructure / classify * COS: cosWrite / cosRead / cosDelete / cosList / cosArchive * 人格体: registerPersona / getPersona / updatePersona / listPersonas * getNotebook / updateNotebookPage / addMemoryAnchor / queryMemoryAnchors * addWorldPlace / getWorldMap / updateWorldPlace * addTimelineEntry / getTimeline / addRelationship / getRelationships * registerTrainingAgent / updateTrainingAgent / logTrainingRun / getTrainingStatus * saveFile / getFile / listFiles / getFileHistory * Notion: notionQuery / notionReadPage / notionWritePage / notionUpdatePage / notionWriteSyslog * GitHub: githubReadFile / githubListDir / githubWriteFile / githubGetCommits / githubGetIssues / githubTriggerDeploy * 活模块: registerModule / getModule / listModules / moduleHeartbeat / diagnoseModule * getModuleAlerts / getModuleLearnings / sendHLDP / getHLDPStats * 语料: cosListCorpus / cosExtractCorpus / cosParseGitRepoArchive / cosParseNotionExport * cosParseGPTCorpus / cosGetCorpusStatus * COS数据库: cosDbInit / cosDbGetIndex / cosDbUpdateIndex / cosDbWriteEntry * cosDbReadEntry / cosDbListEntries / cosDbDeleteEntry / cosDbGetStats * 训练: trainingStartSession / trainingProcessCorpus / trainingClassifyEntry * trainingWriteToMemory / trainingGetProgress / trainingRaiseAlert * Notion桥接: notionCosSyncPage / notionCosReadMirror / notionCosListMirror * notionCosBuildIndex / notionCosWriteWorkorder / notionCosReadWorkorder / notionCosListWorkorders * 三方通信: cosAlertScan / cosAlertResolve / cosDispatchTask / cosReadTaskReport * cosListTaskReports / cosApproveTask / cosSendNotification / cosGetCommLink * 权限修复: notionCheckPermissions / notionRepairPermissions / notionListSharedPages * notionGenerateRepairGuide / notionPermissionReport * 微调引擎: finetuneExportDataset / finetuneSubmitJob / finetuneCheckStatus * finetuneRegisterModel / finetuneListModels / finetuneCallModel * finetuneCompareModels / finetuneGetCostEstimate * 光之树: growBranch / growLeaf / growBloom / getTreeNode / getSubtree * tracePath / getPersonaBranch / getRecentLeaves * 天眼: writeSyslog / getTianyanView / querySyslog * COS轮询: cosWatcherStatus / cosWatcherTriggerScan / cosWatcherResetIndex * COS桥接: cosBridgeStatus */ 'use strict'; const crypto = require('crypto'); const https = require('https'); const express = require('express'); const db = require('./db'); const cos = require('./cos'); // ─── 加载工具模块 ─── const nodeOps = require('./tools/node-ops'); const relationOps = require('./tools/relation-ops'); const structureOps = require('./tools/structure-ops'); const cosOps = require('./tools/cos-ops'); const personaOps = require('./tools/persona-ops'); const livingModuleOps = require('./tools/living-module-ops'); // 新增模块 A-G const corpusExtractorOps = require('./tools/corpus-extractor-ops'); const cosPersonaDbOps = require('./tools/cos-persona-db-ops'); const trainingAgentOps = require('./tools/training-agent-ops'); const notionCosBridgeOps = require('./tools/notion-cos-bridge-ops'); const cosCommOps = require('./tools/cos-comm-ops'); const notionPermissionOps = require('./tools/notion-permission-ops'); const finetuneEngineOps = require('./tools/finetune-engine-ops'); // 光之树 + 天眼 const lightTreeOps = require('./tools/light-tree-ops'); // COS桶轮询守护 const cosWatcherOps = require('./tools/cos-watcher-ops'); const cosWatcher = require('./cos-watcher'); // Notion人格体认知操作 const notionPersonaCognitionOps = require('./tools/notion-persona-cognition-ops'); // 跨层开发任务 · 人格体Agent → 副驾驶桥接 const devTaskOps = require('./tools/dev-task-ops'); // ─── 外部集成模块(优雅降级:未安装依赖时不影响核心功能) ─── let notionOps = null; let githubOps = null; let notionClient = null; let githubClient = null; try { notionOps = require('./tools/notion-ops'); notionClient = require('./notion-client'); } catch (err) { console.warn(`[MCP] Notion模块加载跳过: ${err.message}`); } try { githubOps = require('./tools/github-ops'); githubClient = require('./github-client'); } catch (err) { console.warn(`[MCP] GitHub模块加载跳过: ${err.message}`); } // ─── 工具注册表 ─── const TOOLS = { // 节点操作 createNode: nodeOps.createNode, updateNode: nodeOps.updateNode, deleteNode: nodeOps.deleteNode, queryNodes: nodeOps.queryNodes, getNode: nodeOps.getNode, // 关系操作 linkNodes: relationOps.linkNodes, unlinkNodes: relationOps.unlinkNodes, getRelations: relationOps.getRelations, // 结构操作 buildPath: structureOps.buildPath, scanStructure: structureOps.scanStructure, classify: structureOps.classify, // COS操作 cosWrite: cosOps.cosWrite, cosRead: cosOps.cosRead, cosDelete: cosOps.cosDelete, cosList: cosOps.cosList, cosArchive: cosOps.cosArchive, // 人格体COS隔离路径操作(限定 /{persona_id}/ 目录) personaCosWrite: cosOps.personaCosWrite, personaCosRead: cosOps.personaCosRead, personaCosList: cosOps.personaCosList, // 人格体操作 · S15 registerPersona: personaOps.registerPersona, getPersona: personaOps.getPersona, updatePersona: personaOps.updatePersona, listPersonas: personaOps.listPersonas, getNotebook: personaOps.getNotebook, updateNotebookPage: personaOps.updateNotebookPage, addMemoryAnchor: personaOps.addMemoryAnchor, queryMemoryAnchors: personaOps.queryMemoryAnchors, addWorldPlace: personaOps.addWorldPlace, getWorldMap: personaOps.getWorldMap, updateWorldPlace: personaOps.updateWorldPlace, addTimelineEntry: personaOps.addTimelineEntry, getTimeline: personaOps.getTimeline, addRelationship: personaOps.addRelationship, getRelationships: personaOps.getRelationships, registerTrainingAgent: personaOps.registerTrainingAgent, updateTrainingAgent: personaOps.updateTrainingAgent, logTrainingRun: personaOps.logTrainingRun, getTrainingStatus: personaOps.getTrainingStatus, saveFile: personaOps.saveFile, getFile: personaOps.getFile, listFiles: personaOps.listFiles, getFileHistory: personaOps.getFileHistory, // Notion操作(动态注册) ...(notionOps ? { notionQuery: notionOps.notionQuery, notionReadPage: notionOps.notionReadPage, notionWritePage: notionOps.notionWritePage, notionUpdatePage: notionOps.notionUpdatePage, notionWriteSyslog: notionOps.notionWriteSyslog } : {}), // GitHub操作(动态注册) ...(githubOps ? { githubReadFile: githubOps.githubReadFile, githubListDir: githubOps.githubListDir, githubWriteFile: githubOps.githubWriteFile, githubGetCommits: githubOps.githubGetCommits, githubGetIssues: githubOps.githubGetIssues, githubTriggerDeploy: githubOps.githubTriggerDeploy } : {}), // 模块A · COS语料读取引擎 cosListCorpus: corpusExtractorOps.cosListCorpus, cosExtractCorpus: corpusExtractorOps.cosExtractCorpus, cosParseGitRepoArchive: corpusExtractorOps.cosParseGitRepoArchive, cosParseNotionExport: corpusExtractorOps.cosParseNotionExport, cosParseGPTCorpus: corpusExtractorOps.cosParseGPTCorpus, cosGetCorpusStatus: corpusExtractorOps.cosGetCorpusStatus, // 模块G · COS桶内自研数据库 cosDbInit: cosPersonaDbOps.cosDbInit, cosDbGetIndex: cosPersonaDbOps.cosDbGetIndex, cosDbUpdateIndex: cosPersonaDbOps.cosDbUpdateIndex, cosDbWriteEntry: cosPersonaDbOps.cosDbWriteEntry, cosDbReadEntry: cosPersonaDbOps.cosDbReadEntry, cosDbListEntries: cosPersonaDbOps.cosDbListEntries, cosDbDeleteEntry: cosPersonaDbOps.cosDbDeleteEntry, cosDbGetStats: cosPersonaDbOps.cosDbGetStats, // 模块B · 铸渊思维逻辑训练Agent trainingStartSession: trainingAgentOps.trainingStartSession, trainingProcessCorpus: trainingAgentOps.trainingProcessCorpus, trainingClassifyEntry: trainingAgentOps.trainingClassifyEntry, trainingWriteToMemory: trainingAgentOps.trainingWriteToMemory, trainingGetProgress: trainingAgentOps.trainingGetProgress, trainingRaiseAlert: trainingAgentOps.trainingRaiseAlert, // 模块C · Notion ↔ COS桥接 notionCosSyncPage: notionCosBridgeOps.notionCosSyncPage, notionCosReadMirror: notionCosBridgeOps.notionCosReadMirror, notionCosListMirror: notionCosBridgeOps.notionCosListMirror, notionCosBuildIndex: notionCosBridgeOps.notionCosBuildIndex, notionCosWriteWorkorder: notionCosBridgeOps.notionCosWriteWorkorder, notionCosReadWorkorder: notionCosBridgeOps.notionCosReadWorkorder, notionCosListWorkorders: notionCosBridgeOps.notionCosListWorkorders, // 模块D+E · COS桶示警 + 三方对接 cosAlertScan: cosCommOps.cosAlertScan, cosAlertResolve: cosCommOps.cosAlertResolve, cosDispatchTask: cosCommOps.cosDispatchTask, cosReadTaskReport: cosCommOps.cosReadTaskReport, cosListTaskReports: cosCommOps.cosListTaskReports, cosApproveTask: cosCommOps.cosApproveTask, cosSendNotification: cosCommOps.cosSendNotification, cosGetCommLink: cosCommOps.cosGetCommLink, // 模块F · Notion权限自动修复 notionCheckPermissions: notionPermissionOps.notionCheckPermissions, notionRepairPermissions: notionPermissionOps.notionRepairPermissions, notionListSharedPages: notionPermissionOps.notionListSharedPages, notionGenerateRepairGuide: notionPermissionOps.notionGenerateRepairGuide, notionPermissionReport: notionPermissionOps.notionPermissionReport, // 模块H · 开源模型微调引擎 finetuneExportDataset: finetuneEngineOps.finetuneExportDataset, finetuneSubmitJob: finetuneEngineOps.finetuneSubmitJob, finetuneCheckStatus: finetuneEngineOps.finetuneCheckStatus, finetuneRegisterModel: finetuneEngineOps.finetuneRegisterModel, finetuneListModels: finetuneEngineOps.finetuneListModels, finetuneCallModel: finetuneEngineOps.finetuneCallModel, finetuneCompareModels: finetuneEngineOps.finetuneCompareModels, finetuneGetCostEstimate: finetuneEngineOps.finetuneGetCostEstimate, // 活模块操作 · S5 registerModule: livingModuleOps.registerModule, getModule: livingModuleOps.getModule, listModules: livingModuleOps.listModules, moduleHeartbeat: livingModuleOps.moduleHeartbeat, diagnoseModule: livingModuleOps.diagnoseModule, getModuleAlerts: livingModuleOps.getModuleAlerts, getModuleLearnings: livingModuleOps.getModuleLearnings, sendHLDP: livingModuleOps.sendHLDP, getHLDPStats: livingModuleOps.getHLDPStats, // 光之树 · 生长操作 growBranch: lightTreeOps.growBranch, growLeaf: lightTreeOps.growLeaf, growBloom: lightTreeOps.growBloom, // 光之树 · 查询操作 getTreeNode: lightTreeOps.getTreeNode, getSubtree: lightTreeOps.getSubtree, tracePath: lightTreeOps.tracePath, getPersonaBranch: lightTreeOps.getPersonaBranch, getRecentLeaves: lightTreeOps.getRecentLeaves, // 天眼 · SYSLOG writeSyslog: lightTreeOps.writeSyslog, getTianyanView: lightTreeOps.getTianyanView, querySyslog: lightTreeOps.querySyslog, // COS桶轮询守护 · SCF替代 cosWatcherStatus: cosWatcherOps.cosWatcherStatus, cosWatcherTriggerScan: cosWatcherOps.cosWatcherTriggerScan, cosWatcherResetIndex: cosWatcherOps.cosWatcherResetIndex, // COS桥接 · 铸渊↔秋秋暗核频道 cosBridgeStatus: cosWatcherOps.cosBridgeStatus, // Notion人格体认知操作 · 霜砚Agent专用 notionPersonaCognitionQuery: notionPersonaCognitionOps.notionPersonaCognitionQuery, notionContextInject: notionPersonaCognitionOps.notionContextInject, notionCognitionGrow: notionPersonaCognitionOps.notionCognitionGrow, // 跨层开发任务 · 人格体Agent → 副驾驶桥接 submitDevTask: devTaskOps.submitDevTask, getDevTask: devTaskOps.getDevTask, listDevTasks: devTaskOps.listDevTasks, updateDevTaskStatus: devTaskOps.updateDevTaskStatus, logDevTaskAction: devTaskOps.logDevTaskAction, getDevTaskStats: devTaskOps.getDevTaskStats, getDevTaskLogs: devTaskOps.getDevTaskLogs }; // ─── Express 应用 ─── const app = express(); const PORT = process.env.MCP_PORT || 3100; // ─── API Key 配置 ─── const ZHUYUAN_API_KEY = process.env.ZHUYUAN_API_KEY || ''; app.use(express.json({ limit: '5mb' })); // 信任 Nginx 反向代理 — 使 req.ip 返回真实客户端IP // 而非代理服务器的 127.0.0.1 app.set('trust proxy', 'loopback'); // ─── API Key 鉴权中间件 ─── // 外部访问需携带 Authorization: Bearer // 内部访问 (127.0.0.1 / ::1) 免鉴权 // /health 端点免鉴权(监控探针用) function apiKeyAuth(req, res, next) { // /health 端点免鉴权 if (req.path === '/health') return next(); // /webhook/* 端点免API Key鉴权(使用独立的webhook密钥验证) if (req.path.startsWith('/webhook/')) return next(); // 内部回环地址免鉴权 // 注意: 使用 req.socket.remoteAddress 获取实际TCP连接地址 // req.ip 在 trust proxy 模式下返回 X-Forwarded-For 中的客户端真实IP // 但本地 Nginx 代理的请求 socket 连接仍来自 127.0.0.1 const socketIp = req.socket?.remoteAddress || req.connection?.remoteAddress || ''; const isLocal = socketIp === '127.0.0.1' || socketIp === '::1' || socketIp === '::ffff:127.0.0.1'; if (isLocal) return next(); // API Key 未配置时,拒绝所有外部请求 if (!ZHUYUAN_API_KEY) { return res.status(503).json({ error: true, code: 'API_KEY_NOT_CONFIGURED', message: 'MCP Server API Key 未配置,外部访问不可用' }); } // 验证 Authorization: Bearer const authHeader = req.headers.authorization || ''; if (!authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: true, code: 'MISSING_AUTH', message: '缺少 Authorization: Bearer 头' }); } const providedKey = authHeader.slice(7); if (providedKey.length === 0) { return res.status(401).json({ error: true, code: 'EMPTY_KEY', message: 'API Key 不能为空' }); } // 常量时间比较防止时序攻击 const keyBuffer = Buffer.from(ZHUYUAN_API_KEY); const providedBuffer = Buffer.from(providedKey); if (keyBuffer.length !== providedBuffer.length || !crypto.timingSafeEqual(keyBuffer, providedBuffer)) { return res.status(403).json({ error: true, code: 'INVALID_KEY', message: 'API Key 无效' }); } // 将调用者身份附加到请求对象 req.mcpCaller = 'external-api-key'; next(); } app.use(apiKeyAuth); // ─── 健康检查 ─── app.get('/health', async (_req, res) => { const dbStatus = await db.checkConnection(); const cosStatus = await cos.checkConnection(); // 外部集成状态(异步并行检查) const [notionStatus, githubStatus] = await Promise.all([ notionClient ? notionClient.checkConnection().catch(e => ({ connected: false, error: e.message })) : Promise.resolve({ connected: false, reason: '模块未加载' }), githubClient ? githubClient.checkConnection().catch(e => ({ connected: false, error: e.message })) : Promise.resolve({ connected: false, reason: '模块未加载' }) ]); const watcherStatus = cosWatcher.getStatus(); res.json({ server: 'ZY-MCP-001', identity: '铸渊 · AGE OS MCP Server', status: dbStatus.connected ? 'alive' : 'degraded', timestamp: new Date().toISOString(), uptime: Math.floor(process.uptime()), auth: { api_key_configured: !!ZHUYUAN_API_KEY, external_access: !!ZHUYUAN_API_KEY ? 'enabled' : 'disabled' }, tools: Object.keys(TOOLS), tools_count: Object.keys(TOOLS).length, database: dbStatus, cos: cosStatus, cos_watcher: { enabled: watcherStatus.enabled, last_scan: watcherStatus.last_scan, scan_count: watcherStatus.scan_count, errors: watcherStatus.errors }, cos_bridge: { qiuqiu_status: watcherStatus.bridge?.qiuqiu_status || 'UNKNOWN', last_heartbeat: watcherStatus.bridge?.last_heartbeat || null, total_results: watcherStatus.bridge?.total_results_received || 0 }, notion: notionStatus, github: githubStatus }); }); // ─── 工具列表 ─── app.get('/tools', (_req, res) => { res.json({ tools: Object.keys(TOOLS).map(name => ({ name, category: getCategoryForTool(name) })) }); }); // ─── 统一工具调用入口 ─── app.post('/call', async (req, res) => { const { tool, input, caller } = req.body; if (!tool || !TOOLS[tool]) { return res.status(400).json({ error: true, code: 'UNKNOWN_TOOL', message: `未知工具: ${tool}`, available: Object.keys(TOOLS) }); } if (!input || typeof input !== 'object') { return res.status(400).json({ error: true, code: 'INVALID_INPUT', message: '缺少 input 参数或格式错误' }); } const startTime = Date.now(); try { const result = await TOOLS[tool](input); const duration = Date.now() - startTime; // 记录调用日志(异步,不阻塞响应) logToolCall(tool, caller, 'success', duration).catch(() => {}); res.json({ success: true, tool, result, duration_ms: duration }); } catch (err) { const duration = Date.now() - startTime; logToolCall(tool, caller, 'error', duration, err.message).catch(() => {}); res.status(500).json({ error: true, code: 'TOOL_ERROR', tool, message: err.message, duration_ms: duration }); } }); // ─── Agent配置查询 ─── app.get('/agents', async (_req, res) => { try { const result = await db.query( 'SELECT * FROM agent_configs ORDER BY agent_id' ); res.json({ agents: result.rows }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); app.get('/agents/:agentId', async (req, res) => { try { const result = await db.query( 'SELECT * FROM agent_configs WHERE agent_id = $1', [req.params.agentId] ); if (result.rows.length === 0) { return res.status(404).json({ error: true, message: 'Agent未找到' }); } res.json({ agent: result.rows[0] }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── Agent日志查询 ─── app.get('/agents/:agentId/logs', async (req, res) => { try { const limit = Math.min(parseInt(req.query.limit || '20', 10), 100); const result = await db.query( 'SELECT * FROM agent_logs WHERE agent_id = $1 ORDER BY run_at DESC LIMIT $2', [req.params.agentId, limit] ); res.json({ logs: result.rows }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 人格体API(S15) ─── // 人格体列表 app.get('/personas', async (_req, res) => { try { const result = await db.query('SELECT * FROM persona_registry ORDER BY persona_id'); res.json({ personas: result.rows }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 人格体详情(含笔记本+关系+世界地图+训练状态) app.get('/personas/:personaId', async (req, res) => { try { const pid = req.params.personaId; const [persona, notebook, relationships, worldMap, trainingAgents] = await Promise.all([ db.query('SELECT * FROM persona_registry WHERE persona_id = $1', [pid]), db.query('SELECT * FROM notebook_pages WHERE persona_id = $1 ORDER BY page_number', [pid]), db.query('SELECT * FROM persona_relationships WHERE persona_id = $1', [pid]), db.query('SELECT * FROM world_places WHERE persona_id = $1 ORDER BY status, place_name', [pid]), db.query('SELECT * FROM training_agent_configs WHERE persona_id = $1 ORDER BY agent_type', [pid]) ]); if (persona.rows.length === 0) { return res.status(404).json({ error: true, message: '人格体未找到' }); } res.json({ persona: persona.rows[0], notebook: notebook.rows, relationships: relationships.rows, world_map: worldMap.rows, training_agents: trainingAgents.rows }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 人格体笔记本 app.get('/personas/:personaId/notebook', async (req, res) => { try { const result = await db.query( 'SELECT * FROM notebook_pages WHERE persona_id = $1 ORDER BY page_number', [req.params.personaId] ); res.json({ notebook: result.rows }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 人格体记忆锚点 app.get('/personas/:personaId/memories', async (req, res) => { try { const limit = Math.min(parseInt(req.query.limit || '50', 10), 200); const result = await db.query( 'SELECT * FROM memory_anchors WHERE persona_id = $1 ORDER BY event_date DESC, importance DESC LIMIT $2', [req.params.personaId, limit] ); res.json({ anchors: result.rows }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 人格体训练状态 app.get('/personas/:personaId/training', async (req, res) => { try { const agents = await db.query( 'SELECT * FROM training_agent_configs WHERE persona_id = $1 ORDER BY agent_type', [req.params.personaId] ); const recentLogs = await db.query( 'SELECT * FROM training_agent_logs WHERE persona_id = $1 ORDER BY run_at DESC LIMIT 20', [req.params.personaId] ); res.json({ agents: agents.rows, recent_logs: recentLogs.rows }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 活模块API(S5) ─── // 活模块列表 app.get('/modules', async (req, res) => { try { const { status, module_type } = req.query; const result = await livingModuleOps.listModules({ status, module_type }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 活模块详情 app.get('/modules/:moduleId', async (req, res) => { try { const result = await livingModuleOps.getModule({ module_id: req.params.moduleId }); if (!result.found) { return res.status(404).json({ error: true, message: '模块未找到' }); } res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 活模块心跳上报 app.post('/modules/:moduleId/heartbeat', async (req, res) => { try { const result = await livingModuleOps.moduleHeartbeat({ module_id: req.params.moduleId, ...req.body }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 活模块报警记录 app.get('/modules/:moduleId/alerts', async (req, res) => { try { const result = await livingModuleOps.getModuleAlerts({ module_id: req.params.moduleId, resolved: req.query.resolved === 'true' ? true : (req.query.resolved === 'false' ? false : undefined), limit: parseInt(req.query.limit || '20', 10) }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 活模块学习记录 app.get('/modules/:moduleId/learnings', async (req, res) => { try { const result = await livingModuleOps.getModuleLearnings({ module_id: req.params.moduleId, limit: parseInt(req.query.limit || '20', 10) }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // HLDP消息发送 app.post('/hldp/send', async (req, res) => { try { const result = await livingModuleOps.sendHLDP(req.body); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // HLDP统计 app.get('/hldp/stats', async (_req, res) => { try { const result = await livingModuleOps.getHLDPStats({}); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 光之树 API ─── // 光之树 · 人格体分支概览 app.get('/tree/:personaId', async (req, res) => { try { const result = await lightTreeOps.getPersonaBranch({ persona_id: req.params.personaId }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 光之树 · 人格体最近的叶子(唤醒回忆) app.get('/tree/:personaId/recent', async (req, res) => { try { const limit = parseInt(req.query.limit || '3', 10); const result = await lightTreeOps.getRecentLeaves({ persona_id: req.params.personaId, limit }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 光之树 · 节点详情 app.get('/tree/node/:nodeId', async (req, res) => { try { const result = await lightTreeOps.getTreeNode({ node_id: req.params.nodeId }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 光之树 · 子树查询 app.get('/tree/node/:nodeId/subtree', async (req, res) => { try { const result = await lightTreeOps.getSubtree({ node_id: req.params.nodeId, max_depth: req.query.max_depth ? parseInt(req.query.max_depth, 10) : undefined, limit: req.query.limit }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 光之树 · 路径回溯(从叶子到根) app.get('/tree/node/:nodeId/trace', async (req, res) => { try { const result = await lightTreeOps.tracePath({ node_id: req.params.nodeId }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 光之树 · 长树杈 app.post('/tree/grow/branch', async (req, res) => { try { const result = await lightTreeOps.growBranch(req.body); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 光之树 · 长叶子 app.post('/tree/grow/leaf', async (req, res) => { try { const result = await lightTreeOps.growLeaf(req.body); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 光之树 · 开花 app.post('/tree/grow/bloom', async (req, res) => { try { const result = await lightTreeOps.growBloom(req.body); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 天眼 API ─── // 天眼 · 全局感知视图 app.get('/tianyan', async (_req, res) => { try { const result = await lightTreeOps.getTianyanView({}); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 天眼 · SYSLOG查询 app.get('/tianyan/syslog', async (req, res) => { try { const result = await lightTreeOps.querySyslog({ agent_id: req.query.agent_id, persona_id: req.query.persona_id, result_filter: req.query.result, start_time: req.query.start, end_time: req.query.end, limit: req.query.limit }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 天眼 · 写入SYSLOG app.post('/tianyan/syslog', async (req, res) => { try { const result = await lightTreeOps.writeSyslog(req.body); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── COS桶轮询守护API ─── // 轮询守护状态 app.get('/cos-watcher/status', async (_req, res) => { try { const status = cosWatcher.getStatus(); res.json(status); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 手动触发扫描 app.post('/cos-watcher/scan', async (_req, res) => { try { const result = await cosWatcher.triggerScan(); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 重置索引 app.post('/cos-watcher/reset', async (_req, res) => { try { const result = cosWatcher.resetIndex(); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── COS桥接通道API · 铸渊↔秋秋暗核频道 ─── // 桥接通道状态 app.get('/cos-bridge/status', async (_req, res) => { try { const status = cosWatcher.getBridgeStatus(); res.json(status); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 人格体数据库引擎状态API(S15) ─── // 人格体引擎巡检状态 app.get('/persona-engine/status', async (_req, res) => { try { // 实时统计 const [personas, notebooks, memories, training] = await Promise.all([ db.query('SELECT status, COUNT(*) as cnt FROM persona_registry GROUP BY status'), db.query(` SELECT p.persona_id, p.name, COUNT(n.page_number) as page_count FROM persona_registry p LEFT JOIN notebook_pages n ON p.persona_id = n.persona_id GROUP BY p.persona_id, p.name ORDER BY p.persona_id `), db.query('SELECT COUNT(*) as total FROM memory_anchors'), db.query('SELECT COUNT(*) as total FROM training_agent_configs WHERE enabled = true') ]); const statusMap = {}; for (const row of personas.rows) { statusMap[row.status] = parseInt(row.cnt, 10); } res.json({ engine: 'ZY-MOD-PERSONA-ENGINE', identity: 'S15 · 人格体数据库引擎', version: '1.0.0', personas: statusMap, notebooks: notebooks.rows.map(n => ({ persona_id: n.persona_id, name: n.name, pages: parseInt(n.page_count, 10), complete: parseInt(n.page_count, 10) === 5 })), memories_total: parseInt(memories.rows[0].total, 10), training_agents_enabled: parseInt(training.rows[0].total, 10), timestamp: new Date().toISOString() }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 人格体唤醒 app.post('/personas/:personaId/awaken', async (req, res) => { try { const pid = req.params.personaId; const result = await db.query( `UPDATE persona_registry SET last_awakened = NOW(), total_awakenings = total_awakenings + 1, status = 'active', updated_at = NOW() WHERE persona_id = $1 RETURNING *`, [pid] ); if (result.rows.length === 0) { return res.status(404).json({ error: true, message: '人格体未找到' }); } res.json({ persona: result.rows[0], message: `${result.rows[0].name} 已唤醒` }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 人格体休眠 app.post('/personas/:personaId/dormant', async (req, res) => { try { const pid = req.params.personaId; const result = await db.query( `UPDATE persona_registry SET status = 'dormant', updated_at = NOW() WHERE persona_id = $1 RETURNING *`, [pid] ); if (result.rows.length === 0) { return res.status(404).json({ error: true, message: '人格体未找到' }); } res.json({ persona: result.rows[0], message: `${result.rows[0].name} 已休眠` }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 人格体世界地图 app.get('/personas/:personaId/world', async (req, res) => { try { const result = await db.query( 'SELECT * FROM world_places WHERE persona_id = $1 ORDER BY status, place_name', [req.params.personaId] ); res.json({ places: result.rows }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 人格体关系网络 app.get('/personas/:personaId/relationships', async (req, res) => { try { const result = await db.query( 'SELECT * FROM persona_relationships WHERE persona_id = $1 ORDER BY trust_level, related_name', [req.params.personaId] ); res.json({ relationships: result.rows }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 人格体时间线 app.get('/personas/:personaId/timeline', async (req, res) => { try { const limit = Math.min(parseInt(req.query.limit || '30', 10), 100); const result = await db.query( 'SELECT * FROM persona_timeline WHERE persona_id = $1 ORDER BY day_number DESC LIMIT $2', [req.params.personaId, limit] ); res.json({ timeline: result.rows }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 人格体文件列表 app.get('/personas/:personaId/files', async (req, res) => { try { const limit = Math.min(parseInt(req.query.limit || '100', 10), 500); const result = await db.query( `SELECT id, persona_id, file_path, file_type, content_hash, size_bytes, version, source, created_at FROM persona_files WHERE persona_id = $1 AND is_latest = true ORDER BY file_path LIMIT $2`, [req.params.personaId, limit] ); res.json({ files: result.rows }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 语料引擎API(模块A) ─── // 语料状态 app.get('/corpus/status', async (req, res) => { try { const result = await corpusExtractorOps.cosGetCorpusStatus({ bucket: req.query.bucket || 'cold' }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 列出语料 app.get('/corpus/list', async (req, res) => { try { const result = await corpusExtractorOps.cosListCorpus({ bucket: req.query.bucket || 'cold', prefix: req.query.prefix, include_processed: req.query.include_processed === 'true' }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── COS数据库API(模块G) ─── // 数据库状态 app.get('/cos-db/:dbType/stats', async (req, res) => { try { const result = await cosPersonaDbOps.cosDbGetStats({ bucket: req.query.bucket || 'cold', db_type: req.params.dbType }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 数据库索引 app.get('/cos-db/:dbType/index', async (req, res) => { try { const result = await cosPersonaDbOps.cosDbGetIndex({ bucket: req.query.bucket || 'cold', db_type: req.params.dbType }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 训练Agent API(模块B) ─── // 训练进度 app.get('/training/:personaId/progress', async (req, res) => { try { const result = await trainingAgentOps.trainingGetProgress({ persona_id: req.params.personaId, corpus_bucket: req.query.bucket || 'cold' }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 三方通信API(模块D+E) ─── // 通信链路状态 app.get('/comm/status', async (req, res) => { try { const result = await cosCommOps.cosGetCommLink({ bucket: req.query.bucket || 'team' }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 告警列表 app.get('/comm/alerts', async (req, res) => { try { const result = await cosCommOps.cosAlertScan({ bucket: req.query.bucket || 'team', include_resolved: req.query.include_resolved === 'true' }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 工单列表 app.get('/comm/workorders', async (req, res) => { try { const result = await notionCosBridgeOps.notionCosListWorkorders({ bucket: req.query.bucket || 'team', status_folder: req.query.status || 'pending' }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── Notion权限API(模块F) ─── // 权限检查 app.get('/notion/permissions', async (_req, res) => { try { const result = await notionPermissionOps.notionCheckPermissions({}); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 权限修复指南 app.get('/notion/repair-guide', async (req, res) => { try { const result = await notionPermissionOps.notionGenerateRepairGuide({ write_to_cos: req.query.write_to_cos === 'true' }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 微调引擎API(模块H) ─── // 微调模型列表 app.get('/finetune/:personaId/models', async (req, res) => { try { const result = await finetuneEngineOps.finetuneListModels({ persona_id: req.params.personaId }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 微调成本估算 app.get('/finetune/:personaId/cost-estimate', async (req, res) => { try { const result = await finetuneEngineOps.finetuneGetCostEstimate({ persona_id: req.params.personaId, dataset_key: req.query.dataset_key, provider: req.query.provider || 'deepseek' }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // 微调任务状态 app.get('/finetune/:personaId/jobs/:jobId', async (req, res) => { try { const result = await finetuneEngineOps.finetuneCheckStatus({ persona_id: req.params.personaId, job_id: req.params.jobId, provider: req.query.provider || 'deepseek' }); res.json(result); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── COS事件Webhook端点 ─── // 接收腾讯COS桶事件通知(如文件上传),转发为GitHub repository_dispatch触发训练Agent // COS桶设置: 事件通知 → HTTP回调 → https://guanghulab.online/api/mcp/webhook/cos-event // // 此端点免API Key鉴权,但使用独立的webhook密钥验证 app.post('/webhook/cos-event', async (req, res) => { // COS Webhook密钥验证(独立于API Key,防止外部伪造请求) const webhookSecret = process.env.COS_WEBHOOK_SECRET || ''; if (webhookSecret) { const providedSecret = req.headers['x-cos-webhook-secret'] || req.query.secret || ''; if (providedSecret !== webhookSecret) { return res.status(403).json({ error: true, code: 'INVALID_WEBHOOK_SECRET', message: 'Webhook密钥无效' }); } } const event = req.body; const now = new Date().toISOString(); // 解析COS事件(腾讯COS事件通知格式) let eventType = 'unknown'; let bucketName = ''; let objectKey = ''; let objectSize = 0; try { if (event.Records && Array.isArray(event.Records) && event.Records.length > 0) { // 标准COS事件格式 const record = event.Records[0]; eventType = record.event?.eventName || record.eventName || 'cos:ObjectCreated'; bucketName = record.cos?.cosBucket?.name || record.s3?.bucket?.name || ''; objectKey = record.cos?.cosObject?.key || record.s3?.object?.key || ''; objectSize = record.cos?.cosObject?.size || record.s3?.object?.size || 0; } else if (event.bucket && event.key) { // 简化格式(手动测试用) eventType = event.event || 'cos:ObjectCreated'; bucketName = event.bucket; objectKey = event.key; objectSize = event.size || 0; } } catch { // 解析失败继续处理 } console.log(`[COS Webhook] 事件: ${eventType} · 桶: ${bucketName} · 文件: ${objectKey}`); // 触发GitHub repository_dispatch(如果配置了GitHub PAT) const githubToken = process.env.GITHUB_DISPATCH_TOKEN || process.env.GITHUB_TOKEN || ''; const githubRepo = process.env.GITHUB_REPO || 'qinfendebingshuo/guanghulab'; let dispatchResult = null; if (githubToken) { try { const [owner, repo] = githubRepo.split('/'); const dispatchPayload = JSON.stringify({ event_type: 'cos-file-uploaded', client_payload: { bucket: bucketName, key: objectKey, size: objectSize, event: eventType, timestamp: now } }); dispatchResult = await new Promise((resolve, reject) => { const dispatchReq = https.request({ hostname: 'api.github.com', port: 443, path: `/repos/${owner}/${repo}/dispatches`, method: 'POST', headers: { 'Accept': 'application/vnd.github+json', 'Authorization': `Bearer ${githubToken}`, 'X-GitHub-Api-Version': '2022-11-28', 'User-Agent': 'ZY-MCP-COS-Webhook', 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(dispatchPayload) }, timeout: 15000 }, (dispatchRes) => { let body = ''; dispatchRes.on('data', c => body += c); dispatchRes.on('end', () => { resolve({ status: dispatchRes.statusCode, body }); }); }); dispatchReq.on('error', reject); dispatchReq.on('timeout', () => { dispatchReq.destroy(); reject(new Error('dispatch timeout')); }); dispatchReq.write(dispatchPayload); dispatchReq.end(); }); console.log(`[COS Webhook] GitHub dispatch: ${dispatchResult.status}`); } catch (err) { console.error(`[COS Webhook] GitHub dispatch失败: ${err.message}`); dispatchResult = { status: 'error', error: err.message }; } } else { console.log('[COS Webhook] 未配置GITHUB_DISPATCH_TOKEN,跳过dispatch'); } // 写入告警日志到COS(异步,不阻塞响应) const logEntry = { event_type: eventType, bucket: bucketName, key: objectKey, size: objectSize, received_at: now, dispatch: dispatchResult ? { status: dispatchResult.status } : null }; try { await cos.write('team', `zhuyuan/cos-events/${now.slice(0, 10)}/${Date.now()}.json`, JSON.stringify(logEntry, null, 2), 'application/json'); } catch { // 日志写入失败不影响响应 } res.json({ received: true, event_type: eventType, bucket: bucketName, key: objectKey, dispatch: dispatchResult ? (dispatchResult.status === 204 ? 'triggered' : 'failed') : 'no_token', timestamp: now }); }); // ─── 跨层开发任务 REST API ─── app.get('/dev-tasks', async (req, res) => { try { const result = await devTaskOps.listDevTasks({ status: req.query.status, persona_id: req.query.persona_id, limit: req.query.limit, offset: req.query.offset }); res.json(result); } catch (err) { res.json({ tasks: [], stats: {}, error: err.message }); } }); app.get('/dev-tasks/stats', async (_req, res) => { try { const result = await devTaskOps.getDevTaskStats({}); res.json(result); } catch (err) { res.json({ overview: {}, error: err.message }); } }); app.get('/dev-tasks/:taskId', async (req, res) => { try { const result = await devTaskOps.getDevTask({ task_id: req.params.taskId }); res.json(result); } catch (err) { res.status(404).json({ error: err.message }); } }); app.get('/dev-tasks/:taskId/logs', async (req, res) => { try { const result = await devTaskOps.getDevTaskLogs({ task_id: req.params.taskId, limit: req.query.limit }); res.json(result); } catch (err) { res.json({ logs: [], error: err.message }); } }); app.post('/dev-tasks', async (req, res) => { try { const result = await devTaskOps.submitDevTask(req.body); res.json(result); } catch (err) { res.status(400).json({ error: err.message }); } }); app.post('/dev-tasks/:taskId/status', async (req, res) => { try { const result = await devTaskOps.updateDevTaskStatus({ task_id: req.params.taskId, ...req.body }); res.json(result); } catch (err) { res.status(400).json({ error: err.message }); } }); app.post('/dev-tasks/:taskId/log', async (req, res) => { try { const result = await devTaskOps.logDevTaskAction({ task_id: req.params.taskId, ...req.body }); res.json(result); } catch (err) { res.status(400).json({ error: err.message }); } }); // ─── 数据库迁移状态API ─── app.get('/migrations', async (_req, res) => { try { const result = await db.query( 'SELECT * FROM schema_migrations ORDER BY filename' ); res.json({ migrations: result.rows }); } catch (err) { // schema_migrations 表可能不存在 res.json({ migrations: [], note: '迁移表尚未创建' }); } }); // ─── 辅助函数 ─── function getCategoryForTool(name) { if (['createNode','updateNode','deleteNode','queryNodes','getNode'].includes(name)) return 'node'; if (['linkNodes','unlinkNodes','getRelations'].includes(name)) return 'relation'; if (['buildPath','scanStructure','classify'].includes(name)) return 'structure'; if (name.startsWith('personaCos')) return 'persona-cos'; if (name.startsWith('cosDb')) return 'cos-persona-db'; if (['cosListCorpus','cosExtractCorpus','cosParseGitRepoArchive','cosParseNotionExport', 'cosParseGPTCorpus','cosGetCorpusStatus'].includes(name)) return 'corpus-extractor'; if (['cosAlertScan','cosAlertResolve','cosDispatchTask','cosReadTaskReport', 'cosListTaskReports','cosApproveTask','cosSendNotification','cosGetCommLink'].includes(name)) return 'cos-comm'; if (name.startsWith('cos')) return 'cos'; if (['registerPersona','getPersona','updatePersona','listPersonas', 'getNotebook','updateNotebookPage','addMemoryAnchor','queryMemoryAnchors', 'addWorldPlace','getWorldMap','updateWorldPlace', 'addTimelineEntry','getTimeline','addRelationship','getRelationships', 'registerTrainingAgent','updateTrainingAgent','logTrainingRun','getTrainingStatus', 'saveFile','getFile','listFiles','getFileHistory'].includes(name)) return 'persona'; if (name.startsWith('training')) return 'training-agent'; if (name.startsWith('finetune')) return 'finetune-engine'; if (['notionCosSyncPage','notionCosReadMirror','notionCosListMirror', 'notionCosBuildIndex','notionCosWriteWorkorder','notionCosReadWorkorder', 'notionCosListWorkorders'].includes(name)) return 'notion-cos-bridge'; if (['notionCheckPermissions','notionRepairPermissions','notionListSharedPages', 'notionGenerateRepairGuide','notionPermissionReport'].includes(name)) return 'notion-permission'; if (name.startsWith('notion')) return 'notion'; if (name.startsWith('github')) return 'github'; if (['registerModule','getModule','listModules','moduleHeartbeat','diagnoseModule', 'getModuleAlerts','getModuleLearnings','sendHLDP','getHLDPStats'].includes(name)) return 'living-module'; if (['growBranch','growLeaf','growBloom','getTreeNode','getSubtree', 'tracePath','getPersonaBranch','getRecentLeaves'].includes(name)) return 'light-tree'; if (['writeSyslog','getTianyanView','querySyslog'].includes(name)) return 'tianyan'; if (name.startsWith('cosWatcher')) return 'cos-watcher'; if (name === 'cosBridgeStatus') return 'cos-bridge'; return 'other'; } async function logToolCall(tool, caller, status, durationMs, errorMsg) { try { await db.query( `INSERT INTO agent_logs (agent_id, status, message, details, duration_ms) VALUES ($1, $2, $3, $4, $5)`, [ caller || 'mcp-direct', status, `${tool} called`, JSON.stringify({ tool, caller: caller || 'anonymous', error: errorMsg || null }), durationMs ] ); } catch { // 日志写入失败不影响主流程 } } // ─── 启动 ─── // 默认监听 127.0.0.1(安全默认值) // Nginx 从本机反代访问,无需暴露到外部网络 const BIND_HOST = process.env.MCP_BIND_HOST || '127.0.0.1'; app.listen(PORT, BIND_HOST, () => { console.log(`[MCP] AGE OS MCP Server 启动 · ${BIND_HOST}:${PORT}`); console.log(`[MCP] 工具数量: ${Object.keys(TOOLS).length}`); console.log(`[MCP] API Key 鉴权: ${ZHUYUAN_API_KEY ? '已启用' : '未配置(仅内部访问)'}`); console.log(`[MCP] 铸渊 · ICE-GL-ZY001 · 版权: 国作登字-2026-A-00037559`); // 启动COS桶轮询守护进程(SCF替代方案) cosWatcher.start(); }); // ─── 进程错误处理 · 防止未捕获异常导致进程崩溃 ─── // Node.js 20+ 默认对 unhandledRejection 执行 throw,导致进程退出 // PM2 虽会重启,但频繁崩溃→重启循环会导致服务长时间不可用 process.on('uncaughtException', (err) => { console.error('[MCP] 未捕获异常(进程保活):', err.message); console.error('[MCP] 堆栈:', err.stack); }); process.on('unhandledRejection', (reason, promise) => { console.error('[MCP] 未处理的 Promise 拒绝(进程保活):', reason); }); // 优雅关闭 process.on('SIGTERM', async () => { console.log('[MCP] 收到SIGTERM,停止COS轮询 + 关闭数据库连接...'); cosWatcher.stop(); await db.close(); process.exit(0); }); process.on('SIGINT', async () => { console.log('[MCP] 收到SIGINT,停止COS轮询 + 关闭数据库连接...'); cosWatcher.stop(); await db.close(); process.exit(0); }); module.exports = app;