jinzd-ai-cli 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +33 -2
- package/dist/index.js +520 -7
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
一个跨平台的 REPL 风格 AI 对话工具,支持多个主流 AI 提供商(Claude、Gemini、DeepSeek、智谱清言、Kimi),
|
|
6
6
|
带有 **AI 工具调用(Agentic)** 能力,支持执行 bash 命令、读写文件、运行交互式程序、流式生成大文档。
|
|
7
7
|
代理支持(`proxy` 配置字段 + 环境变量),Gemini 完整支持(2.5 Pro/Flash,array items schema 修复)。
|
|
8
|
+
**MCP 协议支持**:可接入外部 MCP 服务器,自动发现并注册工具,无缝融入 agentic 循环。
|
|
8
9
|
设计上可扩展至 Electron/Tauri 桌面 GUI。
|
|
9
10
|
|
|
10
11
|
## 技术栈
|
|
@@ -44,7 +45,11 @@ src/
|
|
|
44
45
|
│ ├── dev-state.ts # 开发状态交接(provider/model 切换时快照生成、save/load/clear)
|
|
45
46
|
│ ├── setup-wizard.ts # @inquirer/prompts 首次运行交互式设置
|
|
46
47
|
│ └── commands/
|
|
47
|
-
│ └── index.ts # CommandRegistry +
|
|
48
|
+
│ └── index.ts # CommandRegistry + 17个命令(/help /about /provider /model /clear /session /system /context /status /search /undo /export /tools /plugins /mcp /config /exit)
|
|
49
|
+
├── mcp/
|
|
50
|
+
│ ├── types.ts # MCP 协议类型定义(JSON-RPC、工具 schema、服务器配置)
|
|
51
|
+
│ ├── client.ts # McpClient(单个 MCP 服务器 STDIO 连接,JSON-RPC 通信)
|
|
52
|
+
│ └── manager.ts # McpManager(多服务器管理,工具发现与注册,MCP→Tool 转换)
|
|
48
53
|
└── tools/
|
|
49
54
|
├── types.ts # ToolDefinition / ToolCall / ToolResult / DangerLevel / getDangerLevel
|
|
50
55
|
├── registry.ts # ToolRegistry(注册全部内置工具,共14个)
|
|
@@ -117,6 +122,30 @@ npm run pack:all # 同时打包所有平台
|
|
|
117
122
|
}
|
|
118
123
|
```
|
|
119
124
|
|
|
125
|
+
### MCP 服务器配置
|
|
126
|
+
|
|
127
|
+
在 `config.json` 中声明 `mcpServers` 字段,启动时自动连接、发现工具并注册。格式兼容 Claude Desktop。
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"mcpServers": {
|
|
132
|
+
"filesystem": {
|
|
133
|
+
"command": "npx",
|
|
134
|
+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "D:/projects"],
|
|
135
|
+
"timeout": 30000
|
|
136
|
+
},
|
|
137
|
+
"github": {
|
|
138
|
+
"command": "node",
|
|
139
|
+
"args": ["path/to/github-server.js"],
|
|
140
|
+
"env": { "GITHUB_TOKEN": "ghp_xxx" }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
MCP 工具名格式:`mcp__<serverId>__<toolName>`,如 `mcp__filesystem__read_file`。
|
|
147
|
+
所有 MCP 工具默认 `safe` 级别(用户主动配置即表示信任)。
|
|
148
|
+
|
|
120
149
|
## 环境变量(优先级高于配置文件)
|
|
121
150
|
|
|
122
151
|
```
|
|
@@ -176,6 +205,7 @@ AICLI_NO_STREAM 设为 1 禁用流式输出
|
|
|
176
205
|
| `ask_user` | safe | AI 在 agentic 循环中向用户提问,等待文本回答后继续执行 |
|
|
177
206
|
| `write_todos` | safe | AI 拆解复杂任务为子任务列表,终端实时渲染进度(pending/in_progress/completed) |
|
|
178
207
|
| `google_search` | safe | Google Custom Search API 搜索网页,需配置 API Key + Search Engine ID (cx) |
|
|
208
|
+
| `mcp__*` | safe | MCP 服务器暴露的动态工具(命名格式:`mcp__<serverId>__<toolName>`) |
|
|
179
209
|
|
|
180
210
|
### 危险级别与确认机制
|
|
181
211
|
|
|
@@ -271,8 +301,9 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
271
301
|
- [x] **`write_todos` 工具**(2026-02-23):AI 拆解复杂任务为子任务列表,终端实时渲染进度。参数采用 JSON 字符串(因 ToolParameterSchema 不支持嵌套对象数组),容错处理 AI 直接传数组。`execute()` 内直接 `console.log()` 渲染(绕过 executor 8 行截断),模块级变量保持会话内状态。
|
|
272
302
|
- [x] **Google 搜索集成**(2026-02-23):`google_search` 工具通过 Google Custom Search JSON API 搜索网页。需配置 API Key(`apiKeys['google-search']` 或 `AICLI_API_KEY_GOOGLESEARCH`)和 Search Engine ID(`googleSearchEngineId` 或 `AICLI_GOOGLE_CX`)。自动走全局 proxy,15s 超时,返回 Markdown 格式结果列表。配置向导新增 `Configure Google Search` 入口。
|
|
273
303
|
|
|
304
|
+
- [x] **MCP 协议支持**(2026-02-23):轻量级 MCP 客户端(不依赖 SDK),STDIO 传输 + JSON-RPC 2.0 通信。`src/mcp/` 模块:`types.ts`(协议类型)、`client.ts`(单服务器连接)、`manager.ts`(多服务器管理 + Tool 转换)。配置兼容 Claude Desktop(`config.json` 的 `mcpServers` 字段)。启动自动连接、发现工具并注册到 ToolRegistry,工具名格式 `mcp__<serverId>__<toolName>`。新增 `/mcp` REPL 命令查看连接状态和工具列表。
|
|
305
|
+
|
|
274
306
|
### 下一步待实现
|
|
275
|
-
- [ ] **MCP 协议支持**:Model Context Protocol 扩展生态,接入外部工具
|
|
276
307
|
- [ ] **Agent Skills 系统**:可复用的专业技能包(`.aicli/skills/`)
|
|
277
308
|
|
|
278
309
|
## 已知待改进项
|
package/dist/index.js
CHANGED
|
@@ -74,6 +74,16 @@ var ConfigSchema = z.object({
|
|
|
74
74
|
// API Key 通过 apiKeys['google-search'] 或 AICLI_API_KEY_GOOGLESEARCH 环境变量配置
|
|
75
75
|
// CX 也可通过 AICLI_GOOGLE_CX 环境变量覆盖
|
|
76
76
|
googleSearchEngineId: z.string().optional(),
|
|
77
|
+
// MCP (Model Context Protocol) 服务器配置
|
|
78
|
+
// 声明外部 MCP 服务器,启动时自动连接、发现工具并注册
|
|
79
|
+
// 配置格式兼容 Claude Desktop(command + args + env)
|
|
80
|
+
// 示例:{ "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"] } }
|
|
81
|
+
mcpServers: z.record(z.object({
|
|
82
|
+
command: z.string(),
|
|
83
|
+
args: z.array(z.string()).default([]),
|
|
84
|
+
env: z.record(z.string()).optional(),
|
|
85
|
+
timeout: z.number().default(3e4)
|
|
86
|
+
})).default({}),
|
|
77
87
|
// 插件加载开关(安全控制)
|
|
78
88
|
// 默认 false:不自动加载 ~/.aicli/plugins/ 中的插件文件。
|
|
79
89
|
// 插件以完整 Node.js 权限在主进程中执行(可读写文件、访问网络、执行命令),
|
|
@@ -119,7 +129,8 @@ var EnvLoader = class {
|
|
|
119
129
|
};
|
|
120
130
|
|
|
121
131
|
// src/core/constants.ts
|
|
122
|
-
var VERSION = "0.1.
|
|
132
|
+
var VERSION = "0.1.16";
|
|
133
|
+
var APP_NAME = "ai-cli";
|
|
123
134
|
var CONFIG_DIR_NAME = ".aicli";
|
|
124
135
|
var CONFIG_FILE_NAME = "config.json";
|
|
125
136
|
var HISTORY_DIR_NAME = "history";
|
|
@@ -129,6 +140,10 @@ var MEMORY_FILE_NAME = "memory.md";
|
|
|
129
140
|
var MEMORY_MAX_CHARS = 1e4;
|
|
130
141
|
var DEV_STATE_FILE_NAME = "dev-state.md";
|
|
131
142
|
var DEFAULT_MAX_TOKENS = 8192;
|
|
143
|
+
var MCP_TOOL_PREFIX = "mcp__";
|
|
144
|
+
var MCP_CONNECT_TIMEOUT = 3e4;
|
|
145
|
+
var MCP_CALL_TIMEOUT = 6e4;
|
|
146
|
+
var MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
132
147
|
var AUTHOR = "\u664B\u6B63\u4E1C";
|
|
133
148
|
var AUTHOR_EMAIL = "zhengdong.jin@gmail.com";
|
|
134
149
|
var DESCRIPTION = "\u8DE8\u5E73\u53F0 REPL \u98CE\u683C AI \u5BF9\u8BDD\u5DE5\u5177\uFF0C\u652F\u6301\u591A Provider \u4E0E Agentic \u5DE5\u5177\u8C03\u7528";
|
|
@@ -1417,7 +1432,7 @@ var Renderer = class {
|
|
|
1417
1432
|
console.log(chalk.gray(" Commands : ") + chalk.dim("/help \xB7 /about \xB7 Ctrl+C to exit"));
|
|
1418
1433
|
console.log();
|
|
1419
1434
|
}
|
|
1420
|
-
printAbout(pluginCount = 0) {
|
|
1435
|
+
printAbout(pluginCount = 0, mcpInfo) {
|
|
1421
1436
|
const HR = chalk.dim(" " + "\u2500".repeat(56));
|
|
1422
1437
|
const label = (s) => chalk.gray(` ${s.padEnd(6)}`);
|
|
1423
1438
|
const tool = (name, desc) => chalk.cyan(` ${name.padEnd(22)}`) + chalk.dim(desc);
|
|
@@ -1434,8 +1449,12 @@ var Renderer = class {
|
|
|
1434
1449
|
console.log(chalk.dim(" DeepSeek \xB7 Kimi (Moonshot) \xB7 Claude (Anthropic)"));
|
|
1435
1450
|
console.log(chalk.dim(" Gemini (Google) \xB7 \u667A\u8C31\u6E05\u8A00 \xB7 \u81EA\u5B9A\u4E49 OpenAI \u517C\u5BB9"));
|
|
1436
1451
|
console.log(HR);
|
|
1437
|
-
const
|
|
1438
|
-
const
|
|
1452
|
+
const mcpToolCount = mcpInfo?.tools ?? 0;
|
|
1453
|
+
const toolTotal = 14 + pluginCount + mcpToolCount;
|
|
1454
|
+
const extras = [];
|
|
1455
|
+
if (pluginCount > 0) extras.push(`${pluginCount} \u4E2A\u63D2\u4EF6`);
|
|
1456
|
+
if (mcpToolCount > 0) extras.push(`${mcpToolCount} \u4E2A MCP`);
|
|
1457
|
+
const toolLabel = extras.length > 0 ? `\uFF0C\u542B ${extras.join(" + ")}` : "\uFF0C\u63D2\u4EF6/MCP \u53EF\u6269\u5C55";
|
|
1439
1458
|
console.log(chalk.gray(` Agentic \u5DE5\u5177\uFF08${toolTotal}\u4E2A${toolLabel}\uFF09\uFF1A`));
|
|
1440
1459
|
console.log(tool("bash", "\u6267\u884C Shell \u547D\u4EE4\uFF08PowerShell/bash\uFF0CWindows \u5F3A\u5236 UTF-8\uFF09"));
|
|
1441
1460
|
console.log(tool("read_file", "\u8BFB\u53D6\u6587\u4EF6\u5185\u5BB9"));
|
|
@@ -1452,10 +1471,10 @@ var Renderer = class {
|
|
|
1452
1471
|
console.log(tool("ask_user", "\u5411\u7528\u6237\u63D0\u95EE\u5E76\u7B49\u5F85\u56DE\u7B54\uFF08agentic \u5FAA\u73AF\u4E2D\u8BF7\u6C42\u6F84\u6E05\uFF09"));
|
|
1453
1472
|
console.log(tool("write_todos", "\u62C6\u89E3\u4EFB\u52A1\u4E3A\u5B50\u4EFB\u52A1\u5217\u8868\uFF0C\u5B9E\u65F6\u663E\u793A\u8FDB\u5EA6"));
|
|
1454
1473
|
console.log(HR);
|
|
1455
|
-
console.log(chalk.gray(" REPL \u547D\u4EE4\
|
|
1474
|
+
console.log(chalk.gray(" REPL \u547D\u4EE4\uFF0817\u4E2A\uFF09\uFF1A"));
|
|
1456
1475
|
console.log(chalk.dim(" /help /about /provider /model /clear /session"));
|
|
1457
1476
|
console.log(chalk.dim(" /system /context /status /search /undo /export"));
|
|
1458
|
-
console.log(chalk.dim(" /tools /plugins /config /exit"));
|
|
1477
|
+
console.log(chalk.dim(" /tools /plugins /mcp /config /exit"));
|
|
1459
1478
|
console.log(HR);
|
|
1460
1479
|
console.log(chalk.gray(" \u4E3B\u8981\u7279\u6027\uFF1A"));
|
|
1461
1480
|
console.log(feat("Agentic \u5FAA\u73AF\uFF08\u6700\u591A 20 \u8F6E\u5DE5\u5177\u8C03\u7528\uFF0C\u6700\u7EC8\u56DE\u7B54\u6D41\u5F0F\u8F93\u51FA\uFF09"));
|
|
@@ -1466,6 +1485,7 @@ var Renderer = class {
|
|
|
1466
1485
|
console.log(feat("\u6587\u4EF6\u64CD\u4F5C\u64A4\u9500\uFF08/undo\uFF0C\u652F\u6301 write_file / edit_file\uFF09"));
|
|
1467
1486
|
console.log(feat("Thinking \u6A21\u5F0F\u6298\u53E0\uFF08<think> \u5757\u81EA\u52A8\u6298\u53E0\uFF0CGLM-5 \u7B49\uFF09"));
|
|
1468
1487
|
console.log(feat("Token \u7528\u91CF\u8FFD\u8E2A\uFF08\u6BCF\u6B21\u56DE\u590D + session \u7D2F\u8BA1\uFF0C\u652F\u6301 Gemini/Claude/DeepSeek \u7B49\uFF09"));
|
|
1488
|
+
console.log(feat("MCP \u534F\u8BAE\u652F\u6301\uFF1A\u63A5\u5165\u5916\u90E8 MCP \u670D\u52A1\u5668\u5DE5\u5177\uFF08config.json mcpServers \u914D\u7F6E\uFF09"));
|
|
1469
1489
|
console.log(feat("\u63D2\u4EF6\u7CFB\u7EDF\uFF1A~/.aicli/plugins/*.js \u81EA\u5B9A\u4E49\u5DE5\u5177\uFF08\u9700 allowPlugins:true \u542F\u7528\uFF0C\u9ED8\u8BA4\u5173\u95ED\uFF09"));
|
|
1470
1490
|
console.log(feat("\u72EC\u7ACB\u53EF\u6267\u884C\u6587\u4EF6\u6253\u5305\uFF08~56MB\uFF0C\u65E0\u9700 Node.js \u73AF\u5883\uFF09"));
|
|
1471
1491
|
console.log();
|
|
@@ -1725,6 +1745,7 @@ function createDefaultCommands() {
|
|
|
1725
1745
|
" /export [md|json] [file] - Export session to file (default: auto-named .md)",
|
|
1726
1746
|
" /tools - List all AI tools available",
|
|
1727
1747
|
" /plugins - Show plugin directory and loaded plugins",
|
|
1748
|
+
" /mcp - Show MCP server connections and tools",
|
|
1728
1749
|
" /config - Open configuration wizard (API keys, proxy, etc.)",
|
|
1729
1750
|
" /exit - Exit"
|
|
1730
1751
|
] : [];
|
|
@@ -1738,7 +1759,9 @@ function createDefaultCommands() {
|
|
|
1738
1759
|
description: "Show information about ai-cli and its author",
|
|
1739
1760
|
usage: "/about",
|
|
1740
1761
|
execute(_args, ctx) {
|
|
1741
|
-
ctx.
|
|
1762
|
+
const manager = ctx.getMcpManager();
|
|
1763
|
+
const mcpInfo = manager ? { servers: manager.getConnectedCount(), tools: manager.getTotalToolCount() } : void 0;
|
|
1764
|
+
ctx.renderer.printAbout(ctx.tools.listPluginTools().length, mcpInfo);
|
|
1742
1765
|
}
|
|
1743
1766
|
},
|
|
1744
1767
|
{
|
|
@@ -2146,6 +2169,55 @@ ${text}
|
|
|
2146
2169
|
console.log();
|
|
2147
2170
|
}
|
|
2148
2171
|
},
|
|
2172
|
+
{
|
|
2173
|
+
name: "mcp",
|
|
2174
|
+
description: "Show MCP server connections and tools",
|
|
2175
|
+
usage: "/mcp [reconnect]",
|
|
2176
|
+
execute(args, ctx) {
|
|
2177
|
+
const manager = ctx.getMcpManager();
|
|
2178
|
+
if (!manager) {
|
|
2179
|
+
console.log();
|
|
2180
|
+
console.log(chalk2.dim(" No MCP servers configured."));
|
|
2181
|
+
console.log(chalk2.dim(' Add "mcpServers" to ~/.aicli/config.json to connect MCP servers.'));
|
|
2182
|
+
console.log(chalk2.dim(" Example:"));
|
|
2183
|
+
console.log(chalk2.dim(' "mcpServers": {'));
|
|
2184
|
+
console.log(chalk2.dim(' "filesystem": {'));
|
|
2185
|
+
console.log(chalk2.dim(' "command": "npx",'));
|
|
2186
|
+
console.log(chalk2.dim(' "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"]'));
|
|
2187
|
+
console.log(chalk2.dim(" }"));
|
|
2188
|
+
console.log(chalk2.dim(" }"));
|
|
2189
|
+
console.log();
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
const statuses = manager.getStatus();
|
|
2193
|
+
console.log();
|
|
2194
|
+
console.log(chalk2.bold(" \u{1F50C} MCP Servers:"));
|
|
2195
|
+
console.log();
|
|
2196
|
+
if (statuses.length === 0) {
|
|
2197
|
+
console.log(chalk2.dim(" No MCP servers configured."));
|
|
2198
|
+
console.log();
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
for (const s of statuses) {
|
|
2202
|
+
const statusBadge = s.connected ? chalk2.green("connected") : chalk2.red("disconnected");
|
|
2203
|
+
const toolCountStr = s.connected ? chalk2.dim(` \u2014 ${s.toolCount} tool(s)`) : "";
|
|
2204
|
+
const errorStr = s.error ? chalk2.red(`
|
|
2205
|
+
Error: ${s.error}`) : "";
|
|
2206
|
+
const serverLabel = s.serverName !== s.serverId ? ` (${s.serverName})` : "";
|
|
2207
|
+
console.log(` ${chalk2.cyan(s.serverId)}${chalk2.dim(serverLabel)} [${statusBadge}]${toolCountStr}${errorStr}`);
|
|
2208
|
+
if (s.connected) {
|
|
2209
|
+
const mcpTools = ctx.tools.listMcpTools().filter(
|
|
2210
|
+
(t) => t.definition.name.startsWith(`mcp__${s.serverId}__`)
|
|
2211
|
+
);
|
|
2212
|
+
for (const t of mcpTools) {
|
|
2213
|
+
const shortName = t.definition.name.replace(`mcp__${s.serverId}__`, "");
|
|
2214
|
+
console.log(chalk2.dim(` - ${shortName}`));
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
console.log();
|
|
2219
|
+
}
|
|
2220
|
+
},
|
|
2149
2221
|
{
|
|
2150
2222
|
name: "config",
|
|
2151
2223
|
description: "Open configuration wizard (API keys, proxy, default provider)",
|
|
@@ -3798,6 +3870,7 @@ import { join as join7 } from "path";
|
|
|
3798
3870
|
var ToolRegistry = class {
|
|
3799
3871
|
tools = /* @__PURE__ */ new Map();
|
|
3800
3872
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
3873
|
+
mcpToolNames = /* @__PURE__ */ new Set();
|
|
3801
3874
|
constructor() {
|
|
3802
3875
|
this.register(bashTool);
|
|
3803
3876
|
this.register(readFileTool);
|
|
@@ -3831,6 +3904,22 @@ var ToolRegistry = class {
|
|
|
3831
3904
|
listPluginTools() {
|
|
3832
3905
|
return [...this.tools.values()].filter((t) => this.pluginToolNames.has(t.definition.name));
|
|
3833
3906
|
}
|
|
3907
|
+
/** 注册一个 MCP 工具(名称以 mcp__ 开头) */
|
|
3908
|
+
registerMcpTool(tool) {
|
|
3909
|
+
this.tools.set(tool.definition.name, tool);
|
|
3910
|
+
this.mcpToolNames.add(tool.definition.name);
|
|
3911
|
+
}
|
|
3912
|
+
/** 返回所有 MCP 工具 */
|
|
3913
|
+
listMcpTools() {
|
|
3914
|
+
return [...this.tools.values()].filter((t) => this.mcpToolNames.has(t.definition.name));
|
|
3915
|
+
}
|
|
3916
|
+
/** 清除所有已注册的 MCP 工具(重连时先清除再重新注册) */
|
|
3917
|
+
unregisterMcpTools() {
|
|
3918
|
+
for (const name of this.mcpToolNames) {
|
|
3919
|
+
this.tools.delete(name);
|
|
3920
|
+
}
|
|
3921
|
+
this.mcpToolNames.clear();
|
|
3922
|
+
}
|
|
3834
3923
|
/**
|
|
3835
3924
|
* Dynamically loads .js plugin files from pluginsDir.
|
|
3836
3925
|
*
|
|
@@ -3908,6 +3997,7 @@ import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
|
3908
3997
|
|
|
3909
3998
|
// src/tools/types.ts
|
|
3910
3999
|
function getDangerLevel(toolName, args) {
|
|
4000
|
+
if (toolName.startsWith("mcp__")) return "safe";
|
|
3911
4001
|
if (toolName === "bash") {
|
|
3912
4002
|
const cmd = String(args["command"] ?? "");
|
|
3913
4003
|
if (/\brm\s+[^\n]*(?:-\w*[rRfF]\w*|--recursive|--force)\b/.test(cmd)) return "destructive";
|
|
@@ -4737,6 +4827,407 @@ function formatGitContextForPrompt(ctx) {
|
|
|
4737
4827
|
return lines.join("\n");
|
|
4738
4828
|
}
|
|
4739
4829
|
|
|
4830
|
+
// src/mcp/client.ts
|
|
4831
|
+
import { spawn as spawn2 } from "child_process";
|
|
4832
|
+
var McpClient = class {
|
|
4833
|
+
serverId;
|
|
4834
|
+
config;
|
|
4835
|
+
process = null;
|
|
4836
|
+
nextId = 1;
|
|
4837
|
+
connected = false;
|
|
4838
|
+
serverInfo = null;
|
|
4839
|
+
/** stderr 收集(最多保留最后 2KB,用于错误报告) */
|
|
4840
|
+
stderrBuffer = "";
|
|
4841
|
+
/** 缓存已发现的工具列表 */
|
|
4842
|
+
cachedTools = [];
|
|
4843
|
+
/** 错误信息(连接失败时设置) */
|
|
4844
|
+
errorMessage = null;
|
|
4845
|
+
// ── JSON-RPC 请求/响应匹配 ──────────────────────────────────────
|
|
4846
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
4847
|
+
/** stdout 残余缓冲区(处理不完整的 JSON 行) */
|
|
4848
|
+
stdoutBuffer = "";
|
|
4849
|
+
constructor(serverId, config) {
|
|
4850
|
+
this.serverId = serverId;
|
|
4851
|
+
this.config = config;
|
|
4852
|
+
}
|
|
4853
|
+
get isConnected() {
|
|
4854
|
+
return this.connected;
|
|
4855
|
+
}
|
|
4856
|
+
get serverName() {
|
|
4857
|
+
return this.serverInfo?.name ?? this.serverId;
|
|
4858
|
+
}
|
|
4859
|
+
get tools() {
|
|
4860
|
+
return this.cachedTools;
|
|
4861
|
+
}
|
|
4862
|
+
// ══════════════════════════════════════════════════════════════════
|
|
4863
|
+
// 连接与初始化
|
|
4864
|
+
// ══════════════════════════════════════════════════════════════════
|
|
4865
|
+
async connect() {
|
|
4866
|
+
const timeout = this.config.timeout ?? MCP_CONNECT_TIMEOUT;
|
|
4867
|
+
try {
|
|
4868
|
+
this.process = spawn2(this.config.command, this.config.args ?? [], {
|
|
4869
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4870
|
+
env: { ...process.env, ...this.config.env },
|
|
4871
|
+
// Windows 上 npx 等是 .cmd 脚本,需要 shell 模式
|
|
4872
|
+
shell: process.platform === "win32",
|
|
4873
|
+
// 不让子进程阻止父进程退出
|
|
4874
|
+
detached: false
|
|
4875
|
+
});
|
|
4876
|
+
this.process.on("error", (err) => {
|
|
4877
|
+
this.errorMessage = err.message;
|
|
4878
|
+
this.connected = false;
|
|
4879
|
+
this.rejectAllPending(new Error(`MCP server [${this.serverId}] process error: ${err.message}`));
|
|
4880
|
+
});
|
|
4881
|
+
this.process.on("exit", (code, signal) => {
|
|
4882
|
+
this.connected = false;
|
|
4883
|
+
const reason = signal ? `signal ${signal}` : `code ${code}`;
|
|
4884
|
+
this.rejectAllPending(new Error(`MCP server [${this.serverId}] exited: ${reason}`));
|
|
4885
|
+
});
|
|
4886
|
+
this.process.stdout.setEncoding("utf-8");
|
|
4887
|
+
this.process.stdout.on("data", (chunk) => this.handleStdoutData(chunk));
|
|
4888
|
+
this.process.stderr.setEncoding("utf-8");
|
|
4889
|
+
this.process.stderr.on("data", (chunk) => {
|
|
4890
|
+
this.stderrBuffer += chunk;
|
|
4891
|
+
if (this.stderrBuffer.length > 2048) {
|
|
4892
|
+
this.stderrBuffer = this.stderrBuffer.slice(-2048);
|
|
4893
|
+
}
|
|
4894
|
+
});
|
|
4895
|
+
const initResult = await this.withTimeout(
|
|
4896
|
+
this.sendRequest("initialize", {
|
|
4897
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
4898
|
+
capabilities: {},
|
|
4899
|
+
clientInfo: { name: APP_NAME, version: VERSION }
|
|
4900
|
+
}),
|
|
4901
|
+
timeout,
|
|
4902
|
+
"initialize handshake"
|
|
4903
|
+
);
|
|
4904
|
+
this.serverInfo = initResult.serverInfo;
|
|
4905
|
+
this.sendNotification("notifications/initialized");
|
|
4906
|
+
this.connected = true;
|
|
4907
|
+
await this.refreshTools();
|
|
4908
|
+
} catch (err) {
|
|
4909
|
+
this.errorMessage = err instanceof Error ? err.message : String(err);
|
|
4910
|
+
this.connected = false;
|
|
4911
|
+
this.killProcess();
|
|
4912
|
+
throw err;
|
|
4913
|
+
}
|
|
4914
|
+
}
|
|
4915
|
+
// ══════════════════════════════════════════════════════════════════
|
|
4916
|
+
// 工具操作
|
|
4917
|
+
// ══════════════════════════════════════════════════════════════════
|
|
4918
|
+
/** 刷新工具列表(tools/list) */
|
|
4919
|
+
async refreshTools() {
|
|
4920
|
+
this.ensureConnected();
|
|
4921
|
+
const result = await this.withTimeout(
|
|
4922
|
+
this.sendRequest("tools/list", {}),
|
|
4923
|
+
MCP_CALL_TIMEOUT,
|
|
4924
|
+
"tools/list"
|
|
4925
|
+
);
|
|
4926
|
+
this.cachedTools = result.tools ?? [];
|
|
4927
|
+
return this.cachedTools;
|
|
4928
|
+
}
|
|
4929
|
+
/** 调用工具(tools/call) */
|
|
4930
|
+
async callTool(name, args) {
|
|
4931
|
+
this.ensureConnected();
|
|
4932
|
+
return this.withTimeout(
|
|
4933
|
+
this.sendRequest("tools/call", { name, arguments: args }),
|
|
4934
|
+
MCP_CALL_TIMEOUT,
|
|
4935
|
+
`tools/call(${name})`
|
|
4936
|
+
);
|
|
4937
|
+
}
|
|
4938
|
+
// ══════════════════════════════════════════════════════════════════
|
|
4939
|
+
// 关闭连接
|
|
4940
|
+
// ══════════════════════════════════════════════════════════════════
|
|
4941
|
+
async close() {
|
|
4942
|
+
this.connected = false;
|
|
4943
|
+
this.rejectAllPending(new Error("Client closing"));
|
|
4944
|
+
this.killProcess();
|
|
4945
|
+
}
|
|
4946
|
+
// ══════════════════════════════════════════════════════════════════
|
|
4947
|
+
// 内部方法:JSON-RPC 通信
|
|
4948
|
+
// ══════════════════════════════════════════════════════════════════
|
|
4949
|
+
sendRequest(method, params) {
|
|
4950
|
+
return new Promise((resolve5, reject) => {
|
|
4951
|
+
if (!this.process?.stdin?.writable) {
|
|
4952
|
+
return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
|
|
4953
|
+
}
|
|
4954
|
+
const id = this.nextId++;
|
|
4955
|
+
const request = {
|
|
4956
|
+
jsonrpc: "2.0",
|
|
4957
|
+
id,
|
|
4958
|
+
method,
|
|
4959
|
+
...params !== void 0 ? { params } : {}
|
|
4960
|
+
};
|
|
4961
|
+
const timer = setTimeout(() => {
|
|
4962
|
+
this.pendingRequests.delete(id);
|
|
4963
|
+
reject(new Error(`MCP request [${method}] timed out (internal)`));
|
|
4964
|
+
}, MCP_CALL_TIMEOUT * 2);
|
|
4965
|
+
this.pendingRequests.set(id, {
|
|
4966
|
+
resolve: resolve5,
|
|
4967
|
+
reject,
|
|
4968
|
+
timer
|
|
4969
|
+
});
|
|
4970
|
+
const json = JSON.stringify(request) + "\n";
|
|
4971
|
+
this.process.stdin.write(json, (err) => {
|
|
4972
|
+
if (err) {
|
|
4973
|
+
this.pendingRequests.delete(id);
|
|
4974
|
+
clearTimeout(timer);
|
|
4975
|
+
reject(new Error(`MCP write error: ${err.message}`));
|
|
4976
|
+
}
|
|
4977
|
+
});
|
|
4978
|
+
});
|
|
4979
|
+
}
|
|
4980
|
+
sendNotification(method, params) {
|
|
4981
|
+
if (!this.process?.stdin?.writable) return;
|
|
4982
|
+
const notification = {
|
|
4983
|
+
jsonrpc: "2.0",
|
|
4984
|
+
method,
|
|
4985
|
+
...params !== void 0 ? { params } : {}
|
|
4986
|
+
};
|
|
4987
|
+
this.process.stdin.write(JSON.stringify(notification) + "\n");
|
|
4988
|
+
}
|
|
4989
|
+
/**
|
|
4990
|
+
* 处理 stdout 数据:按行分割,每行解析为 JSON-RPC 响应。
|
|
4991
|
+
* 处理不完整行:残余数据保留在 stdoutBuffer 中。
|
|
4992
|
+
*/
|
|
4993
|
+
handleStdoutData(chunk) {
|
|
4994
|
+
this.stdoutBuffer += chunk;
|
|
4995
|
+
const lines = this.stdoutBuffer.split("\n");
|
|
4996
|
+
this.stdoutBuffer = lines.pop() ?? "";
|
|
4997
|
+
for (const line of lines) {
|
|
4998
|
+
const trimmed = line.trim();
|
|
4999
|
+
if (!trimmed) continue;
|
|
5000
|
+
try {
|
|
5001
|
+
const msg = JSON.parse(trimmed);
|
|
5002
|
+
this.handleMessage(msg);
|
|
5003
|
+
} catch {
|
|
5004
|
+
}
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
5007
|
+
/** 处理收到的 JSON-RPC 消息 */
|
|
5008
|
+
handleMessage(msg) {
|
|
5009
|
+
if ("id" in msg && typeof msg.id === "number") {
|
|
5010
|
+
const pending = this.pendingRequests.get(msg.id);
|
|
5011
|
+
if (!pending) return;
|
|
5012
|
+
this.pendingRequests.delete(msg.id);
|
|
5013
|
+
clearTimeout(pending.timer);
|
|
5014
|
+
const response = msg;
|
|
5015
|
+
if (response.error) {
|
|
5016
|
+
pending.reject(new Error(
|
|
5017
|
+
`MCP error [${response.error.code}]: ${response.error.message}`
|
|
5018
|
+
));
|
|
5019
|
+
} else {
|
|
5020
|
+
pending.resolve(response.result);
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
// ══════════════════════════════════════════════════════════════════
|
|
5025
|
+
// 辅助方法
|
|
5026
|
+
// ══════════════════════════════════════════════════════════════════
|
|
5027
|
+
ensureConnected() {
|
|
5028
|
+
if (!this.connected) {
|
|
5029
|
+
throw new Error(`MCP server [${this.serverId}] is not connected`);
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
/** Promise 超时包装 */
|
|
5033
|
+
withTimeout(promise, ms, label) {
|
|
5034
|
+
return new Promise((resolve5, reject) => {
|
|
5035
|
+
const timer = setTimeout(() => {
|
|
5036
|
+
reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
|
|
5037
|
+
}, ms);
|
|
5038
|
+
promise.then((val) => {
|
|
5039
|
+
clearTimeout(timer);
|
|
5040
|
+
resolve5(val);
|
|
5041
|
+
}).catch((err) => {
|
|
5042
|
+
clearTimeout(timer);
|
|
5043
|
+
reject(err);
|
|
5044
|
+
});
|
|
5045
|
+
});
|
|
5046
|
+
}
|
|
5047
|
+
/** 拒绝所有挂起的请求 */
|
|
5048
|
+
rejectAllPending(error) {
|
|
5049
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
5050
|
+
clearTimeout(pending.timer);
|
|
5051
|
+
pending.reject(error);
|
|
5052
|
+
}
|
|
5053
|
+
this.pendingRequests.clear();
|
|
5054
|
+
}
|
|
5055
|
+
/** 杀掉子进程 */
|
|
5056
|
+
killProcess() {
|
|
5057
|
+
if (this.process) {
|
|
5058
|
+
try {
|
|
5059
|
+
this.process.stdin?.end();
|
|
5060
|
+
this.process.kill();
|
|
5061
|
+
} catch {
|
|
5062
|
+
}
|
|
5063
|
+
this.process = null;
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
};
|
|
5067
|
+
|
|
5068
|
+
// src/mcp/manager.ts
|
|
5069
|
+
var McpManager = class {
|
|
5070
|
+
clients = /* @__PURE__ */ new Map();
|
|
5071
|
+
/**
|
|
5072
|
+
* 连接所有配置的 MCP 服务器(并发连接,单个失败不阻塞其他)。
|
|
5073
|
+
* 连接结果通过 getStatus() 查看。
|
|
5074
|
+
*/
|
|
5075
|
+
async connectAll(servers) {
|
|
5076
|
+
const entries = Object.entries(servers);
|
|
5077
|
+
if (entries.length === 0) return;
|
|
5078
|
+
const promises = entries.map(async ([serverId, config]) => {
|
|
5079
|
+
const client = new McpClient(serverId, config);
|
|
5080
|
+
this.clients.set(serverId, client);
|
|
5081
|
+
try {
|
|
5082
|
+
await client.connect();
|
|
5083
|
+
process.stderr.write(`[mcp] \u2713 ${serverId}: connected (${client.serverName}, ${client.tools.length} tools)
|
|
5084
|
+
`);
|
|
5085
|
+
} catch (err) {
|
|
5086
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5087
|
+
process.stderr.write(`[mcp] \u2717 ${serverId}: ${msg}
|
|
5088
|
+
`);
|
|
5089
|
+
}
|
|
5090
|
+
});
|
|
5091
|
+
await Promise.allSettled(promises);
|
|
5092
|
+
}
|
|
5093
|
+
/**
|
|
5094
|
+
* 获取所有已连接服务器的工具,转换为 ai-cli 的 Tool 接口。
|
|
5095
|
+
* 工具名格式:mcp__<serverId>__<toolName>
|
|
5096
|
+
*/
|
|
5097
|
+
getAllTools() {
|
|
5098
|
+
const tools = [];
|
|
5099
|
+
for (const [serverId, client] of this.clients) {
|
|
5100
|
+
if (!client.isConnected) continue;
|
|
5101
|
+
for (const mcpTool of client.tools) {
|
|
5102
|
+
tools.push(this.wrapMcpTool(serverId, client, mcpTool));
|
|
5103
|
+
}
|
|
5104
|
+
}
|
|
5105
|
+
return tools;
|
|
5106
|
+
}
|
|
5107
|
+
/**
|
|
5108
|
+
* 获取连接状态摘要。
|
|
5109
|
+
*/
|
|
5110
|
+
getStatus() {
|
|
5111
|
+
const statuses = [];
|
|
5112
|
+
for (const [serverId, client] of this.clients) {
|
|
5113
|
+
statuses.push({
|
|
5114
|
+
serverId,
|
|
5115
|
+
serverName: client.serverName,
|
|
5116
|
+
toolCount: client.tools.length,
|
|
5117
|
+
connected: client.isConnected,
|
|
5118
|
+
error: client.errorMessage ?? void 0
|
|
5119
|
+
});
|
|
5120
|
+
}
|
|
5121
|
+
return statuses;
|
|
5122
|
+
}
|
|
5123
|
+
/**
|
|
5124
|
+
* 获取已连接服务器数量。
|
|
5125
|
+
*/
|
|
5126
|
+
getConnectedCount() {
|
|
5127
|
+
let count = 0;
|
|
5128
|
+
for (const client of this.clients.values()) {
|
|
5129
|
+
if (client.isConnected) count++;
|
|
5130
|
+
}
|
|
5131
|
+
return count;
|
|
5132
|
+
}
|
|
5133
|
+
/**
|
|
5134
|
+
* 获取所有 MCP 工具的总数。
|
|
5135
|
+
*/
|
|
5136
|
+
getTotalToolCount() {
|
|
5137
|
+
let count = 0;
|
|
5138
|
+
for (const client of this.clients.values()) {
|
|
5139
|
+
if (client.isConnected) count += client.tools.length;
|
|
5140
|
+
}
|
|
5141
|
+
return count;
|
|
5142
|
+
}
|
|
5143
|
+
/**
|
|
5144
|
+
* 关闭所有 MCP 服务器连接。
|
|
5145
|
+
*/
|
|
5146
|
+
async closeAll() {
|
|
5147
|
+
const promises = [...this.clients.values()].map((c) => c.close().catch(() => {
|
|
5148
|
+
}));
|
|
5149
|
+
await Promise.allSettled(promises);
|
|
5150
|
+
this.clients.clear();
|
|
5151
|
+
}
|
|
5152
|
+
// ══════════════════════════════════════════════════════════════════
|
|
5153
|
+
// 内部方法
|
|
5154
|
+
// ══════════════════════════════════════════════════════════════════
|
|
5155
|
+
/**
|
|
5156
|
+
* 将 MCP 工具包装为 ai-cli 的 Tool 接口。
|
|
5157
|
+
* execute() 方法委托给对应 McpClient.callTool()。
|
|
5158
|
+
*/
|
|
5159
|
+
wrapMcpTool(serverId, client, mcpTool) {
|
|
5160
|
+
const toolName = `${MCP_TOOL_PREFIX}${serverId}__${mcpTool.name}`;
|
|
5161
|
+
const definition = this.convertDefinition(toolName, serverId, mcpTool);
|
|
5162
|
+
return {
|
|
5163
|
+
definition,
|
|
5164
|
+
execute: async (args) => {
|
|
5165
|
+
try {
|
|
5166
|
+
const result = await client.callTool(mcpTool.name, args);
|
|
5167
|
+
if (result.isError) {
|
|
5168
|
+
const errorText = this.extractText(result.content);
|
|
5169
|
+
throw new Error(errorText || "MCP tool returned error with no message");
|
|
5170
|
+
}
|
|
5171
|
+
return this.extractText(result.content) || "(no output)";
|
|
5172
|
+
} catch (err) {
|
|
5173
|
+
throw new Error(`MCP [${serverId}/${mcpTool.name}]: ${err instanceof Error ? err.message : String(err)}`);
|
|
5174
|
+
}
|
|
5175
|
+
}
|
|
5176
|
+
};
|
|
5177
|
+
}
|
|
5178
|
+
/**
|
|
5179
|
+
* 转换 MCP 工具定义为 ai-cli 的 ToolDefinition。
|
|
5180
|
+
* MCP 使用 JSON Schema 的 inputSchema,ai-cli 使用扁平的 Record<string, ToolParameterSchema>。
|
|
5181
|
+
*/
|
|
5182
|
+
convertDefinition(toolName, serverId, mcpTool) {
|
|
5183
|
+
const parameters = {};
|
|
5184
|
+
const props = mcpTool.inputSchema?.properties ?? {};
|
|
5185
|
+
const required = new Set(mcpTool.inputSchema?.required ?? []);
|
|
5186
|
+
for (const [key, schema] of Object.entries(props)) {
|
|
5187
|
+
parameters[key] = {
|
|
5188
|
+
type: this.normalizeType(schema.type),
|
|
5189
|
+
description: schema.description ?? "",
|
|
5190
|
+
...schema.enum ? { enum: schema.enum } : {},
|
|
5191
|
+
...schema.items ? { items: { type: this.normalizeType(schema.items.type) } } : {},
|
|
5192
|
+
...required.has(key) ? { required: true } : {}
|
|
5193
|
+
};
|
|
5194
|
+
}
|
|
5195
|
+
return {
|
|
5196
|
+
name: toolName,
|
|
5197
|
+
description: (mcpTool.description ?? mcpTool.name) + ` [MCP: ${serverId}]`,
|
|
5198
|
+
parameters
|
|
5199
|
+
};
|
|
5200
|
+
}
|
|
5201
|
+
/**
|
|
5202
|
+
* 将 JSON Schema 类型映射到 ai-cli 的 ToolParameterType。
|
|
5203
|
+
* integer → number(ai-cli 不区分整数/浮点)
|
|
5204
|
+
*/
|
|
5205
|
+
normalizeType(jsonSchemaType) {
|
|
5206
|
+
switch (jsonSchemaType) {
|
|
5207
|
+
case "string":
|
|
5208
|
+
return "string";
|
|
5209
|
+
case "number":
|
|
5210
|
+
case "integer":
|
|
5211
|
+
return "number";
|
|
5212
|
+
case "boolean":
|
|
5213
|
+
return "boolean";
|
|
5214
|
+
case "array":
|
|
5215
|
+
return "array";
|
|
5216
|
+
case "object":
|
|
5217
|
+
return "object";
|
|
5218
|
+
default:
|
|
5219
|
+
return "string";
|
|
5220
|
+
}
|
|
5221
|
+
}
|
|
5222
|
+
/**
|
|
5223
|
+
* 从 MCP 响应的 content blocks 中提取纯文本。
|
|
5224
|
+
* 只处理 type="text" 的块,其他类型(image 等)跳过。
|
|
5225
|
+
*/
|
|
5226
|
+
extractText(content) {
|
|
5227
|
+
return content.filter((block) => block.type === "text" && block.text).map((block) => block.text).join("\n");
|
|
5228
|
+
}
|
|
5229
|
+
};
|
|
5230
|
+
|
|
4740
5231
|
// src/repl/repl.ts
|
|
4741
5232
|
var IMAGE_MIME = {
|
|
4742
5233
|
".png": "image/png",
|
|
@@ -4832,6 +5323,8 @@ var Repl = class {
|
|
|
4832
5323
|
sessionTokenUsage = { inputTokens: 0, outputTokens: 0 };
|
|
4833
5324
|
/** 启动时检测到的 Git 分支(无 git 仓库时为 null) */
|
|
4834
5325
|
gitBranch = null;
|
|
5326
|
+
/** MCP 多服务器管理器(无 MCP 配置时为 null) */
|
|
5327
|
+
mcpManager = null;
|
|
4835
5328
|
/**
|
|
4836
5329
|
* 交互式列表选择器进行中标志。
|
|
4837
5330
|
* 与 toolExecutor.confirming 类似:主循环 line handler 在此为 true 时忽略 line 事件,
|
|
@@ -5107,6 +5600,23 @@ ${memory.content}`);
|
|
|
5107
5600
|
process.stdout.write(chalk9.dim(` \u{1F50C} Plugins loaded: ${pluginCount} tool(s) from plugins/
|
|
5108
5601
|
`));
|
|
5109
5602
|
}
|
|
5603
|
+
const mcpServers = this.config.get("mcpServers");
|
|
5604
|
+
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
5605
|
+
this.mcpManager = new McpManager();
|
|
5606
|
+
await this.mcpManager.connectAll(mcpServers);
|
|
5607
|
+
const mcpTools = this.mcpManager.getAllTools();
|
|
5608
|
+
for (const tool of mcpTools) {
|
|
5609
|
+
this.toolRegistry.registerMcpTool(tool);
|
|
5610
|
+
}
|
|
5611
|
+
const connectedCount = this.mcpManager.getConnectedCount();
|
|
5612
|
+
const totalTools = this.mcpManager.getTotalToolCount();
|
|
5613
|
+
if (connectedCount > 0) {
|
|
5614
|
+
process.stdout.write(
|
|
5615
|
+
chalk9.dim(` \u{1F50C} MCP: ${connectedCount} server(s), ${totalTools} tool(s)
|
|
5616
|
+
`)
|
|
5617
|
+
);
|
|
5618
|
+
}
|
|
5619
|
+
}
|
|
5110
5620
|
this.rl.on("SIGINT", () => {
|
|
5111
5621
|
if (this.toolExecutor.confirming) {
|
|
5112
5622
|
this.toolExecutor.cancelConfirm();
|
|
@@ -5536,6 +6046,7 @@ ${memory.content}`);
|
|
|
5536
6046
|
},
|
|
5537
6047
|
generateDevStateSnapshot: () => this.generateDevStateSnapshot(),
|
|
5538
6048
|
clearDevState: () => clearDevState(),
|
|
6049
|
+
getMcpManager: () => this.mcpManager,
|
|
5539
6050
|
exit: () => this.handleExit()
|
|
5540
6051
|
};
|
|
5541
6052
|
await cmd.execute(args, ctx);
|
|
@@ -5546,6 +6057,8 @@ ${memory.content}`);
|
|
|
5546
6057
|
if (sessionId) {
|
|
5547
6058
|
this.events.emit("session.end", { sessionId });
|
|
5548
6059
|
}
|
|
6060
|
+
this.mcpManager?.closeAll().catch(() => {
|
|
6061
|
+
});
|
|
5549
6062
|
this.rl.close();
|
|
5550
6063
|
console.log(chalk9.gray("\nGoodbye!"));
|
|
5551
6064
|
process.exit(0);
|