skyloom 1.4.0
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/.github/workflows/ci.yml +36 -0
- package/CONVERSION_PLAN.md +191 -0
- package/README.md +67 -0
- package/dist/agents/dew.d.ts +15 -0
- package/dist/agents/dew.d.ts.map +1 -0
- package/dist/agents/dew.js +74 -0
- package/dist/agents/dew.js.map +1 -0
- package/dist/agents/fair.d.ts +15 -0
- package/dist/agents/fair.d.ts.map +1 -0
- package/dist/agents/fair.js +106 -0
- package/dist/agents/fair.js.map +1 -0
- package/dist/agents/fog.d.ts +15 -0
- package/dist/agents/fog.d.ts.map +1 -0
- package/dist/agents/fog.js +52 -0
- package/dist/agents/fog.js.map +1 -0
- package/dist/agents/frost.d.ts +15 -0
- package/dist/agents/frost.d.ts.map +1 -0
- package/dist/agents/frost.js +54 -0
- package/dist/agents/frost.js.map +1 -0
- package/dist/agents/rain.d.ts +15 -0
- package/dist/agents/rain.d.ts.map +1 -0
- package/dist/agents/rain.js +54 -0
- package/dist/agents/rain.js.map +1 -0
- package/dist/agents/snow.d.ts +27 -0
- package/dist/agents/snow.d.ts.map +1 -0
- package/dist/agents/snow.js +226 -0
- package/dist/agents/snow.js.map +1 -0
- package/dist/cli/main.d.ts +7 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +402 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/mode.d.ts +17 -0
- package/dist/cli/mode.d.ts.map +1 -0
- package/dist/cli/mode.js +56 -0
- package/dist/cli/mode.js.map +1 -0
- package/dist/core/agent.d.ts +174 -0
- package/dist/core/agent.d.ts.map +1 -0
- package/dist/core/agent.js +1332 -0
- package/dist/core/agent.js.map +1 -0
- package/dist/core/agent_helpers.d.ts +51 -0
- package/dist/core/agent_helpers.d.ts.map +1 -0
- package/dist/core/agent_helpers.js +477 -0
- package/dist/core/agent_helpers.js.map +1 -0
- package/dist/core/bus.d.ts +99 -0
- package/dist/core/bus.d.ts.map +1 -0
- package/dist/core/bus.js +191 -0
- package/dist/core/bus.js.map +1 -0
- package/dist/core/cache.d.ts +63 -0
- package/dist/core/cache.d.ts.map +1 -0
- package/dist/core/cache.js +121 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/checkpoint.d.ts +19 -0
- package/dist/core/checkpoint.d.ts.map +1 -0
- package/dist/core/checkpoint.js +120 -0
- package/dist/core/checkpoint.js.map +1 -0
- package/dist/core/circuit_breaker.d.ts +46 -0
- package/dist/core/circuit_breaker.d.ts.map +1 -0
- package/dist/core/circuit_breaker.js +99 -0
- package/dist/core/circuit_breaker.js.map +1 -0
- package/dist/core/config.d.ts +97 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +281 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/constants.d.ts +78 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/constants.js +84 -0
- package/dist/core/constants.js.map +1 -0
- package/dist/core/factory.d.ts +63 -0
- package/dist/core/factory.d.ts.map +1 -0
- package/dist/core/factory.js +537 -0
- package/dist/core/factory.js.map +1 -0
- package/dist/core/icons.d.ts +28 -0
- package/dist/core/icons.d.ts.map +1 -0
- package/dist/core/icons.js +86 -0
- package/dist/core/icons.js.map +1 -0
- package/dist/core/index.d.ts +29 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +54 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/llm.d.ts +121 -0
- package/dist/core/llm.d.ts.map +1 -0
- package/dist/core/llm.js +532 -0
- package/dist/core/llm.js.map +1 -0
- package/dist/core/logger.d.ts +57 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +122 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/mcp.d.ts +190 -0
- package/dist/core/mcp.d.ts.map +1 -0
- package/dist/core/mcp.js +822 -0
- package/dist/core/mcp.js.map +1 -0
- package/dist/core/mcp_server.d.ts +26 -0
- package/dist/core/mcp_server.d.ts.map +1 -0
- package/dist/core/mcp_server.js +211 -0
- package/dist/core/mcp_server.js.map +1 -0
- package/dist/core/memory.d.ts +190 -0
- package/dist/core/memory.d.ts.map +1 -0
- package/dist/core/memory.js +988 -0
- package/dist/core/memory.js.map +1 -0
- package/dist/core/middleware.d.ts +114 -0
- package/dist/core/middleware.d.ts.map +1 -0
- package/dist/core/middleware.js +248 -0
- package/dist/core/middleware.js.map +1 -0
- package/dist/core/pipelines.d.ts +87 -0
- package/dist/core/pipelines.d.ts.map +1 -0
- package/dist/core/pipelines.js +301 -0
- package/dist/core/pipelines.js.map +1 -0
- package/dist/core/profile.d.ts +23 -0
- package/dist/core/profile.d.ts.map +1 -0
- package/dist/core/profile.js +289 -0
- package/dist/core/profile.js.map +1 -0
- package/dist/core/router.d.ts +24 -0
- package/dist/core/router.d.ts.map +1 -0
- package/dist/core/router.js +111 -0
- package/dist/core/router.js.map +1 -0
- package/dist/core/schemas.d.ts +82 -0
- package/dist/core/schemas.d.ts.map +1 -0
- package/dist/core/schemas.js +200 -0
- package/dist/core/schemas.js.map +1 -0
- package/dist/core/semantic.d.ts +92 -0
- package/dist/core/semantic.d.ts.map +1 -0
- package/dist/core/semantic.js +175 -0
- package/dist/core/semantic.js.map +1 -0
- package/dist/core/skill.d.ts +68 -0
- package/dist/core/skill.d.ts.map +1 -0
- package/dist/core/skill.js +350 -0
- package/dist/core/skill.js.map +1 -0
- package/dist/core/tool.d.ts +99 -0
- package/dist/core/tool.d.ts.map +1 -0
- package/dist/core/tool.js +341 -0
- package/dist/core/tool.js.map +1 -0
- package/dist/core/tool_router.d.ts +29 -0
- package/dist/core/tool_router.d.ts.map +1 -0
- package/dist/core/tool_router.js +172 -0
- package/dist/core/tool_router.js.map +1 -0
- package/dist/core/workspace.d.ts +48 -0
- package/dist/core/workspace.d.ts.map +1 -0
- package/dist/core/workspace.js +179 -0
- package/dist/core/workspace.js.map +1 -0
- package/dist/plugins/loader.d.ts +17 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +96 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/skills/loader.d.ts +9 -0
- package/dist/skills/loader.d.ts.map +1 -0
- package/dist/skills/loader.js +78 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/tools/builtin.d.ts +10 -0
- package/dist/tools/builtin.d.ts.map +1 -0
- package/dist/tools/builtin.js +414 -0
- package/dist/tools/builtin.js.map +1 -0
- package/dist/tools/computer.d.ts +12 -0
- package/dist/tools/computer.d.ts.map +1 -0
- package/dist/tools/computer.js +326 -0
- package/dist/tools/computer.js.map +1 -0
- package/dist/tools/delegate.d.ts +10 -0
- package/dist/tools/delegate.d.ts.map +1 -0
- package/dist/tools/delegate.js +45 -0
- package/dist/tools/delegate.js.map +1 -0
- package/dist/web/server.d.ts +5 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +647 -0
- package/dist/web/server.js.map +1 -0
- package/dist/web/tts.d.ts +33 -0
- package/dist/web/tts.d.ts.map +1 -0
- package/dist/web/tts.js +69 -0
- package/dist/web/tts.js.map +1 -0
- package/package.json +60 -0
- package/scripts/install.js +48 -0
- package/scripts/link.js +10 -0
- package/setup.bat +79 -0
- package/skill-test-ty2fOA/test.md +10 -0
- package/src/agents/dew.ts +70 -0
- package/src/agents/fair.ts +102 -0
- package/src/agents/fog.ts +48 -0
- package/src/agents/frost.ts +50 -0
- package/src/agents/rain.ts +50 -0
- package/src/agents/snow.ts +239 -0
- package/src/cli/main.ts +405 -0
- package/src/cli/mode.ts +58 -0
- package/src/core/agent.ts +1506 -0
- package/src/core/agent_helpers.ts +461 -0
- package/src/core/bus.ts +221 -0
- package/src/core/cache.ts +153 -0
- package/src/core/checkpoint.ts +94 -0
- package/src/core/circuit_breaker.ts +119 -0
- package/src/core/config.ts +341 -0
- package/src/core/constants.ts +95 -0
- package/src/core/factory.ts +627 -0
- package/src/core/icons.ts +53 -0
- package/src/core/index.ts +31 -0
- package/src/core/llm.ts +724 -0
- package/src/core/logger.ts +144 -0
- package/src/core/mcp.ts +953 -0
- package/src/core/mcp_server.ts +176 -0
- package/src/core/memory.ts +1169 -0
- package/src/core/middleware.ts +350 -0
- package/src/core/pipelines.ts +424 -0
- package/src/core/profile.ts +255 -0
- package/src/core/router.ts +124 -0
- package/src/core/schemas.ts +282 -0
- package/src/core/semantic.ts +211 -0
- package/src/core/skill.ts +342 -0
- package/src/core/tool.ts +427 -0
- package/src/core/tool_router.ts +193 -0
- package/src/core/workspace.ts +150 -0
- package/src/plugins/loader.ts +66 -0
- package/src/skills/loader.ts +46 -0
- package/src/sql.js.d.ts +29 -0
- package/src/tools/builtin.ts +382 -0
- package/src/tools/computer.ts +269 -0
- package/src/tools/delegate.ts +49 -0
- package/src/web/server.ts +634 -0
- package/src/web/tts.ts +93 -0
- package/tests/bus.test.ts +121 -0
- package/tests/icons.test.ts +45 -0
- package/tests/router.test.ts +86 -0
- package/tests/schemas.test.ts +51 -0
- package/tests/semantic.test.ts +83 -0
- package/tests/setup.ts +10 -0
- package/tests/skill.test.ts +172 -0
- package/tests/tool.test.ts +108 -0
- package/tests/tool_router.test.ts +71 -0
- package/tsconfig.json +37 -0
- package/vitest.config.ts +17 -0
package/dist/core/mcp.js
ADDED
|
@@ -0,0 +1,822 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MCP (Model Context Protocol) client — Anthropic-compatible transport.
|
|
4
|
+
*
|
|
5
|
+
* Implements the MCP 2025-03-26 specification with two transports:
|
|
6
|
+
* - stdio: subprocess-based with JSON-RPC over stdin/stdout
|
|
7
|
+
* - SSE: text/event-stream via HTTP/fetch with jsonrpc POST endpoint
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.MCPManager = exports.MCPClient = void 0;
|
|
44
|
+
exports.loadPersistedServers = loadPersistedServers;
|
|
45
|
+
exports.savePersistedServer = savePersistedServer;
|
|
46
|
+
exports.removePersistedServer = removePersistedServer;
|
|
47
|
+
const child_process_1 = require("child_process");
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const MCP_PROTOCOL_VERSION = "2025-03-26";
|
|
51
|
+
const CLIENT_INFO = { name: "skyloom", version: "1.0.0" };
|
|
52
|
+
/**
|
|
53
|
+
* Client for connecting to a single MCP server.
|
|
54
|
+
*
|
|
55
|
+
* Supports both stdio (subprocess) and SSE (text/event-stream) transports.
|
|
56
|
+
* Uses JSON-RPC 2.0 with id-based correlation for concurrent requests.
|
|
57
|
+
*/
|
|
58
|
+
class MCPClient {
|
|
59
|
+
constructor(config, log) {
|
|
60
|
+
this.process = null;
|
|
61
|
+
this.serverTools = [];
|
|
62
|
+
this.nextId = 0;
|
|
63
|
+
this.pending = new Map();
|
|
64
|
+
this.log = null;
|
|
65
|
+
// SSE-specific state
|
|
66
|
+
this.sseResponse = null;
|
|
67
|
+
this.sseMessageUrl = "";
|
|
68
|
+
this.config = config;
|
|
69
|
+
this.log = log || null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Generate next request ID.
|
|
73
|
+
*/
|
|
74
|
+
newId() {
|
|
75
|
+
return ++this.nextId;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Connect to the MCP server and list available tools.
|
|
79
|
+
*/
|
|
80
|
+
async initialize() {
|
|
81
|
+
try {
|
|
82
|
+
if (this.config.command) {
|
|
83
|
+
return await this.initStdio();
|
|
84
|
+
}
|
|
85
|
+
else if (this.config.url) {
|
|
86
|
+
return await this.initSSE();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
this.log?.warn("mcp_server_unavailable", {
|
|
91
|
+
server: this.config.name,
|
|
92
|
+
error: String(e),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Perform a health check on the MCP connection.
|
|
99
|
+
*/
|
|
100
|
+
async healthCheck() {
|
|
101
|
+
if (this.config.command) {
|
|
102
|
+
return this.healthStdio();
|
|
103
|
+
}
|
|
104
|
+
else if (this.config.url) {
|
|
105
|
+
return this.healthSSE();
|
|
106
|
+
}
|
|
107
|
+
return { healthy: false, details: "No transport configured" };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Initialize stdio transport.
|
|
111
|
+
*/
|
|
112
|
+
async initStdio() {
|
|
113
|
+
const cmd = this.config.command;
|
|
114
|
+
if (!cmd) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
const env = {
|
|
118
|
+
...process.env,
|
|
119
|
+
...(this.config.env || {}),
|
|
120
|
+
};
|
|
121
|
+
return new Promise((resolve, reject) => {
|
|
122
|
+
try {
|
|
123
|
+
this.process = (0, child_process_1.spawn)(cmd, this.config.args || [], {
|
|
124
|
+
env,
|
|
125
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
126
|
+
});
|
|
127
|
+
if (!this.process) {
|
|
128
|
+
return resolve([]);
|
|
129
|
+
}
|
|
130
|
+
// Start background reading tasks
|
|
131
|
+
this.readStdioLoop();
|
|
132
|
+
this.drainStderr();
|
|
133
|
+
// Send initialize request
|
|
134
|
+
this.requestStdio("initialize", {
|
|
135
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
136
|
+
capabilities: {},
|
|
137
|
+
clientInfo: CLIENT_INFO,
|
|
138
|
+
}).then((initResp) => {
|
|
139
|
+
if (!initResp || initResp.error) {
|
|
140
|
+
resolve([]);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
// Send initialized notification
|
|
144
|
+
this.sendJson({
|
|
145
|
+
jsonrpc: "2.0",
|
|
146
|
+
method: "notifications/initialized",
|
|
147
|
+
params: {},
|
|
148
|
+
}).catch(() => { });
|
|
149
|
+
// Request tool list
|
|
150
|
+
return this.requestStdio("tools/list", {});
|
|
151
|
+
}).then((toolsResp) => {
|
|
152
|
+
if (toolsResp && toolsResp.result) {
|
|
153
|
+
this.serverTools = toolsResp.result.tools || [];
|
|
154
|
+
}
|
|
155
|
+
resolve(this.serverTools);
|
|
156
|
+
})
|
|
157
|
+
.catch((err) => {
|
|
158
|
+
this.log?.warn("mcp_stdio_init_failed", {
|
|
159
|
+
server: this.config.name,
|
|
160
|
+
error: String(err),
|
|
161
|
+
});
|
|
162
|
+
this.close().catch(() => { });
|
|
163
|
+
resolve([]);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
reject(err);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Initialize SSE transport.
|
|
173
|
+
*/
|
|
174
|
+
async initSSE() {
|
|
175
|
+
// SSE implementation would connect via HTTP GET to /sse endpoint
|
|
176
|
+
// and receive endpoint information, then POST to message endpoint.
|
|
177
|
+
// For now, return empty array as placeholder.
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Health check via stdio.
|
|
182
|
+
*/
|
|
183
|
+
async healthStdio() {
|
|
184
|
+
if (!this.process || this.process.exitCode !== null) {
|
|
185
|
+
return { healthy: false, details: "stdio process not running" };
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const resp = await this.requestStdio("ping", {}, 5000);
|
|
189
|
+
if (resp && resp.result) {
|
|
190
|
+
return { healthy: true, details: "ok" };
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
healthy: false,
|
|
194
|
+
details: `unexpected ping response: ${JSON.stringify(resp)}`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
return { healthy: false, details: String(e) };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Health check via SSE.
|
|
203
|
+
*/
|
|
204
|
+
async healthSSE() {
|
|
205
|
+
if (!this.sseMessageUrl) {
|
|
206
|
+
return {
|
|
207
|
+
healthy: false,
|
|
208
|
+
details: "SSE connection not established",
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const resp = await this.requestSSE("ping", {}, 5000);
|
|
213
|
+
if (resp && resp.result) {
|
|
214
|
+
return { healthy: true, details: "ok" };
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
healthy: false,
|
|
218
|
+
details: `unexpected ping response: ${JSON.stringify(resp)}`,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
return { healthy: false, details: String(e) };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Background task: read from subprocess stdout.
|
|
227
|
+
*/
|
|
228
|
+
async readStdioLoop() {
|
|
229
|
+
if (!this.process?.stdout) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
let buffer = "";
|
|
234
|
+
this.process.stdout.on("data", (chunk) => {
|
|
235
|
+
buffer += chunk.toString("utf-8");
|
|
236
|
+
// Process complete lines (JSON-RPC messages are newline-delimited)
|
|
237
|
+
while (buffer.includes("\n")) {
|
|
238
|
+
const idx = buffer.indexOf("\n");
|
|
239
|
+
const line = idx >= 0 ? buffer.slice(0, idx) : buffer;
|
|
240
|
+
buffer = idx >= 0 ? buffer.slice(idx + 1) : "";
|
|
241
|
+
const msg = this.parseJsonLine(line);
|
|
242
|
+
if (!msg) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const msgId = msg.id;
|
|
246
|
+
if (msgId === undefined || msgId === null) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const pending = this.pending.get(msgId);
|
|
250
|
+
if (pending) {
|
|
251
|
+
clearTimeout(pending.timer);
|
|
252
|
+
pending.resolve(msg);
|
|
253
|
+
this.pending.delete(msgId);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
this.process.stdout.on("end", () => {
|
|
258
|
+
// Fail all pending requests when stdout closes
|
|
259
|
+
for (const [id, { reject, timer }] of this.pending) {
|
|
260
|
+
clearTimeout(timer);
|
|
261
|
+
reject(new Error("MCP server stdout closed"));
|
|
262
|
+
this.pending.delete(id);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
this.log?.warn("mcp_stdio_read_error", {
|
|
268
|
+
server: this.config.name,
|
|
269
|
+
error: String(e),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Background task: drain stderr to prevent blocking.
|
|
275
|
+
*/
|
|
276
|
+
async drainStderr() {
|
|
277
|
+
if (!this.process?.stderr) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
this.process.stderr.on("data", (chunk) => {
|
|
282
|
+
const line = chunk.toString("utf-8", 0, Math.min(100, chunk.length));
|
|
283
|
+
this.log?.debug("mcp_stderr", {
|
|
284
|
+
server: this.config.name,
|
|
285
|
+
line: line.trim(),
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
catch (e) {
|
|
290
|
+
// Ignore stderr drain errors
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Send JSON-RPC request via stdio and wait for response.
|
|
295
|
+
*/
|
|
296
|
+
async requestStdio(method, params, timeoutMs = 10000) {
|
|
297
|
+
if (!this.process?.stdin) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
const reqId = this.newId();
|
|
301
|
+
return new Promise((resolve, reject) => {
|
|
302
|
+
const timer = setTimeout(() => {
|
|
303
|
+
this.pending.delete(reqId);
|
|
304
|
+
this.log?.warn("mcp_stdio_timeout", {
|
|
305
|
+
server: this.config.name,
|
|
306
|
+
method,
|
|
307
|
+
});
|
|
308
|
+
reject(new Error("Request timeout"));
|
|
309
|
+
}, timeoutMs);
|
|
310
|
+
this.pending.set(reqId, {
|
|
311
|
+
resolve,
|
|
312
|
+
reject: (err) => {
|
|
313
|
+
reject(err);
|
|
314
|
+
},
|
|
315
|
+
timer,
|
|
316
|
+
});
|
|
317
|
+
this.sendJson({
|
|
318
|
+
jsonrpc: "2.0",
|
|
319
|
+
method,
|
|
320
|
+
params,
|
|
321
|
+
id: reqId,
|
|
322
|
+
}).catch((err) => {
|
|
323
|
+
clearTimeout(timer);
|
|
324
|
+
this.pending.delete(reqId);
|
|
325
|
+
reject(err);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Send JSON-RPC request via SSE and wait for response.
|
|
331
|
+
*/
|
|
332
|
+
async requestSSE(method, params, timeoutMs = 10000) {
|
|
333
|
+
const reqId = this.newId();
|
|
334
|
+
return new Promise((resolve, reject) => {
|
|
335
|
+
const timer = setTimeout(() => {
|
|
336
|
+
this.pending.delete(reqId);
|
|
337
|
+
this.log?.warn("mcp_sse_timeout", {
|
|
338
|
+
server: this.config.name,
|
|
339
|
+
method,
|
|
340
|
+
});
|
|
341
|
+
reject(new Error("Request timeout"));
|
|
342
|
+
}, timeoutMs);
|
|
343
|
+
this.pending.set(reqId, {
|
|
344
|
+
resolve,
|
|
345
|
+
reject: (err) => {
|
|
346
|
+
reject(err);
|
|
347
|
+
},
|
|
348
|
+
timer,
|
|
349
|
+
});
|
|
350
|
+
this.postJson({
|
|
351
|
+
jsonrpc: "2.0",
|
|
352
|
+
method,
|
|
353
|
+
params,
|
|
354
|
+
id: reqId,
|
|
355
|
+
}).catch((err) => {
|
|
356
|
+
clearTimeout(timer);
|
|
357
|
+
this.pending.delete(reqId);
|
|
358
|
+
reject(err);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Send JSON data via stdin.
|
|
364
|
+
*/
|
|
365
|
+
async sendJson(data) {
|
|
366
|
+
if (!this.process?.stdin) {
|
|
367
|
+
throw new Error("stdin not available");
|
|
368
|
+
}
|
|
369
|
+
// Serialize to JSON and ensure newline termination
|
|
370
|
+
const line = JSON.stringify(data) + "\n";
|
|
371
|
+
return new Promise((resolve, reject) => {
|
|
372
|
+
this.process.stdin.write(line, "utf-8", (err) => {
|
|
373
|
+
if (err) {
|
|
374
|
+
reject(err);
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
resolve();
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* POST JSON data via HTTP (SSE).
|
|
384
|
+
*/
|
|
385
|
+
async postJson(_data) {
|
|
386
|
+
if (!this.sseMessageUrl) {
|
|
387
|
+
throw new Error("SSE message URL not set");
|
|
388
|
+
}
|
|
389
|
+
// Placeholder: in real implementation would use fetch() to POST
|
|
390
|
+
// For now, just return empty promise
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Parse a single JSON line.
|
|
394
|
+
*/
|
|
395
|
+
parseJsonLine(line) {
|
|
396
|
+
try {
|
|
397
|
+
return JSON.parse(line);
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Call a tool on the MCP server.
|
|
405
|
+
*/
|
|
406
|
+
async callTool(name, args) {
|
|
407
|
+
let response = null;
|
|
408
|
+
if (this.config.command) {
|
|
409
|
+
response = await this.requestStdio("tools/call", {
|
|
410
|
+
name,
|
|
411
|
+
arguments: args,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
else if (this.config.url) {
|
|
415
|
+
response = await this.requestSSE("tools/call", {
|
|
416
|
+
name,
|
|
417
|
+
arguments: args,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
if (response && response.result) {
|
|
421
|
+
return this.extractToolResult(response.result);
|
|
422
|
+
}
|
|
423
|
+
const error = response?.error || {};
|
|
424
|
+
return `MCP tool error: ${error.message || "unknown"}`;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Extract text content from MCP tool result.
|
|
428
|
+
*/
|
|
429
|
+
extractToolResult(result) {
|
|
430
|
+
const parts = [];
|
|
431
|
+
if (Array.isArray(result.content)) {
|
|
432
|
+
for (const item of result.content) {
|
|
433
|
+
if (item.type === "text") {
|
|
434
|
+
parts.push(item.text);
|
|
435
|
+
}
|
|
436
|
+
else if (item.type === "resource") {
|
|
437
|
+
parts.push(JSON.stringify(item.resource || {}));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return parts.length > 0 ? parts.join("\n") : "Tool returned no content.";
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Get list of tool definitions from server.
|
|
445
|
+
*/
|
|
446
|
+
getToolDefinitions() {
|
|
447
|
+
return [...this.serverTools];
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Close the connection and clean up resources.
|
|
451
|
+
*/
|
|
452
|
+
async close() {
|
|
453
|
+
// Cancel pending requests
|
|
454
|
+
for (const [, { reject, timer }] of this.pending) {
|
|
455
|
+
clearTimeout(timer);
|
|
456
|
+
reject(new Error("Connection closed"));
|
|
457
|
+
}
|
|
458
|
+
this.pending.clear();
|
|
459
|
+
// Terminate subprocess
|
|
460
|
+
if (this.process) {
|
|
461
|
+
try {
|
|
462
|
+
this.process.kill("SIGTERM");
|
|
463
|
+
// Wait for graceful shutdown
|
|
464
|
+
await new Promise((resolve) => {
|
|
465
|
+
const timeout = setTimeout(() => {
|
|
466
|
+
this.process?.kill("SIGKILL");
|
|
467
|
+
resolve();
|
|
468
|
+
}, 5000);
|
|
469
|
+
this.process.on("exit", () => {
|
|
470
|
+
clearTimeout(timeout);
|
|
471
|
+
resolve();
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
catch (e) {
|
|
476
|
+
// Ignore cleanup errors
|
|
477
|
+
}
|
|
478
|
+
this.process = null;
|
|
479
|
+
}
|
|
480
|
+
// Close SSE response if exists
|
|
481
|
+
if (this.sseResponse) {
|
|
482
|
+
try {
|
|
483
|
+
await this.sseResponse.close?.();
|
|
484
|
+
}
|
|
485
|
+
catch {
|
|
486
|
+
// Ignore
|
|
487
|
+
}
|
|
488
|
+
this.sseResponse = null;
|
|
489
|
+
}
|
|
490
|
+
this.sseMessageUrl = "";
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
exports.MCPClient = MCPClient;
|
|
494
|
+
/**
|
|
495
|
+
* Manages multiple MCP server connections and registers their tools.
|
|
496
|
+
*/
|
|
497
|
+
class MCPManager {
|
|
498
|
+
constructor(toolRegistry, log) {
|
|
499
|
+
this.clients = new Map();
|
|
500
|
+
this.serverConfigs = [];
|
|
501
|
+
this.agents = new Map();
|
|
502
|
+
this.log = null;
|
|
503
|
+
this.toolRegistry = toolRegistry;
|
|
504
|
+
this.log = log || null;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Bind live agent instances for tool refresh.
|
|
508
|
+
*/
|
|
509
|
+
bindAgents(agents) {
|
|
510
|
+
this.agents = agents;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Configure MCP servers from config data.
|
|
514
|
+
*/
|
|
515
|
+
configure(servers) {
|
|
516
|
+
this.serverConfigs = servers
|
|
517
|
+
.filter((s) => s.enabled !== false)
|
|
518
|
+
.map((s) => ({
|
|
519
|
+
name: s.name,
|
|
520
|
+
command: s.command,
|
|
521
|
+
args: s.args || [],
|
|
522
|
+
url: s.url,
|
|
523
|
+
env: s.env || {},
|
|
524
|
+
enabled: s.enabled !== false,
|
|
525
|
+
}));
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Connect to all configured MCP servers in parallel.
|
|
529
|
+
* Returns list of "server_name: N tools" status strings.
|
|
530
|
+
*/
|
|
531
|
+
async connectAll() {
|
|
532
|
+
const enabled = this.serverConfigs.filter((cfg) => cfg.enabled !== false);
|
|
533
|
+
if (enabled.length === 0) {
|
|
534
|
+
return [];
|
|
535
|
+
}
|
|
536
|
+
// Connect all servers in parallel
|
|
537
|
+
const results = await Promise.all(enabled.map(async (cfg) => {
|
|
538
|
+
const client = new MCPClient(cfg, this.log);
|
|
539
|
+
try {
|
|
540
|
+
const tools = await client.initialize();
|
|
541
|
+
if (!tools || tools.length === 0) {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
return { cfg, client, tools };
|
|
545
|
+
}
|
|
546
|
+
catch (e) {
|
|
547
|
+
this.log?.warn("mcp_init_failed", {
|
|
548
|
+
server: cfg.name,
|
|
549
|
+
error: String(e),
|
|
550
|
+
});
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
}));
|
|
554
|
+
// Register tools from successful connections
|
|
555
|
+
const statusLines = [];
|
|
556
|
+
for (const result of results) {
|
|
557
|
+
if (!result) {
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
const { cfg, client, tools } = result;
|
|
561
|
+
this.clients.set(cfg.name, client);
|
|
562
|
+
const count = this.registerMCPTools(cfg.name, tools);
|
|
563
|
+
statusLines.push(`${cfg.name}: ${count} tools`);
|
|
564
|
+
}
|
|
565
|
+
return statusLines;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Register MCP tools into the tool registry and agents.
|
|
569
|
+
*/
|
|
570
|
+
registerMCPTools(serverName, mcpTools) {
|
|
571
|
+
let count = 0;
|
|
572
|
+
for (const mt of mcpTools) {
|
|
573
|
+
const name = mt.name;
|
|
574
|
+
const description = mt.description || "";
|
|
575
|
+
const inputSchema = mt.inputSchema || {};
|
|
576
|
+
if (!name) {
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
// Convert MCP tool schema to internal tool format
|
|
580
|
+
const parameters = [];
|
|
581
|
+
const props = inputSchema.properties || {};
|
|
582
|
+
const required = inputSchema.required || [];
|
|
583
|
+
for (const [paramName, paramSchema] of Object.entries(props)) {
|
|
584
|
+
parameters.push({
|
|
585
|
+
name: paramName,
|
|
586
|
+
type: paramSchema.type || "string",
|
|
587
|
+
description: paramSchema.description || "",
|
|
588
|
+
required: required.includes(paramName),
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
// Create tool wrapper
|
|
592
|
+
const mcpToolName = `mcp_${serverName}_${name}`;
|
|
593
|
+
const tool = {
|
|
594
|
+
name: mcpToolName,
|
|
595
|
+
description: `[MCP/${serverName}] ${description}`,
|
|
596
|
+
parameters,
|
|
597
|
+
handler: this.makeMCPHandler(serverName, name),
|
|
598
|
+
};
|
|
599
|
+
// Register in all registries
|
|
600
|
+
this.toolRegistry.register(tool);
|
|
601
|
+
for (const agent of this.agents.values()) {
|
|
602
|
+
const reg = agent.toolRegistry;
|
|
603
|
+
if (reg) {
|
|
604
|
+
reg.register(tool);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
count++;
|
|
608
|
+
}
|
|
609
|
+
// Refresh agents' tool lists
|
|
610
|
+
for (const agent of this.agents.values()) {
|
|
611
|
+
try {
|
|
612
|
+
agent.refreshTools?.();
|
|
613
|
+
}
|
|
614
|
+
catch {
|
|
615
|
+
// Ignore refresh errors
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return count;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Create a handler function that calls an MCP tool.
|
|
622
|
+
*/
|
|
623
|
+
makeMCPHandler(serverName, toolName) {
|
|
624
|
+
return async (kwargs) => {
|
|
625
|
+
const client = this.clients.get(serverName);
|
|
626
|
+
if (!client) {
|
|
627
|
+
return `Error: MCP server '${serverName}' not connected.`;
|
|
628
|
+
}
|
|
629
|
+
return client.callTool(toolName, kwargs);
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Add a new MCP server at runtime.
|
|
634
|
+
*/
|
|
635
|
+
async addServer(config) {
|
|
636
|
+
const name = config.name?.trim();
|
|
637
|
+
if (!name) {
|
|
638
|
+
return "Error: server name is required";
|
|
639
|
+
}
|
|
640
|
+
if (this.clients.has(name)) {
|
|
641
|
+
return `MCP server '${name}' is already connected`;
|
|
642
|
+
}
|
|
643
|
+
if (!config.command && !config.url) {
|
|
644
|
+
return "Error: provide either 'command' (stdio) or 'url' (SSE)";
|
|
645
|
+
}
|
|
646
|
+
const client = new MCPClient(config, this.log);
|
|
647
|
+
try {
|
|
648
|
+
const tools = await client.initialize();
|
|
649
|
+
if (!tools || tools.length === 0) {
|
|
650
|
+
await client.close();
|
|
651
|
+
return `MCP server '${name}' 未返回任何工具`;
|
|
652
|
+
}
|
|
653
|
+
this.clients.set(name, client);
|
|
654
|
+
this.serverConfigs.push(config);
|
|
655
|
+
const count = this.registerMCPTools(name, tools);
|
|
656
|
+
const toolNames = tools
|
|
657
|
+
.slice(0, 10)
|
|
658
|
+
.map((t) => t.name)
|
|
659
|
+
.join(", ");
|
|
660
|
+
return `✓ 已接入 MCP server '${name}',注册 ${count} 个工具: ${toolNames}`;
|
|
661
|
+
}
|
|
662
|
+
catch (e) {
|
|
663
|
+
await client.close();
|
|
664
|
+
return `连接 MCP server '${name}' 失败: ${String(e)}`;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Remove an MCP server and unregister its tools.
|
|
669
|
+
*/
|
|
670
|
+
async removeServer(name) {
|
|
671
|
+
const cleanName = name?.trim();
|
|
672
|
+
const client = this.clients.get(cleanName);
|
|
673
|
+
if (!client) {
|
|
674
|
+
return `未连接 MCP server '${cleanName}'`;
|
|
675
|
+
}
|
|
676
|
+
await client.close();
|
|
677
|
+
this.clients.delete(cleanName);
|
|
678
|
+
this.serverConfigs = this.serverConfigs.filter((c) => c.name !== cleanName);
|
|
679
|
+
// Unregister tools
|
|
680
|
+
const prefix = `mcp_${cleanName}_`;
|
|
681
|
+
let removed = 0;
|
|
682
|
+
// Get all tool names with this prefix
|
|
683
|
+
const toolNames = this.toolRegistry.listNames?.() || [];
|
|
684
|
+
for (const toolName of toolNames) {
|
|
685
|
+
if (toolName.startsWith(prefix)) {
|
|
686
|
+
this.toolRegistry.unregister(toolName);
|
|
687
|
+
for (const agent of this.agents.values()) {
|
|
688
|
+
agent.toolRegistry?.unregister(toolName);
|
|
689
|
+
}
|
|
690
|
+
removed++;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
// Refresh agents
|
|
694
|
+
for (const agent of this.agents.values()) {
|
|
695
|
+
try {
|
|
696
|
+
agent.refreshTools?.();
|
|
697
|
+
}
|
|
698
|
+
catch {
|
|
699
|
+
// Ignore
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return `✓ 已断开 MCP server '${cleanName}',移除 ${removed} 个工具`;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Get list of connected servers and their tool counts.
|
|
706
|
+
*/
|
|
707
|
+
listServers() {
|
|
708
|
+
const result = [];
|
|
709
|
+
for (const cfg of this.serverConfigs) {
|
|
710
|
+
const prefix = `mcp_${cfg.name}_`;
|
|
711
|
+
const toolNames = this.toolRegistry.listNames?.() || [];
|
|
712
|
+
const count = toolNames.filter((n) => n.startsWith(prefix))
|
|
713
|
+
.length;
|
|
714
|
+
result.push({
|
|
715
|
+
name: cfg.name,
|
|
716
|
+
transport: cfg.command ? "stdio" : "sse",
|
|
717
|
+
target: cfg.command || cfg.url || "",
|
|
718
|
+
tools: count,
|
|
719
|
+
connected: this.clients.has(cfg.name),
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
return result;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Close all server connections and clean up.
|
|
726
|
+
*/
|
|
727
|
+
async closeAll() {
|
|
728
|
+
const errors = [];
|
|
729
|
+
for (const [, client] of this.clients) {
|
|
730
|
+
try {
|
|
731
|
+
await client.close();
|
|
732
|
+
}
|
|
733
|
+
catch (e) {
|
|
734
|
+
errors.push({
|
|
735
|
+
server: client["config"]?.name || "unknown",
|
|
736
|
+
error: String(e),
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
this.clients.clear();
|
|
741
|
+
// Log any errors that occurred during cleanup
|
|
742
|
+
for (const err of errors) {
|
|
743
|
+
this.log?.warn("mcp_close_failed", {
|
|
744
|
+
server: err.server,
|
|
745
|
+
error: err.error,
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
exports.MCPManager = MCPManager;
|
|
751
|
+
/**
|
|
752
|
+
* Persistence helpers for runtime-added MCP servers.
|
|
753
|
+
*/
|
|
754
|
+
/**
|
|
755
|
+
* Get path to persisted MCP servers file.
|
|
756
|
+
*/
|
|
757
|
+
function getPersistPath() {
|
|
758
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
759
|
+
return path.join(home, ".skyloom", "mcp_servers.json");
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Load persisted MCP server configs.
|
|
763
|
+
*/
|
|
764
|
+
function loadPersistedServers() {
|
|
765
|
+
try {
|
|
766
|
+
const persistPath = getPersistPath();
|
|
767
|
+
if (!fs.existsSync(persistPath)) {
|
|
768
|
+
return [];
|
|
769
|
+
}
|
|
770
|
+
const data = JSON.parse(fs.readFileSync(persistPath, "utf-8"));
|
|
771
|
+
return Array.isArray(data) ? data : [];
|
|
772
|
+
}
|
|
773
|
+
catch {
|
|
774
|
+
return [];
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Save MCP server config (add or update).
|
|
779
|
+
*/
|
|
780
|
+
function savePersistedServer(config) {
|
|
781
|
+
const name = config.name?.trim();
|
|
782
|
+
if (!name) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const persistPath = getPersistPath();
|
|
786
|
+
const dir = path.dirname(persistPath);
|
|
787
|
+
// Ensure directory exists
|
|
788
|
+
if (!fs.existsSync(dir)) {
|
|
789
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
790
|
+
}
|
|
791
|
+
// Load existing, update/add entry
|
|
792
|
+
const items = loadPersistedServers();
|
|
793
|
+
const filtered = items.filter((s) => s.name !== name);
|
|
794
|
+
filtered.push(config);
|
|
795
|
+
// Write atomically
|
|
796
|
+
const tmpPath = persistPath + ".tmp";
|
|
797
|
+
fs.writeFileSync(tmpPath, JSON.stringify(filtered, null, 2), "utf-8");
|
|
798
|
+
fs.renameSync(tmpPath, persistPath);
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Remove persisted MCP server config.
|
|
802
|
+
*/
|
|
803
|
+
function removePersistedServer(name) {
|
|
804
|
+
const persistPath = getPersistPath();
|
|
805
|
+
const items = loadPersistedServers();
|
|
806
|
+
const filtered = items.filter((s) => s.name !== name);
|
|
807
|
+
if (filtered.length === 0 && fs.existsSync(persistPath)) {
|
|
808
|
+
try {
|
|
809
|
+
fs.unlinkSync(persistPath);
|
|
810
|
+
}
|
|
811
|
+
catch {
|
|
812
|
+
// Ignore
|
|
813
|
+
}
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if (fs.existsSync(path.dirname(persistPath))) {
|
|
817
|
+
const tmpPath = persistPath + ".tmp";
|
|
818
|
+
fs.writeFileSync(tmpPath, JSON.stringify(filtered, null, 2), "utf-8");
|
|
819
|
+
fs.renameSync(tmpPath, persistPath);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
//# sourceMappingURL=mcp.js.map
|