feat: 实现 MCP (Model Context Protocol) 完整功能
主进程: - 新增 mcp-manager.ts: JSON-RPC 2.0 over stdio 通信 - 服务器生命周期管理 (spawn/initialize/handshake/stop) - tools/list 动态工具发现 - tools/call 工具执行 (30 秒超时) - 消息分帧 (newline-delimited JSON) - 优雅清理 (pending 请求超时 + SIGTERM) IPC 通道 (7 个): - mcp:startServer / mcp:stopServer / mcp:stopAll - mcp:callTool / mcp:getTools / mcp:getStatuses / mcp:refreshTools 渲染端: - mcp-client.ts 完整重写: 配置管理 + 服务器生命周期 + 工具定义获取 + 工具执行 - tool-registry.ts: MCP 工具路由 (mcp_ 前缀识别 → executeMCPTool) - settings-modal.ts: MCP 服务器管理 UI (添加/启用/禁用/删除/状态显示) - main.ts: 启动时自动启动已启用的 MCP 服务器 数据流: settings.mcp_servers → startAllMCPServers → initialize → tools/list → getMCPToolDefinitions → Agent Loop → mcp_ 工具调用 → callTool → JSON-RPC → MCP Server
This commit is contained in:
@@ -151,6 +151,78 @@ export function initSettingsModal(): void {
|
||||
logSetting('检查间隔', `${ms / 60000} 分钟`);
|
||||
});
|
||||
|
||||
// ── v5.0 MCP 服务器管理 ──
|
||||
async function renderMCPServerList(): Promise<void> {
|
||||
const { getMCPServers, getMCPServerStatuses, toggleMCPServer, removeMCPServer } = await import('../services/mcp-client.js');
|
||||
const { refreshMCPTools } = await import('../services/tool-registry.js');
|
||||
const container = document.querySelector('#mcpServerList')!;
|
||||
const servers = await getMCPServers();
|
||||
const statuses = await getMCPServerStatuses();
|
||||
|
||||
if (servers.length === 0) {
|
||||
container.innerHTML = '<p class="text-muted" style="margin:4px 0;font-size:11px;">未配置 MCP 服务器。MCP 允许连接外部工具服务。</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = servers.map(s => {
|
||||
const status = statuses.find(st => st.name === s.name);
|
||||
const running = status?.running || false;
|
||||
const toolCount = status?.toolCount || 0;
|
||||
return `<div style="display:flex;align-items:center;gap:8px;padding:4px 0;border-bottom:1px solid var(--border-subtle);">
|
||||
<span style="width:8px;height:8px;border-radius:50%;background:${running ? 'var(--success)' : 'var(--text-muted)'};" title="${running ? '已连接' : '未连接'}"></span>
|
||||
<span style="flex:1;font-weight:500;">${s.name}</span>
|
||||
<span class="text-muted" style="font-size:11px;">${s.command} ${s.args.join(' ')}${running ? ` (${toolCount} 工具)` : ''}</span>
|
||||
<button class="btn btn-sm btn-outline mcp-toggle" data-name="${s.name}" style="padding:2px 8px;font-size:11px;">${s.enabled ? '禁用' : '启用'}</button>
|
||||
<button class="btn btn-sm btn-outline mcp-delete" data-name="${s.name}" style="padding:2px 8px;font-size:11px;color:var(--critical);">删除</button>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
container.querySelectorAll('.mcp-toggle').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const name = (btn as HTMLElement).dataset.name!;
|
||||
await toggleMCPServer(name);
|
||||
await refreshMCPTools();
|
||||
await renderMCPServerList();
|
||||
});
|
||||
});
|
||||
|
||||
container.querySelectorAll('.mcp-delete').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const name = (btn as HTMLElement).dataset.name!;
|
||||
await removeMCPServer(name);
|
||||
await refreshMCPTools();
|
||||
await renderMCPServerList();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelector('#btnAddMCPServer')!.addEventListener('click', async () => {
|
||||
const { addMCPServer } = await import('../services/mcp-client.js');
|
||||
const { refreshMCPTools } = await import('../services/tool-registry.js');
|
||||
const name = (document.querySelector('#inputMCPName') as HTMLInputElement).value.trim();
|
||||
const command = (document.querySelector('#inputMCPCommand') as HTMLInputElement).value.trim();
|
||||
const argsStr = (document.querySelector('#inputMCPArgs') as HTMLInputElement).value.trim();
|
||||
|
||||
if (!name || !command) {
|
||||
showToast('请填写名称和命令', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const args = argsStr ? argsStr.split(/\s+/) : [];
|
||||
await addMCPServer({ name, command, args, enabled: true });
|
||||
await refreshMCPTools();
|
||||
(document.querySelector('#inputMCPName') as HTMLInputElement).value = '';
|
||||
(document.querySelector('#inputMCPCommand') as HTMLInputElement).value = '';
|
||||
(document.querySelector('#inputMCPArgs') as HTMLInputElement).value = '';
|
||||
showToast(`MCP 服务器已添加: ${name}`, 'success');
|
||||
await renderMCPServerList();
|
||||
});
|
||||
|
||||
// 设置面板打开时刷新 MCP 列表
|
||||
document.querySelector('#btnSettings')!.addEventListener('click', () => {
|
||||
setTimeout(renderMCPServerList, 100);
|
||||
});
|
||||
|
||||
// ── v5.0 Cron 定时任务 ──
|
||||
document.querySelector('#btnAddCronTask')!.addEventListener('click', async () => {
|
||||
const name = (document.querySelector('#inputCronName') as HTMLInputElement).value.trim();
|
||||
|
||||
Reference in New Issue
Block a user