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.
Files changed (225) hide show
  1. package/.github/workflows/ci.yml +36 -0
  2. package/CONVERSION_PLAN.md +191 -0
  3. package/README.md +67 -0
  4. package/dist/agents/dew.d.ts +15 -0
  5. package/dist/agents/dew.d.ts.map +1 -0
  6. package/dist/agents/dew.js +74 -0
  7. package/dist/agents/dew.js.map +1 -0
  8. package/dist/agents/fair.d.ts +15 -0
  9. package/dist/agents/fair.d.ts.map +1 -0
  10. package/dist/agents/fair.js +106 -0
  11. package/dist/agents/fair.js.map +1 -0
  12. package/dist/agents/fog.d.ts +15 -0
  13. package/dist/agents/fog.d.ts.map +1 -0
  14. package/dist/agents/fog.js +52 -0
  15. package/dist/agents/fog.js.map +1 -0
  16. package/dist/agents/frost.d.ts +15 -0
  17. package/dist/agents/frost.d.ts.map +1 -0
  18. package/dist/agents/frost.js +54 -0
  19. package/dist/agents/frost.js.map +1 -0
  20. package/dist/agents/rain.d.ts +15 -0
  21. package/dist/agents/rain.d.ts.map +1 -0
  22. package/dist/agents/rain.js +54 -0
  23. package/dist/agents/rain.js.map +1 -0
  24. package/dist/agents/snow.d.ts +27 -0
  25. package/dist/agents/snow.d.ts.map +1 -0
  26. package/dist/agents/snow.js +226 -0
  27. package/dist/agents/snow.js.map +1 -0
  28. package/dist/cli/main.d.ts +7 -0
  29. package/dist/cli/main.d.ts.map +1 -0
  30. package/dist/cli/main.js +402 -0
  31. package/dist/cli/main.js.map +1 -0
  32. package/dist/cli/mode.d.ts +17 -0
  33. package/dist/cli/mode.d.ts.map +1 -0
  34. package/dist/cli/mode.js +56 -0
  35. package/dist/cli/mode.js.map +1 -0
  36. package/dist/core/agent.d.ts +174 -0
  37. package/dist/core/agent.d.ts.map +1 -0
  38. package/dist/core/agent.js +1332 -0
  39. package/dist/core/agent.js.map +1 -0
  40. package/dist/core/agent_helpers.d.ts +51 -0
  41. package/dist/core/agent_helpers.d.ts.map +1 -0
  42. package/dist/core/agent_helpers.js +477 -0
  43. package/dist/core/agent_helpers.js.map +1 -0
  44. package/dist/core/bus.d.ts +99 -0
  45. package/dist/core/bus.d.ts.map +1 -0
  46. package/dist/core/bus.js +191 -0
  47. package/dist/core/bus.js.map +1 -0
  48. package/dist/core/cache.d.ts +63 -0
  49. package/dist/core/cache.d.ts.map +1 -0
  50. package/dist/core/cache.js +121 -0
  51. package/dist/core/cache.js.map +1 -0
  52. package/dist/core/checkpoint.d.ts +19 -0
  53. package/dist/core/checkpoint.d.ts.map +1 -0
  54. package/dist/core/checkpoint.js +120 -0
  55. package/dist/core/checkpoint.js.map +1 -0
  56. package/dist/core/circuit_breaker.d.ts +46 -0
  57. package/dist/core/circuit_breaker.d.ts.map +1 -0
  58. package/dist/core/circuit_breaker.js +99 -0
  59. package/dist/core/circuit_breaker.js.map +1 -0
  60. package/dist/core/config.d.ts +97 -0
  61. package/dist/core/config.d.ts.map +1 -0
  62. package/dist/core/config.js +281 -0
  63. package/dist/core/config.js.map +1 -0
  64. package/dist/core/constants.d.ts +78 -0
  65. package/dist/core/constants.d.ts.map +1 -0
  66. package/dist/core/constants.js +84 -0
  67. package/dist/core/constants.js.map +1 -0
  68. package/dist/core/factory.d.ts +63 -0
  69. package/dist/core/factory.d.ts.map +1 -0
  70. package/dist/core/factory.js +537 -0
  71. package/dist/core/factory.js.map +1 -0
  72. package/dist/core/icons.d.ts +28 -0
  73. package/dist/core/icons.d.ts.map +1 -0
  74. package/dist/core/icons.js +86 -0
  75. package/dist/core/icons.js.map +1 -0
  76. package/dist/core/index.d.ts +29 -0
  77. package/dist/core/index.d.ts.map +1 -0
  78. package/dist/core/index.js +54 -0
  79. package/dist/core/index.js.map +1 -0
  80. package/dist/core/llm.d.ts +121 -0
  81. package/dist/core/llm.d.ts.map +1 -0
  82. package/dist/core/llm.js +532 -0
  83. package/dist/core/llm.js.map +1 -0
  84. package/dist/core/logger.d.ts +57 -0
  85. package/dist/core/logger.d.ts.map +1 -0
  86. package/dist/core/logger.js +122 -0
  87. package/dist/core/logger.js.map +1 -0
  88. package/dist/core/mcp.d.ts +190 -0
  89. package/dist/core/mcp.d.ts.map +1 -0
  90. package/dist/core/mcp.js +822 -0
  91. package/dist/core/mcp.js.map +1 -0
  92. package/dist/core/mcp_server.d.ts +26 -0
  93. package/dist/core/mcp_server.d.ts.map +1 -0
  94. package/dist/core/mcp_server.js +211 -0
  95. package/dist/core/mcp_server.js.map +1 -0
  96. package/dist/core/memory.d.ts +190 -0
  97. package/dist/core/memory.d.ts.map +1 -0
  98. package/dist/core/memory.js +988 -0
  99. package/dist/core/memory.js.map +1 -0
  100. package/dist/core/middleware.d.ts +114 -0
  101. package/dist/core/middleware.d.ts.map +1 -0
  102. package/dist/core/middleware.js +248 -0
  103. package/dist/core/middleware.js.map +1 -0
  104. package/dist/core/pipelines.d.ts +87 -0
  105. package/dist/core/pipelines.d.ts.map +1 -0
  106. package/dist/core/pipelines.js +301 -0
  107. package/dist/core/pipelines.js.map +1 -0
  108. package/dist/core/profile.d.ts +23 -0
  109. package/dist/core/profile.d.ts.map +1 -0
  110. package/dist/core/profile.js +289 -0
  111. package/dist/core/profile.js.map +1 -0
  112. package/dist/core/router.d.ts +24 -0
  113. package/dist/core/router.d.ts.map +1 -0
  114. package/dist/core/router.js +111 -0
  115. package/dist/core/router.js.map +1 -0
  116. package/dist/core/schemas.d.ts +82 -0
  117. package/dist/core/schemas.d.ts.map +1 -0
  118. package/dist/core/schemas.js +200 -0
  119. package/dist/core/schemas.js.map +1 -0
  120. package/dist/core/semantic.d.ts +92 -0
  121. package/dist/core/semantic.d.ts.map +1 -0
  122. package/dist/core/semantic.js +175 -0
  123. package/dist/core/semantic.js.map +1 -0
  124. package/dist/core/skill.d.ts +68 -0
  125. package/dist/core/skill.d.ts.map +1 -0
  126. package/dist/core/skill.js +350 -0
  127. package/dist/core/skill.js.map +1 -0
  128. package/dist/core/tool.d.ts +99 -0
  129. package/dist/core/tool.d.ts.map +1 -0
  130. package/dist/core/tool.js +341 -0
  131. package/dist/core/tool.js.map +1 -0
  132. package/dist/core/tool_router.d.ts +29 -0
  133. package/dist/core/tool_router.d.ts.map +1 -0
  134. package/dist/core/tool_router.js +172 -0
  135. package/dist/core/tool_router.js.map +1 -0
  136. package/dist/core/workspace.d.ts +48 -0
  137. package/dist/core/workspace.d.ts.map +1 -0
  138. package/dist/core/workspace.js +179 -0
  139. package/dist/core/workspace.js.map +1 -0
  140. package/dist/plugins/loader.d.ts +17 -0
  141. package/dist/plugins/loader.d.ts.map +1 -0
  142. package/dist/plugins/loader.js +96 -0
  143. package/dist/plugins/loader.js.map +1 -0
  144. package/dist/skills/loader.d.ts +9 -0
  145. package/dist/skills/loader.d.ts.map +1 -0
  146. package/dist/skills/loader.js +78 -0
  147. package/dist/skills/loader.js.map +1 -0
  148. package/dist/tools/builtin.d.ts +10 -0
  149. package/dist/tools/builtin.d.ts.map +1 -0
  150. package/dist/tools/builtin.js +414 -0
  151. package/dist/tools/builtin.js.map +1 -0
  152. package/dist/tools/computer.d.ts +12 -0
  153. package/dist/tools/computer.d.ts.map +1 -0
  154. package/dist/tools/computer.js +326 -0
  155. package/dist/tools/computer.js.map +1 -0
  156. package/dist/tools/delegate.d.ts +10 -0
  157. package/dist/tools/delegate.d.ts.map +1 -0
  158. package/dist/tools/delegate.js +45 -0
  159. package/dist/tools/delegate.js.map +1 -0
  160. package/dist/web/server.d.ts +5 -0
  161. package/dist/web/server.d.ts.map +1 -0
  162. package/dist/web/server.js +647 -0
  163. package/dist/web/server.js.map +1 -0
  164. package/dist/web/tts.d.ts +33 -0
  165. package/dist/web/tts.d.ts.map +1 -0
  166. package/dist/web/tts.js +69 -0
  167. package/dist/web/tts.js.map +1 -0
  168. package/package.json +60 -0
  169. package/scripts/install.js +48 -0
  170. package/scripts/link.js +10 -0
  171. package/setup.bat +79 -0
  172. package/skill-test-ty2fOA/test.md +10 -0
  173. package/src/agents/dew.ts +70 -0
  174. package/src/agents/fair.ts +102 -0
  175. package/src/agents/fog.ts +48 -0
  176. package/src/agents/frost.ts +50 -0
  177. package/src/agents/rain.ts +50 -0
  178. package/src/agents/snow.ts +239 -0
  179. package/src/cli/main.ts +405 -0
  180. package/src/cli/mode.ts +58 -0
  181. package/src/core/agent.ts +1506 -0
  182. package/src/core/agent_helpers.ts +461 -0
  183. package/src/core/bus.ts +221 -0
  184. package/src/core/cache.ts +153 -0
  185. package/src/core/checkpoint.ts +94 -0
  186. package/src/core/circuit_breaker.ts +119 -0
  187. package/src/core/config.ts +341 -0
  188. package/src/core/constants.ts +95 -0
  189. package/src/core/factory.ts +627 -0
  190. package/src/core/icons.ts +53 -0
  191. package/src/core/index.ts +31 -0
  192. package/src/core/llm.ts +724 -0
  193. package/src/core/logger.ts +144 -0
  194. package/src/core/mcp.ts +953 -0
  195. package/src/core/mcp_server.ts +176 -0
  196. package/src/core/memory.ts +1169 -0
  197. package/src/core/middleware.ts +350 -0
  198. package/src/core/pipelines.ts +424 -0
  199. package/src/core/profile.ts +255 -0
  200. package/src/core/router.ts +124 -0
  201. package/src/core/schemas.ts +282 -0
  202. package/src/core/semantic.ts +211 -0
  203. package/src/core/skill.ts +342 -0
  204. package/src/core/tool.ts +427 -0
  205. package/src/core/tool_router.ts +193 -0
  206. package/src/core/workspace.ts +150 -0
  207. package/src/plugins/loader.ts +66 -0
  208. package/src/skills/loader.ts +46 -0
  209. package/src/sql.js.d.ts +29 -0
  210. package/src/tools/builtin.ts +382 -0
  211. package/src/tools/computer.ts +269 -0
  212. package/src/tools/delegate.ts +49 -0
  213. package/src/web/server.ts +634 -0
  214. package/src/web/tts.ts +93 -0
  215. package/tests/bus.test.ts +121 -0
  216. package/tests/icons.test.ts +45 -0
  217. package/tests/router.test.ts +86 -0
  218. package/tests/schemas.test.ts +51 -0
  219. package/tests/semantic.test.ts +83 -0
  220. package/tests/setup.ts +10 -0
  221. package/tests/skill.test.ts +172 -0
  222. package/tests/tool.test.ts +108 -0
  223. package/tests/tool_router.test.ts +71 -0
  224. package/tsconfig.json +37 -0
  225. package/vitest.config.ts +17 -0
@@ -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