2026-05-10 13:12:44 +08:00

1464 lines
51 KiB
JavaScript
Raw Permalink 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.

/**
* ═══════════════════════════════════════════════════════════
* 🧠 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 <ZHUYUAN_API_KEY>
* 免鉴权端点: /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 <ZHUYUAN_API_KEY>
// 内部访问 (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 <key>
const authHeader = req.headers.authorization || '';
if (!authHeader.startsWith('Bearer ')) {
return res.status(401).json({
error: true,
code: 'MISSING_AUTH',
message: '缺少 Authorization: Bearer <API_KEY> 头'
});
}
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 });
}
});
// ─── 人格体APIS15 ───
// 人格体列表
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 });
}
});
// ─── 活模块APIS5 ───
// 活模块列表
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 });
}
});
// ─── 人格体数据库引擎状态APIS15 ───
// 人格体引擎巡检状态
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;