indusagi 0.12.34 → 0.13.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/dist/agent.js +1247 -184
- package/dist/ai.js +72 -4
- package/dist/capabilities.js +69 -2
- package/dist/cli.js +83 -13
- package/dist/connectors-saas.js +66 -0
- package/dist/index.js +83 -13
- package/dist/interop.js +66 -0
- package/dist/mcp.js +270 -363
- package/dist/react-ink.js +15 -11
- package/dist/shell-app.js +83 -13
- package/dist/smithy.js +69 -2
- package/dist/swarm.js +69 -2
- package/dist/types/capabilities/backends/node-backends.d.ts +3 -1
- package/dist/types/capabilities/files/read-state-gate.d.ts +69 -0
- package/dist/types/capabilities/files/read-state-gate.test.d.ts +14 -0
- package/dist/types/capabilities/kernel/context.d.ts +4 -0
- package/dist/types/capabilities/kernel/index.d.ts +2 -2
- package/dist/types/capabilities/kernel/spec.d.ts +55 -0
- package/dist/types/facade/bot/actions/bash.d.ts +15 -0
- package/dist/types/facade/bot/actions/bash.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/checkpoint.d.ts +49 -0
- package/dist/types/facade/bot/actions/checkpoint.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/edit-utils.d.ts +86 -0
- package/dist/types/facade/bot/actions/edit.d.ts +18 -0
- package/dist/types/facade/bot/actions/edit.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/find.d.ts +2 -0
- package/dist/types/facade/bot/actions/find.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/grep.d.ts +10 -0
- package/dist/types/facade/bot/actions/grep.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/index.d.ts +16 -0
- package/dist/types/facade/bot/actions/read-state.d.ts +83 -0
- package/dist/types/facade/bot/actions/read-state.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/read.d.ts +7 -0
- package/dist/types/facade/bot/actions/read.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/sandbox-backend.d.ts +99 -0
- package/dist/types/facade/bot/actions/sandbox-backend.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/websearch.d.ts +5 -2
- package/dist/types/facade/bot/actions/websearch.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/write.d.ts +15 -0
- package/dist/types/facade/bot/agent-loop.d.ts +10 -0
- package/dist/types/facade/bot/agent-loop.test.d.ts +1 -0
- package/dist/types/facade/bot/agent.d.ts +9 -1
- package/dist/types/facade/bot/permission-gate.test.d.ts +1 -0
- package/dist/types/facade/bot/types.d.ts +60 -0
- package/dist/types/facade/mcp-core/client.d.ts +71 -15
- package/dist/types/facade/mcp-core/client.test.d.ts +18 -0
- package/dist/types/facade/mcp-core/types.d.ts +10 -0
- package/dist/types/facade/ml/adapters/anthropic-retry.test.d.ts +1 -0
- package/dist/types/facade/ml/adapters/anthropic.d.ts +17 -0
- package/dist/types/facade/ml/adapters/simple-options.d.ts +13 -0
- package/dist/types/facade/ml/adapters/simple-options.test.d.ts +1 -0
- package/dist/types/react-ink/components/StatusLine.d.ts +10 -1
- package/dist/types/react-ink/components/ToolEventBlock.d.ts +2 -1
- package/dist/types/react-ink/components/ToolEventBlock.test.d.ts +1 -0
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
-
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
-
});
|
|
7
|
-
|
|
8
1
|
// src/facade/mcp-core/client.ts
|
|
9
|
-
import {
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
5
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
6
|
+
import {
|
|
7
|
+
ListRootsRequestSchema,
|
|
8
|
+
ElicitRequestSchema,
|
|
9
|
+
CreateMessageRequestSchema,
|
|
10
|
+
PingRequestSchema,
|
|
11
|
+
ToolListChangedNotificationSchema,
|
|
12
|
+
ResourceListChangedNotificationSchema,
|
|
13
|
+
ResourceUpdatedNotificationSchema,
|
|
14
|
+
PromptListChangedNotificationSchema,
|
|
15
|
+
ProgressNotificationSchema,
|
|
16
|
+
LoggingMessageNotificationSchema,
|
|
17
|
+
CancelledNotificationSchema
|
|
18
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
19
|
|
|
11
20
|
// src/facade/mcp-core/errors.ts
|
|
12
21
|
var MCPErrorCode = /* @__PURE__ */ ((MCPErrorCode2) => {
|
|
@@ -114,6 +123,7 @@ function createSchemaConversionError(toolName, details, serverName) {
|
|
|
114
123
|
|
|
115
124
|
// src/facade/mcp-core/client.ts
|
|
116
125
|
var DEFAULT_REQUEST_TIMEOUT = 6e4;
|
|
126
|
+
var DEFAULT_CONNECT_TIMEOUT = 3e4;
|
|
117
127
|
var MCPClient = class {
|
|
118
128
|
constructor(options) {
|
|
119
129
|
this.options = options;
|
|
@@ -126,10 +136,8 @@ var MCPClient = class {
|
|
|
126
136
|
this._roots = options.roots ?? [];
|
|
127
137
|
}
|
|
128
138
|
options;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
messageId = 0;
|
|
132
|
-
pendingRequests = /* @__PURE__ */ new Map();
|
|
139
|
+
client;
|
|
140
|
+
transport;
|
|
133
141
|
isConnected = false;
|
|
134
142
|
connectionPromise = null;
|
|
135
143
|
serverCapabilities;
|
|
@@ -137,6 +145,19 @@ var MCPClient = class {
|
|
|
137
145
|
enableServerLogs;
|
|
138
146
|
enableProgressTracking;
|
|
139
147
|
_roots;
|
|
148
|
+
// --- Host-overridable handlers (Core of #13) --------------------------------
|
|
149
|
+
// Server-initiated REQUESTS are answered by request handlers registered on the
|
|
150
|
+
// SDK client; the handlers delegate to these host-supplied callbacks when set,
|
|
151
|
+
// and fall back to sensible defaults otherwise.
|
|
152
|
+
elicitationHandler;
|
|
153
|
+
samplingHandler;
|
|
154
|
+
// Server-initiated NOTIFICATIONS are dispatched to these (so the host can, for
|
|
155
|
+
// example, re-list tools when a `tools/list_changed` arrives).
|
|
156
|
+
resourceUpdatedHandler;
|
|
157
|
+
resourceListChangedHandler;
|
|
158
|
+
toolListChangedHandler;
|
|
159
|
+
promptListChangedHandler;
|
|
160
|
+
progressHandler;
|
|
140
161
|
/** Server name */
|
|
141
162
|
serverName;
|
|
142
163
|
/** Server config */
|
|
@@ -163,106 +184,169 @@ var MCPClient = class {
|
|
|
163
184
|
}
|
|
164
185
|
}
|
|
165
186
|
async doConnect() {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
const initResult = await this.sendRequest("initialize", {
|
|
179
|
-
protocolVersion: "2024-11-05",
|
|
180
|
-
capabilities: {
|
|
181
|
-
...this._roots.length > 0 ? { roots: { listChanged: true } } : {},
|
|
182
|
-
elicitation: {}
|
|
183
|
-
},
|
|
184
|
-
clientInfo: {
|
|
185
|
-
name: "new_indusvx",
|
|
186
|
-
version: "1.0.0"
|
|
187
|
+
const transport = this.createTransport(this.config);
|
|
188
|
+
this.transport = transport;
|
|
189
|
+
const client = new Client(
|
|
190
|
+
{ name: "indusagi-coding-agent", version: "0.13.0" },
|
|
191
|
+
{
|
|
192
|
+
capabilities: {
|
|
193
|
+
roots: { listChanged: true },
|
|
194
|
+
elicitation: {},
|
|
195
|
+
sampling: {}
|
|
196
|
+
}
|
|
187
197
|
}
|
|
188
|
-
|
|
189
|
-
this.
|
|
198
|
+
);
|
|
199
|
+
this.client = client;
|
|
200
|
+
this.registerServerHandlers(client);
|
|
201
|
+
const prevOnClose = transport.onclose;
|
|
202
|
+
transport.onclose = () => {
|
|
203
|
+
this.isConnected = false;
|
|
204
|
+
this.log("debug", "Transport closed");
|
|
205
|
+
prevOnClose?.();
|
|
206
|
+
};
|
|
207
|
+
await client.connect(transport, { timeout: DEFAULT_CONNECT_TIMEOUT });
|
|
208
|
+
this.serverCapabilities = client.getServerCapabilities();
|
|
190
209
|
this.isConnected = true;
|
|
191
|
-
await this.sendNotification("notifications/initialized", {});
|
|
192
210
|
this.log("info", `Connected to MCP server`);
|
|
193
211
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
212
|
+
/**
|
|
213
|
+
* Build the SDK transport for the configured server.
|
|
214
|
+
*
|
|
215
|
+
* - stdio: `StdioClientTransport` (inherits the parent env, merges config.env).
|
|
216
|
+
* - http: `StreamableHTTPClientTransport` (POST + server-push SSE channel,
|
|
217
|
+
* reconnect + session-expiry handled by the SDK), unless the config
|
|
218
|
+
* asks for the legacy `SSEClientTransport`.
|
|
219
|
+
*/
|
|
220
|
+
createTransport(config) {
|
|
221
|
+
if ("command" in config) {
|
|
222
|
+
return new StdioClientTransport({
|
|
223
|
+
command: config.command,
|
|
224
|
+
args: config.args ?? [],
|
|
225
|
+
env: { ...sanitizeEnv(process.env), ...config.env ?? {} },
|
|
203
226
|
cwd: config.cwd,
|
|
204
|
-
|
|
205
|
-
});
|
|
206
|
-
this.process.stdout?.on("data", (data) => {
|
|
207
|
-
this.buffer += data.toString("utf-8");
|
|
208
|
-
this.processBuffer();
|
|
209
|
-
});
|
|
210
|
-
this.process.stderr?.on("data", (data) => {
|
|
211
|
-
const message = data.toString("utf-8").trim();
|
|
212
|
-
if (message) {
|
|
213
|
-
this.log("debug", `[stderr] ${message}`);
|
|
214
|
-
}
|
|
227
|
+
stderr: "pipe"
|
|
215
228
|
});
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
229
|
+
}
|
|
230
|
+
if ("url" in config) {
|
|
231
|
+
const requestInit = config.headers ? { headers: config.headers } : void 0;
|
|
232
|
+
if (config.transport === "sse") {
|
|
233
|
+
return new SSEClientTransport(config.url, { requestInit });
|
|
234
|
+
}
|
|
235
|
+
return new StreamableHTTPClientTransport(config.url, { requestInit });
|
|
236
|
+
}
|
|
237
|
+
throw new MCPError(
|
|
238
|
+
"Server configuration must include either a command or a url",
|
|
239
|
+
"CONFIG_ERROR" /* CONFIG_ERROR */,
|
|
240
|
+
void 0,
|
|
241
|
+
{ serverName: this.serverName }
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Register handlers for server-initiated REQUESTS and NOTIFICATIONS.
|
|
246
|
+
*
|
|
247
|
+
* REQUESTS (have both `.method` AND `.id`) must be ANSWERED with a JSON-RPC
|
|
248
|
+
* RESPONSE carrying the matching id — the SDK does this automatically for the
|
|
249
|
+
* value a request handler returns (or rejects). The previous hand-rolled
|
|
250
|
+
* transport routed these into the notification path, so they were never
|
|
251
|
+
* answered (bug #13).
|
|
252
|
+
*
|
|
253
|
+
* Defaults:
|
|
254
|
+
* - ping → {}
|
|
255
|
+
* - roots/list → the configured roots
|
|
256
|
+
* - elicitation/create→ decline ({action:"cancel"}) unless a host handler is set
|
|
257
|
+
* - sampling → reject "Method not found" unless a host handler is set
|
|
258
|
+
*/
|
|
259
|
+
registerServerHandlers(client) {
|
|
260
|
+
client.setRequestHandler(PingRequestSchema, async () => ({}));
|
|
261
|
+
client.setRequestHandler(ListRootsRequestSchema, async () => ({
|
|
262
|
+
roots: this._roots.map((r) => ({ uri: r.uri, name: r.name }))
|
|
263
|
+
}));
|
|
264
|
+
client.setRequestHandler(ElicitRequestSchema, async (request) => {
|
|
265
|
+
if (!this.elicitationHandler) {
|
|
266
|
+
return { action: "cancel" };
|
|
267
|
+
}
|
|
268
|
+
const params = request.params;
|
|
269
|
+
const elicitRequest = {
|
|
270
|
+
message: params.message,
|
|
271
|
+
requestedSchema: params.requestedSchema ?? {}
|
|
272
|
+
};
|
|
273
|
+
const result = await this.elicitationHandler(elicitRequest);
|
|
274
|
+
return mapToSdkElicitResult(result);
|
|
275
|
+
});
|
|
276
|
+
client.setRequestHandler(CreateMessageRequestSchema, async (request) => {
|
|
277
|
+
if (!this.samplingHandler) {
|
|
278
|
+
throw new MCPError(
|
|
279
|
+
"Method not found: sampling/createMessage",
|
|
280
|
+
"SERVER_ERROR" /* SERVER_ERROR */,
|
|
281
|
+
{ method: "sampling/createMessage" },
|
|
282
|
+
{ serverName: this.serverName }
|
|
222
283
|
);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
284
|
+
}
|
|
285
|
+
return await this.samplingHandler(request.params);
|
|
286
|
+
});
|
|
287
|
+
client.setNotificationHandler(ToolListChangedNotificationSchema, () => {
|
|
288
|
+
this.log("debug", "Tool list changed");
|
|
289
|
+
this.toolListChangedHandler?.();
|
|
290
|
+
});
|
|
291
|
+
client.setNotificationHandler(ResourceListChangedNotificationSchema, () => {
|
|
292
|
+
this.log("debug", "Resource list changed");
|
|
293
|
+
this.resourceListChangedHandler?.();
|
|
294
|
+
});
|
|
295
|
+
client.setNotificationHandler(ResourceUpdatedNotificationSchema, (n) => {
|
|
296
|
+
const uri = n.params?.uri;
|
|
297
|
+
this.log("debug", `Resource updated: ${uri}`);
|
|
298
|
+
if (uri) this.resourceUpdatedHandler?.({ uri });
|
|
299
|
+
});
|
|
300
|
+
client.setNotificationHandler(PromptListChangedNotificationSchema, () => {
|
|
301
|
+
this.log("debug", "Prompt list changed");
|
|
302
|
+
this.promptListChangedHandler?.();
|
|
303
|
+
});
|
|
304
|
+
client.setNotificationHandler(ProgressNotificationSchema, (n) => {
|
|
305
|
+
const params = n.params;
|
|
306
|
+
this.log("debug", `Progress: ${JSON.stringify(params)}`);
|
|
307
|
+
if (this.progressHandler && params) {
|
|
308
|
+
const notification = {
|
|
309
|
+
progressToken: params.progressToken,
|
|
310
|
+
progress: params.progress,
|
|
311
|
+
total: params.total,
|
|
312
|
+
message: params.message
|
|
313
|
+
};
|
|
314
|
+
this.progressHandler(notification);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
client.setNotificationHandler(LoggingMessageNotificationSchema, (n) => {
|
|
318
|
+
if (this.enableServerLogs && n.params) {
|
|
319
|
+
const { level, ...rest } = n.params;
|
|
320
|
+
this.log(level || "info", "[SERVER]", rest);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
client.setNotificationHandler(CancelledNotificationSchema, (n) => {
|
|
324
|
+
this.log("debug", `Request cancelled: ${JSON.stringify(n.params)}`);
|
|
237
325
|
});
|
|
238
326
|
}
|
|
239
327
|
/**
|
|
240
328
|
* Disconnect from the MCP server.
|
|
329
|
+
*
|
|
330
|
+
* The SDK's `client.close()` closes the transport and rejects any pending
|
|
331
|
+
* requests — a clean teardown for both stdio (graceful subprocess shutdown)
|
|
332
|
+
* and HTTP (close the push channel / end the session).
|
|
241
333
|
*/
|
|
242
334
|
async disconnect() {
|
|
243
|
-
if (
|
|
335
|
+
if (!this.client) {
|
|
336
|
+
this.log("debug", "Disconnect called but not connected");
|
|
244
337
|
this.isConnected = false;
|
|
245
|
-
this.log("debug", "Disconnected from HTTP MCP server");
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
if (!this.process) {
|
|
249
|
-
this.log("debug", "Disconnect called but no process was running");
|
|
250
338
|
return;
|
|
251
339
|
}
|
|
252
340
|
this.log("debug", "Disconnecting from MCP server");
|
|
253
341
|
try {
|
|
254
|
-
this.
|
|
255
|
-
this.process = void 0;
|
|
256
|
-
this.isConnected = false;
|
|
257
|
-
for (const [id, { reject, timeout }] of this.pendingRequests) {
|
|
258
|
-
clearTimeout(timeout);
|
|
259
|
-
reject(new MCPError("Connection closed", "NOT_CONNECTED" /* NOT_CONNECTED */));
|
|
260
|
-
}
|
|
261
|
-
this.pendingRequests.clear();
|
|
262
|
-
this.log("debug", "Successfully disconnected");
|
|
342
|
+
await this.client.close();
|
|
263
343
|
} catch (error) {
|
|
264
344
|
this.log("error", `Error during disconnect: ${error}`);
|
|
265
|
-
|
|
345
|
+
} finally {
|
|
346
|
+
this.client = void 0;
|
|
347
|
+
this.transport = void 0;
|
|
348
|
+
this.isConnected = false;
|
|
349
|
+
this.log("debug", "Successfully disconnected");
|
|
266
350
|
}
|
|
267
351
|
}
|
|
268
352
|
/**
|
|
@@ -279,7 +363,9 @@ var MCPClient = class {
|
|
|
279
363
|
*/
|
|
280
364
|
async listTools() {
|
|
281
365
|
this.ensureConnected();
|
|
282
|
-
const result = await this.
|
|
366
|
+
const result = await this.client.listTools(void 0, {
|
|
367
|
+
timeout: this.timeout
|
|
368
|
+
});
|
|
283
369
|
return result.tools;
|
|
284
370
|
}
|
|
285
371
|
/**
|
|
@@ -289,15 +375,16 @@ var MCPClient = class {
|
|
|
289
375
|
this.ensureConnected();
|
|
290
376
|
this.log("debug", `Calling tool: ${name}`);
|
|
291
377
|
try {
|
|
292
|
-
const result = await this.
|
|
293
|
-
name,
|
|
294
|
-
|
|
295
|
-
|
|
378
|
+
const result = await this.client.callTool(
|
|
379
|
+
{ name, arguments: args },
|
|
380
|
+
void 0,
|
|
381
|
+
{ timeout: this.timeout }
|
|
382
|
+
);
|
|
296
383
|
this.log("debug", `Tool ${name} executed successfully`);
|
|
297
384
|
return result;
|
|
298
385
|
} catch (error) {
|
|
299
386
|
this.log("error", `Tool ${name} failed: ${error}`);
|
|
300
|
-
throw error;
|
|
387
|
+
throw this.wrapError(error);
|
|
301
388
|
}
|
|
302
389
|
}
|
|
303
390
|
// ========================================================================
|
|
@@ -311,7 +398,9 @@ var MCPClient = class {
|
|
|
311
398
|
if (!this.serverCapabilities?.resources) {
|
|
312
399
|
return [];
|
|
313
400
|
}
|
|
314
|
-
const result = await this.
|
|
401
|
+
const result = await this.client.listResources(void 0, {
|
|
402
|
+
timeout: this.timeout
|
|
403
|
+
});
|
|
315
404
|
return result.resources;
|
|
316
405
|
}
|
|
317
406
|
/**
|
|
@@ -319,21 +408,21 @@ var MCPClient = class {
|
|
|
319
408
|
*/
|
|
320
409
|
async readResource(uri) {
|
|
321
410
|
this.ensureConnected();
|
|
322
|
-
return await this.
|
|
411
|
+
return await this.client.readResource({ uri }, { timeout: this.timeout });
|
|
323
412
|
}
|
|
324
413
|
/**
|
|
325
414
|
* Subscribe to resource updates.
|
|
326
415
|
*/
|
|
327
416
|
async subscribeResource(uri) {
|
|
328
417
|
this.ensureConnected();
|
|
329
|
-
await this.
|
|
418
|
+
await this.client.subscribeResource({ uri }, { timeout: this.timeout });
|
|
330
419
|
}
|
|
331
420
|
/**
|
|
332
421
|
* Unsubscribe from resource updates.
|
|
333
422
|
*/
|
|
334
423
|
async unsubscribeResource(uri) {
|
|
335
424
|
this.ensureConnected();
|
|
336
|
-
await this.
|
|
425
|
+
await this.client.unsubscribeResource({ uri }, { timeout: this.timeout });
|
|
337
426
|
}
|
|
338
427
|
// ========================================================================
|
|
339
428
|
// Prompt Operations
|
|
@@ -346,7 +435,9 @@ var MCPClient = class {
|
|
|
346
435
|
if (!this.serverCapabilities?.prompts) {
|
|
347
436
|
return [];
|
|
348
437
|
}
|
|
349
|
-
const result = await this.
|
|
438
|
+
const result = await this.client.listPrompts(void 0, {
|
|
439
|
+
timeout: this.timeout
|
|
440
|
+
});
|
|
350
441
|
return result.prompts;
|
|
351
442
|
}
|
|
352
443
|
/**
|
|
@@ -354,7 +445,10 @@ var MCPClient = class {
|
|
|
354
445
|
*/
|
|
355
446
|
async getPrompt(name, args) {
|
|
356
447
|
this.ensureConnected();
|
|
357
|
-
return await this.
|
|
448
|
+
return await this.client.getPrompt(
|
|
449
|
+
{ name, arguments: args },
|
|
450
|
+
{ timeout: this.timeout }
|
|
451
|
+
);
|
|
358
452
|
}
|
|
359
453
|
// ========================================================================
|
|
360
454
|
// Roots Operations
|
|
@@ -371,49 +465,79 @@ var MCPClient = class {
|
|
|
371
465
|
async setRoots(roots) {
|
|
372
466
|
this.log("debug", `Updating roots to ${roots.length} entries`);
|
|
373
467
|
this._roots = [...roots];
|
|
374
|
-
if (this.isConnected) {
|
|
375
|
-
|
|
468
|
+
if (this.isConnected && this.client) {
|
|
469
|
+
try {
|
|
470
|
+
await this.client.sendRootsListChanged();
|
|
471
|
+
} catch (error) {
|
|
472
|
+
this.log("debug", `Failed to send roots/list_changed: ${error}`);
|
|
473
|
+
}
|
|
376
474
|
}
|
|
377
475
|
}
|
|
378
476
|
// ========================================================================
|
|
379
477
|
// Handler Registration
|
|
478
|
+
//
|
|
479
|
+
// Setters STORE the host handler (and, for notifications, the SDK handler
|
|
480
|
+
// registered in `registerServerHandlers` delegates to the stored callback).
|
|
481
|
+
// This is the host-overridable layer at the core of bug #13.
|
|
380
482
|
// ========================================================================
|
|
381
483
|
/**
|
|
382
484
|
* Set a handler for resource updated notifications.
|
|
383
485
|
*/
|
|
384
486
|
setResourceUpdatedHandler(handler) {
|
|
487
|
+
this.resourceUpdatedHandler = handler;
|
|
385
488
|
this.log("debug", "Resource updated handler registered");
|
|
386
489
|
}
|
|
387
490
|
/**
|
|
388
491
|
* Set a handler for resource list changed notifications.
|
|
389
492
|
*/
|
|
390
493
|
setResourceListChangedHandler(handler) {
|
|
494
|
+
this.resourceListChangedHandler = handler;
|
|
391
495
|
this.log("debug", "Resource list changed handler registered");
|
|
392
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* Set a handler for tool list changed notifications.
|
|
499
|
+
*/
|
|
500
|
+
setToolListChangedHandler(handler) {
|
|
501
|
+
this.toolListChangedHandler = handler;
|
|
502
|
+
this.log("debug", "Tool list changed handler registered");
|
|
503
|
+
}
|
|
393
504
|
/**
|
|
394
505
|
* Set a handler for prompt list changed notifications.
|
|
395
506
|
*/
|
|
396
507
|
setPromptListChangedHandler(handler) {
|
|
508
|
+
this.promptListChangedHandler = handler;
|
|
397
509
|
this.log("debug", "Prompt list changed handler registered");
|
|
398
510
|
}
|
|
399
511
|
/**
|
|
400
|
-
* Set a handler for elicitation requests.
|
|
512
|
+
* Set a handler for elicitation requests. When set, the server's
|
|
513
|
+
* `elicitation/create` REQUEST is answered with the host's decision; when
|
|
514
|
+
* unset the request is declined ({action:"cancel"}).
|
|
401
515
|
*/
|
|
402
516
|
setElicitationHandler(handler) {
|
|
517
|
+
this.elicitationHandler = handler;
|
|
403
518
|
this.log("debug", "Elicitation handler registered");
|
|
404
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* Set a handler for sampling (`sampling/createMessage`) requests. When set,
|
|
522
|
+
* the server request is answered with the host's result; when unset the
|
|
523
|
+
* request is rejected with "Method not found".
|
|
524
|
+
*/
|
|
525
|
+
setSamplingHandler(handler) {
|
|
526
|
+
this.samplingHandler = handler;
|
|
527
|
+
this.log("debug", "Sampling handler registered");
|
|
528
|
+
}
|
|
405
529
|
/**
|
|
406
530
|
* Set a handler for progress notifications.
|
|
407
531
|
*/
|
|
408
532
|
setProgressHandler(handler) {
|
|
533
|
+
this.progressHandler = handler;
|
|
409
534
|
this.log("debug", "Progress handler registered");
|
|
410
535
|
}
|
|
411
536
|
// ========================================================================
|
|
412
537
|
// Private Methods
|
|
413
538
|
// ========================================================================
|
|
414
539
|
ensureConnected() {
|
|
415
|
-
|
|
416
|
-
if (!this.isConnected || !isHttpTransport && !this.process) {
|
|
540
|
+
if (!this.isConnected || !this.client) {
|
|
417
541
|
throw new MCPError(
|
|
418
542
|
"Not connected to MCP server",
|
|
419
543
|
"NOT_CONNECTED" /* NOT_CONNECTED */,
|
|
@@ -422,258 +546,24 @@ var MCPClient = class {
|
|
|
422
546
|
);
|
|
423
547
|
}
|
|
424
548
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if (
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
);
|
|
439
|
-
}
|
|
440
|
-
return new Promise((resolve, reject) => {
|
|
441
|
-
const id = ++this.messageId;
|
|
442
|
-
const timeout = setTimeout(() => {
|
|
443
|
-
this.pendingRequests.delete(id);
|
|
444
|
-
reject(
|
|
445
|
-
new MCPError(
|
|
446
|
-
`Request timeout: ${method}`,
|
|
447
|
-
"TIMEOUT" /* TIMEOUT */,
|
|
448
|
-
{ method, id },
|
|
449
|
-
{ serverName: this.serverName }
|
|
450
|
-
)
|
|
451
|
-
);
|
|
452
|
-
}, this.timeout);
|
|
453
|
-
this.pendingRequests.set(id, {
|
|
454
|
-
resolve,
|
|
455
|
-
reject,
|
|
456
|
-
timeout
|
|
457
|
-
});
|
|
458
|
-
const message = {
|
|
459
|
-
jsonrpc: "2.0",
|
|
460
|
-
id,
|
|
461
|
-
method,
|
|
462
|
-
params
|
|
463
|
-
};
|
|
464
|
-
this.log("debug", `Sending request: ${method} (id: ${id})`);
|
|
465
|
-
try {
|
|
466
|
-
const messageStr = JSON.stringify(message) + "\n";
|
|
467
|
-
this.process.stdin.write(messageStr);
|
|
468
|
-
} catch (error) {
|
|
469
|
-
this.pendingRequests.delete(id);
|
|
470
|
-
clearTimeout(timeout);
|
|
471
|
-
reject(
|
|
472
|
-
new MCPError(
|
|
473
|
-
`Failed to send request: ${error}`,
|
|
474
|
-
"TRANSPORT_ERROR" /* TRANSPORT_ERROR */,
|
|
475
|
-
{ error: String(error) },
|
|
476
|
-
{ serverName: this.serverName }
|
|
477
|
-
)
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
async sendHttpRequest(method, params) {
|
|
483
|
-
const config = this.config;
|
|
484
|
-
const id = ++this.messageId;
|
|
485
|
-
const message = {
|
|
486
|
-
jsonrpc: "2.0",
|
|
487
|
-
id,
|
|
488
|
-
method,
|
|
489
|
-
params
|
|
490
|
-
};
|
|
491
|
-
this.log("debug", `Sending HTTP request: ${method} (id: ${id})`);
|
|
492
|
-
const controller = new AbortController();
|
|
493
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
494
|
-
try {
|
|
495
|
-
const response = await fetch(config.url.toString(), {
|
|
496
|
-
method: "POST",
|
|
497
|
-
headers: {
|
|
498
|
-
"Content-Type": "application/json",
|
|
499
|
-
"Accept": "application/json, text/event-stream",
|
|
500
|
-
...config.headers
|
|
501
|
-
},
|
|
502
|
-
body: JSON.stringify(message),
|
|
503
|
-
signal: controller.signal
|
|
549
|
+
/**
|
|
550
|
+
* Normalize an SDK/transport error into an MCPError. Session-expiry style
|
|
551
|
+
* failures (404 session / -32001) are surfaced as SESSION_ERROR so callers
|
|
552
|
+
* (the pool) can decide to reconnect; the SDK's StreamableHTTP transport
|
|
553
|
+
* already attempts a bounded reconnect re-using the session id before this.
|
|
554
|
+
*/
|
|
555
|
+
wrapError(error) {
|
|
556
|
+
if (error instanceof MCPError) return error;
|
|
557
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
558
|
+
const code = error.code;
|
|
559
|
+
if (code === -32001 || /\b404\b|session/i.test(message)) {
|
|
560
|
+
return new MCPError(message, "SESSION_ERROR" /* SESSION_ERROR */, { code }, {
|
|
561
|
+
serverName: this.serverName
|
|
504
562
|
});
|
|
505
|
-
clearTimeout(timeoutId);
|
|
506
|
-
if (!response.ok) {
|
|
507
|
-
const errorText = await response.text();
|
|
508
|
-
let errorMessage = `HTTP error: ${response.status} ${response.statusText}`;
|
|
509
|
-
try {
|
|
510
|
-
const errorJson = JSON.parse(errorText);
|
|
511
|
-
if (errorJson.error?.message) {
|
|
512
|
-
errorMessage = errorJson.error.message;
|
|
513
|
-
}
|
|
514
|
-
} catch {
|
|
515
|
-
if (errorText) {
|
|
516
|
-
errorMessage = errorText;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
throw new MCPError(
|
|
520
|
-
errorMessage,
|
|
521
|
-
"TRANSPORT_ERROR" /* TRANSPORT_ERROR */,
|
|
522
|
-
{ status: response.status, body: errorText },
|
|
523
|
-
{ serverName: this.serverName }
|
|
524
|
-
);
|
|
525
|
-
}
|
|
526
|
-
const responseText = await response.text();
|
|
527
|
-
let result;
|
|
528
|
-
if (responseText.startsWith("event:") || responseText.startsWith("data:")) {
|
|
529
|
-
const lines = responseText.split("\n");
|
|
530
|
-
let dataLine = "";
|
|
531
|
-
for (const line of lines) {
|
|
532
|
-
if (line.startsWith("data:")) {
|
|
533
|
-
dataLine = line.slice(5).trim();
|
|
534
|
-
break;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
if (dataLine) {
|
|
538
|
-
result = JSON.parse(dataLine);
|
|
539
|
-
} else {
|
|
540
|
-
throw new MCPError(
|
|
541
|
-
"Empty SSE response",
|
|
542
|
-
"TRANSPORT_ERROR" /* TRANSPORT_ERROR */,
|
|
543
|
-
{ response: responseText },
|
|
544
|
-
{ serverName: this.serverName }
|
|
545
|
-
);
|
|
546
|
-
}
|
|
547
|
-
} else {
|
|
548
|
-
result = JSON.parse(responseText);
|
|
549
|
-
}
|
|
550
|
-
if (result.error) {
|
|
551
|
-
throw new MCPError(
|
|
552
|
-
result.error.message || "MCP error",
|
|
553
|
-
"SERVER_ERROR" /* SERVER_ERROR */,
|
|
554
|
-
result.error,
|
|
555
|
-
{ serverName: this.serverName }
|
|
556
|
-
);
|
|
557
|
-
}
|
|
558
|
-
this.log("debug", `HTTP request ${id} succeeded`);
|
|
559
|
-
return result.result;
|
|
560
|
-
} catch (error) {
|
|
561
|
-
clearTimeout(timeoutId);
|
|
562
|
-
if (error instanceof MCPError) throw error;
|
|
563
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
564
|
-
throw new MCPError(
|
|
565
|
-
`Request timeout: ${method}`,
|
|
566
|
-
"TIMEOUT" /* TIMEOUT */,
|
|
567
|
-
{ method, id },
|
|
568
|
-
{ serverName: this.serverName }
|
|
569
|
-
);
|
|
570
|
-
}
|
|
571
|
-
throw new MCPError(
|
|
572
|
-
`HTTP request failed: ${error}`,
|
|
573
|
-
"TRANSPORT_ERROR" /* TRANSPORT_ERROR */,
|
|
574
|
-
{ error: String(error) },
|
|
575
|
-
{ serverName: this.serverName }
|
|
576
|
-
);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
async sendNotification(method, params) {
|
|
580
|
-
const message = {
|
|
581
|
-
jsonrpc: "2.0",
|
|
582
|
-
method,
|
|
583
|
-
params
|
|
584
|
-
};
|
|
585
|
-
this.log("debug", `Sending notification: ${method}`);
|
|
586
|
-
if ("url" in this.config) {
|
|
587
|
-
const config = this.config;
|
|
588
|
-
try {
|
|
589
|
-
await fetch(config.url.toString(), {
|
|
590
|
-
method: "POST",
|
|
591
|
-
headers: {
|
|
592
|
-
"Content-Type": "application/json",
|
|
593
|
-
...config.headers
|
|
594
|
-
},
|
|
595
|
-
body: JSON.stringify(message)
|
|
596
|
-
});
|
|
597
|
-
} catch (error) {
|
|
598
|
-
this.log("error", `Failed to send HTTP notification: ${error}`);
|
|
599
|
-
}
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
if (!this.process) {
|
|
603
|
-
throw new MCPError(
|
|
604
|
-
"Not connected to MCP server",
|
|
605
|
-
"NOT_CONNECTED" /* NOT_CONNECTED */,
|
|
606
|
-
void 0,
|
|
607
|
-
{ serverName: this.serverName }
|
|
608
|
-
);
|
|
609
|
-
}
|
|
610
|
-
try {
|
|
611
|
-
const messageStr = JSON.stringify(message) + "\n";
|
|
612
|
-
this.process.stdin.write(messageStr);
|
|
613
|
-
} catch (error) {
|
|
614
|
-
this.log("error", `Failed to send notification: ${error}`);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
processBuffer() {
|
|
618
|
-
const lines = this.buffer.split("\n");
|
|
619
|
-
this.buffer = lines.pop() || "";
|
|
620
|
-
for (const line of lines) {
|
|
621
|
-
const trimmed = line.trim();
|
|
622
|
-
if (!trimmed) continue;
|
|
623
|
-
try {
|
|
624
|
-
const message = JSON.parse(trimmed);
|
|
625
|
-
this.handleMessage(message);
|
|
626
|
-
} catch (error) {
|
|
627
|
-
this.log("warning", `Failed to parse message: ${trimmed}`);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
handleMessage(message) {
|
|
632
|
-
if (message.id !== void 0 && this.pendingRequests.has(message.id)) {
|
|
633
|
-
const { resolve, reject, timeout } = this.pendingRequests.get(message.id);
|
|
634
|
-
this.pendingRequests.delete(message.id);
|
|
635
|
-
clearTimeout(timeout);
|
|
636
|
-
if (message.error) {
|
|
637
|
-
this.log("debug", `Request ${message.id} failed: ${message.error.message}`);
|
|
638
|
-
reject(
|
|
639
|
-
new MCPError(
|
|
640
|
-
message.error.message || "Unknown error",
|
|
641
|
-
"SERVER_ERROR" /* SERVER_ERROR */,
|
|
642
|
-
message.error,
|
|
643
|
-
{ serverName: this.serverName }
|
|
644
|
-
)
|
|
645
|
-
);
|
|
646
|
-
} else {
|
|
647
|
-
this.log("debug", `Request ${message.id} succeeded`);
|
|
648
|
-
resolve(message.result);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
if (message.method) {
|
|
652
|
-
this.handleNotification(message.method, message.params);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
handleNotification(method, params) {
|
|
656
|
-
this.log("debug", `Received notification: ${method}`);
|
|
657
|
-
switch (method) {
|
|
658
|
-
case "notifications/message":
|
|
659
|
-
if (this.enableServerLogs && params) {
|
|
660
|
-
const { level, ...rest } = params;
|
|
661
|
-
this.log(level || "info", "[SERVER]", rest);
|
|
662
|
-
}
|
|
663
|
-
break;
|
|
664
|
-
case "notifications/progress":
|
|
665
|
-
this.log("debug", `Progress: ${JSON.stringify(params)}`);
|
|
666
|
-
break;
|
|
667
|
-
case "notifications/resources/updated":
|
|
668
|
-
this.log("debug", `Resource updated: ${JSON.stringify(params)}`);
|
|
669
|
-
break;
|
|
670
|
-
case "notifications/resources/list_changed":
|
|
671
|
-
this.log("debug", "Resource list changed");
|
|
672
|
-
break;
|
|
673
|
-
case "notifications/prompts/list_changed":
|
|
674
|
-
this.log("debug", "Prompt list changed");
|
|
675
|
-
break;
|
|
676
563
|
}
|
|
564
|
+
return new MCPError(message, "SERVER_ERROR" /* SERVER_ERROR */, { code }, {
|
|
565
|
+
serverName: this.serverName
|
|
566
|
+
});
|
|
677
567
|
}
|
|
678
568
|
log(level, message, details) {
|
|
679
569
|
const msg = `[${this.serverName}] ${message}`;
|
|
@@ -705,6 +595,22 @@ var MCPClient = class {
|
|
|
705
595
|
}
|
|
706
596
|
}
|
|
707
597
|
};
|
|
598
|
+
function mapToSdkElicitResult(result) {
|
|
599
|
+
if (result.action === "accept") {
|
|
600
|
+
return {
|
|
601
|
+
action: "accept",
|
|
602
|
+
content: result.content ?? {}
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
return { action: result.action };
|
|
606
|
+
}
|
|
607
|
+
function sanitizeEnv(env) {
|
|
608
|
+
const out = {};
|
|
609
|
+
for (const [k, v] of Object.entries(env)) {
|
|
610
|
+
if (typeof v === "string") out[k] = v;
|
|
611
|
+
}
|
|
612
|
+
return out;
|
|
613
|
+
}
|
|
708
614
|
|
|
709
615
|
// src/facade/mcp-core/client-pool.ts
|
|
710
616
|
var MCPClientPool = class {
|
|
@@ -906,14 +812,14 @@ var MCPClientPool = class {
|
|
|
906
812
|
};
|
|
907
813
|
|
|
908
814
|
// src/facade/mcp-core/config.ts
|
|
909
|
-
import { existsSync, readFileSync, mkdirSync } from "fs";
|
|
815
|
+
import { existsSync, readFileSync, mkdirSync, statSync, writeFileSync } from "fs";
|
|
910
816
|
import { join, dirname } from "path";
|
|
911
817
|
import { homedir } from "os";
|
|
912
818
|
function loadMCPConfig(configPathOrCwd = process.cwd()) {
|
|
913
819
|
const configs = [];
|
|
914
820
|
if (existsSync(configPathOrCwd)) {
|
|
915
821
|
try {
|
|
916
|
-
const stats =
|
|
822
|
+
const stats = statSync(configPathOrCwd);
|
|
917
823
|
if (stats.isFile()) {
|
|
918
824
|
const fileConfigs = parseConfigFile(configPathOrCwd);
|
|
919
825
|
configs.push(...fileConfigs);
|
|
@@ -991,7 +897,8 @@ function parseConfigFile(path) {
|
|
|
991
897
|
name: server.name,
|
|
992
898
|
config: {
|
|
993
899
|
url: new URL(server.url),
|
|
994
|
-
headers: server.headers
|
|
900
|
+
headers: server.headers,
|
|
901
|
+
...server.transport ? { transport: server.transport } : {}
|
|
995
902
|
},
|
|
996
903
|
timeout: server.timeout
|
|
997
904
|
};
|
|
@@ -1041,7 +948,7 @@ function saveConfig(path, config) {
|
|
|
1041
948
|
mkdirSync(dir, { recursive: true });
|
|
1042
949
|
}
|
|
1043
950
|
const content = JSON.stringify(config, null, 2);
|
|
1044
|
-
|
|
951
|
+
writeFileSync(path, content, "utf-8");
|
|
1045
952
|
}
|
|
1046
953
|
function saveUserConfig(config) {
|
|
1047
954
|
saveConfig(getUserConfigPath(), config);
|