indusagi 0.13.6 → 0.13.8
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/LICENSE +661 -0
- package/README.md +7 -1
- package/dist/cli.js +114 -14
- package/dist/index.js +1618 -51
- package/dist/llmgateway.js +114 -14
- package/dist/mcp.js +812 -3
- package/dist/runtime.js +114 -14
- package/dist/sarvam-mcp.js +960 -0
- package/dist/shell-app.js +116 -14
- package/dist/smithy.js +114 -14
- package/dist/swarm.js +114 -14
- package/dist/tracing.js +37 -0
- package/dist/types/connectors-sarvam/connect.d.ts +14 -0
- package/dist/types/connectors-sarvam/connectors-sarvam.test.d.ts +14 -0
- package/dist/types/connectors-sarvam/index.d.ts +14 -0
- package/dist/types/connectors-sarvam/toolbox.d.ts +19 -0
- package/dist/types/connectors-sarvam/types.d.ts +49 -0
- package/dist/types/connectors-zoho/connect.d.ts +22 -0
- package/dist/types/connectors-zoho/connectors-zoho.test.d.ts +10 -0
- package/dist/types/connectors-zoho/index.d.ts +13 -0
- package/dist/types/connectors-zoho/toolbox.d.ts +19 -0
- package/dist/types/connectors-zoho/types.d.ts +56 -0
- package/dist/types/facade/mcp-core/client.d.ts +32 -0
- package/dist/types/facade/mcp-core/index.d.ts +4 -0
- package/dist/types/facade/mcp-core/oauth/flow.d.ts +72 -0
- package/dist/types/facade/mcp-core/oauth/index.d.ts +18 -0
- package/dist/types/facade/mcp-core/oauth/oauth.test.d.ts +8 -0
- package/dist/types/facade/mcp-core/oauth/provider.d.ts +51 -0
- package/dist/types/facade/mcp-core/oauth/token-store.d.ts +31 -0
- package/dist/types/facade/mcp-core/oauth/types.d.ts +96 -0
- package/dist/types/facade/mcp-core/toolbox-bridge.d.ts +46 -0
- package/dist/types/facade/mcp-core/toolbox-bridge.test.d.ts +8 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/llmgateway/contract/model-card.d.ts +1 -1
- package/dist/types/llmgateway/credentials/secrets.d.ts +2 -0
- package/dist/zoho.js +1451 -0
- package/package.json +11 -4
- package/dist/knowledge/guides/authoring-an-agent.md +0 -53
- package/dist/knowledge/guides/choosing-tools.md +0 -49
- package/dist/knowledge/guides/model-selection.md +0 -51
- package/dist/knowledge/guides/writing-system-prompts.md +0 -53
- package/dist/knowledge/index.ts +0 -19
- package/dist/knowledge/loader.ts +0 -200
- package/dist/knowledge/manifest.json +0 -29
package/dist/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
2
8
|
var __export = (target, all) => {
|
|
3
9
|
for (var name in all)
|
|
4
10
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -261,6 +267,85 @@ var MODEL_CARDS = [
|
|
|
261
267
|
cacheReadPerMTok: 0.15
|
|
262
268
|
}
|
|
263
269
|
},
|
|
270
|
+
// ── MiniMax (Anthropic-compatible Messages API) ─────────────────────────
|
|
271
|
+
// Wire: same dialect as Claude (`anthropic-messages`) at api.minimax.io.
|
|
272
|
+
// Auth: MINIMAX_API_KEY. Ids match facade/models.generated.ts.
|
|
273
|
+
{
|
|
274
|
+
id: "MiniMax-M2",
|
|
275
|
+
provider: "minimax",
|
|
276
|
+
api: "anthropic-messages",
|
|
277
|
+
displayName: "MiniMax M2",
|
|
278
|
+
baseUrl: "https://api.minimax.io/anthropic",
|
|
279
|
+
contextWindow: 204800,
|
|
280
|
+
maxOutputTokens: 131072,
|
|
281
|
+
modalities: ["text"],
|
|
282
|
+
reasoning: true,
|
|
283
|
+
cost: {
|
|
284
|
+
inputPerMTok: 0.3,
|
|
285
|
+
outputPerMTok: 1.2
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: "MiniMax-M2.1",
|
|
290
|
+
provider: "minimax",
|
|
291
|
+
api: "anthropic-messages",
|
|
292
|
+
displayName: "MiniMax M2.1",
|
|
293
|
+
baseUrl: "https://api.minimax.io/anthropic",
|
|
294
|
+
contextWindow: 204800,
|
|
295
|
+
maxOutputTokens: 131072,
|
|
296
|
+
modalities: ["text"],
|
|
297
|
+
reasoning: true,
|
|
298
|
+
cost: {
|
|
299
|
+
inputPerMTok: 0.3,
|
|
300
|
+
outputPerMTok: 1.2
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
id: "MiniMax-M2.5",
|
|
305
|
+
provider: "minimax",
|
|
306
|
+
api: "anthropic-messages",
|
|
307
|
+
displayName: "MiniMax M2.5",
|
|
308
|
+
baseUrl: "https://api.minimax.io/anthropic",
|
|
309
|
+
contextWindow: 204800,
|
|
310
|
+
maxOutputTokens: 131072,
|
|
311
|
+
modalities: ["text"],
|
|
312
|
+
reasoning: true,
|
|
313
|
+
cost: {
|
|
314
|
+
inputPerMTok: 0.3,
|
|
315
|
+
outputPerMTok: 1.2
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: "MiniMax-M2.7",
|
|
320
|
+
provider: "minimax",
|
|
321
|
+
api: "anthropic-messages",
|
|
322
|
+
displayName: "MiniMax M2.7",
|
|
323
|
+
baseUrl: "https://api.minimax.io/anthropic",
|
|
324
|
+
contextWindow: 204800,
|
|
325
|
+
maxOutputTokens: 131072,
|
|
326
|
+
modalities: ["text"],
|
|
327
|
+
reasoning: true,
|
|
328
|
+
cost: {
|
|
329
|
+
inputPerMTok: 0.3,
|
|
330
|
+
outputPerMTok: 1.2
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
id: "MiniMax-M3",
|
|
335
|
+
provider: "minimax",
|
|
336
|
+
api: "anthropic-messages",
|
|
337
|
+
displayName: "MiniMax M3",
|
|
338
|
+
baseUrl: "https://api.minimax.io/anthropic",
|
|
339
|
+
contextWindow: 1048576,
|
|
340
|
+
maxOutputTokens: 512e3,
|
|
341
|
+
modalities: ["text", "image"],
|
|
342
|
+
reasoning: true,
|
|
343
|
+
cost: {
|
|
344
|
+
inputPerMTok: 0.6,
|
|
345
|
+
outputPerMTok: 2.4,
|
|
346
|
+
cacheReadPerMTok: 0.12
|
|
347
|
+
}
|
|
348
|
+
},
|
|
264
349
|
// ── Ollama (local; pricing is zero — your own hardware) ─────────────────
|
|
265
350
|
{
|
|
266
351
|
id: "llama3.1:8b",
|
|
@@ -423,6 +508,19 @@ var SECRET_TABLE = {
|
|
|
423
508
|
envVars: ["MOONSHOT_API_KEY", "KIMI_API_KEY"],
|
|
424
509
|
hint: "Moonshot AI (Kimi) API key."
|
|
425
510
|
},
|
|
511
|
+
minimax: {
|
|
512
|
+
provider: "minimax",
|
|
513
|
+
scheme: "api-key",
|
|
514
|
+
envVars: ["MINIMAX_API_KEY"],
|
|
515
|
+
hint: "MiniMax global API key (api.minimax.io Anthropic-compatible)."
|
|
516
|
+
},
|
|
517
|
+
"minimax-cn": {
|
|
518
|
+
provider: "minimax-cn",
|
|
519
|
+
scheme: "api-key",
|
|
520
|
+
// Same key often works for both; CN-specific override first when set.
|
|
521
|
+
envVars: ["MINIMAX_CN_API_KEY", "MINIMAX_API_KEY"],
|
|
522
|
+
hint: "MiniMax China API key (api.minimaxi.com Anthropic-compatible)."
|
|
523
|
+
},
|
|
426
524
|
ollama: {
|
|
427
525
|
provider: "ollama",
|
|
428
526
|
scheme: "none",
|
|
@@ -1263,7 +1361,7 @@ function endpointFor(model) {
|
|
|
1263
1361
|
const base = (model.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1264
1362
|
return `${base}/v1/messages`;
|
|
1265
1363
|
}
|
|
1266
|
-
async function postMessages(url, apiKey, body, signal) {
|
|
1364
|
+
async function postMessages(url, apiKey, body, signal, provider = "anthropic") {
|
|
1267
1365
|
let response;
|
|
1268
1366
|
try {
|
|
1269
1367
|
response = await fetch(url, {
|
|
@@ -1278,24 +1376,24 @@ async function postMessages(url, apiKey, body, signal) {
|
|
|
1278
1376
|
});
|
|
1279
1377
|
} catch (cause) {
|
|
1280
1378
|
if (signal?.aborted) {
|
|
1281
|
-
throw gatewayError("aborted",
|
|
1282
|
-
provider
|
|
1379
|
+
throw gatewayError("aborted", `request canceled before ${provider} responded`, {
|
|
1380
|
+
provider,
|
|
1283
1381
|
cause
|
|
1284
1382
|
});
|
|
1285
1383
|
}
|
|
1286
|
-
throw gatewayError("transport",
|
|
1384
|
+
throw gatewayError("transport", `network call to ${provider} failed`, { provider, cause });
|
|
1287
1385
|
}
|
|
1288
1386
|
if (!response.ok) {
|
|
1289
1387
|
const detail = await readErrorBody(response);
|
|
1290
1388
|
const kind = response.status === 401 || response.status === 403 ? "auth" : response.status === 429 ? "rate_limit" : "http";
|
|
1291
|
-
throw gatewayError(kind,
|
|
1292
|
-
provider
|
|
1389
|
+
throw gatewayError(kind, `${provider} returned HTTP ${response.status}${detail}`, {
|
|
1390
|
+
provider,
|
|
1293
1391
|
status: response.status
|
|
1294
1392
|
});
|
|
1295
1393
|
}
|
|
1296
1394
|
if (response.body === null) {
|
|
1297
|
-
throw gatewayError("transport",
|
|
1298
|
-
provider
|
|
1395
|
+
throw gatewayError("transport", `${provider} response arrived with no body`, {
|
|
1396
|
+
provider,
|
|
1299
1397
|
status: response.status
|
|
1300
1398
|
});
|
|
1301
1399
|
}
|
|
@@ -1317,11 +1415,13 @@ function asGatewayError(cause) {
|
|
|
1317
1415
|
}
|
|
1318
1416
|
function createanthropicConnector(deps2) {
|
|
1319
1417
|
const api = "anthropic-messages";
|
|
1320
|
-
async function requireKey(options) {
|
|
1321
|
-
const
|
|
1418
|
+
async function requireKey(model, options) {
|
|
1419
|
+
const provider = model.provider;
|
|
1420
|
+
const key = await deps2.resolveSecret(provider, options.apiKey);
|
|
1322
1421
|
if (key === void 0 || key === "") {
|
|
1323
|
-
|
|
1324
|
-
|
|
1422
|
+
const hint = provider === "minimax" || provider === "minimax-cn" ? "set MINIMAX_API_KEY (or MINIMAX_CN_API_KEY for CN) or pass options.apiKey" : "set ANTHROPIC_API_KEY or pass options.apiKey";
|
|
1423
|
+
throw gatewayError("auth", `no API key found for provider "${provider}" (${hint})`, {
|
|
1424
|
+
provider
|
|
1325
1425
|
});
|
|
1326
1426
|
}
|
|
1327
1427
|
return key;
|
|
@@ -1330,10 +1430,16 @@ function createanthropicConnector(deps2) {
|
|
|
1330
1430
|
return channelOf(async function* run2() {
|
|
1331
1431
|
const collected = [];
|
|
1332
1432
|
try {
|
|
1333
|
-
const apiKey = await requireKey(options);
|
|
1433
|
+
const apiKey = await requireKey(model, options);
|
|
1334
1434
|
const body = toAnthropicRequest(conversation, model, options);
|
|
1335
1435
|
body.stream = true;
|
|
1336
|
-
const response = await postMessages(
|
|
1436
|
+
const response = await postMessages(
|
|
1437
|
+
endpointFor(model),
|
|
1438
|
+
apiKey,
|
|
1439
|
+
body,
|
|
1440
|
+
options.signal,
|
|
1441
|
+
model.provider
|
|
1442
|
+
);
|
|
1337
1443
|
const wire = response.body;
|
|
1338
1444
|
const state = { toolIds: /* @__PURE__ */ new Map(), usage: { inputTokens: 0, outputTokens: 0 } };
|
|
1339
1445
|
for await (const event of sseEvents(wire, options.signal)) {
|
|
@@ -8595,6 +8701,1426 @@ function createComposioGateway(opts) {
|
|
|
8595
8701
|
return createSaasGateway(createComposioBackend({ apiKey: opts.apiKey }));
|
|
8596
8702
|
}
|
|
8597
8703
|
|
|
8704
|
+
// src/connectors-zoho/index.ts
|
|
8705
|
+
var connectors_zoho_exports = {};
|
|
8706
|
+
__export(connectors_zoho_exports, {
|
|
8707
|
+
connectZohoMcp: () => connectZohoMcp,
|
|
8708
|
+
zohoToolBoxFromClient: () => zohoToolBoxFromClient
|
|
8709
|
+
});
|
|
8710
|
+
|
|
8711
|
+
// src/facade/mcp-core/client.ts
|
|
8712
|
+
import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8713
|
+
import { StdioClientTransport as StdioClientTransport2 } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
8714
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
8715
|
+
import { SSEClientTransport as SSEClientTransport2 } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
8716
|
+
import {
|
|
8717
|
+
UnauthorizedError
|
|
8718
|
+
} from "@modelcontextprotocol/sdk/client/auth.js";
|
|
8719
|
+
import {
|
|
8720
|
+
ListRootsRequestSchema,
|
|
8721
|
+
ElicitRequestSchema,
|
|
8722
|
+
CreateMessageRequestSchema,
|
|
8723
|
+
PingRequestSchema,
|
|
8724
|
+
ToolListChangedNotificationSchema,
|
|
8725
|
+
ResourceListChangedNotificationSchema,
|
|
8726
|
+
ResourceUpdatedNotificationSchema,
|
|
8727
|
+
PromptListChangedNotificationSchema,
|
|
8728
|
+
ProgressNotificationSchema,
|
|
8729
|
+
LoggingMessageNotificationSchema,
|
|
8730
|
+
CancelledNotificationSchema
|
|
8731
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
8732
|
+
|
|
8733
|
+
// src/facade/mcp-core/errors.ts
|
|
8734
|
+
var MCPError = class extends Error {
|
|
8735
|
+
/** Error code */
|
|
8736
|
+
code;
|
|
8737
|
+
/** Additional error details */
|
|
8738
|
+
details;
|
|
8739
|
+
/** Server name where error occurred */
|
|
8740
|
+
serverName;
|
|
8741
|
+
/** Tool name if error occurred during tool execution */
|
|
8742
|
+
toolName;
|
|
8743
|
+
constructor(message, code, details, options) {
|
|
8744
|
+
super(message, options?.cause ? { cause: options.cause } : void 0);
|
|
8745
|
+
this.name = "MCPError";
|
|
8746
|
+
this.code = code;
|
|
8747
|
+
this.details = details;
|
|
8748
|
+
this.serverName = options?.serverName;
|
|
8749
|
+
this.toolName = options?.toolName;
|
|
8750
|
+
}
|
|
8751
|
+
/**
|
|
8752
|
+
* Convert error to JSON for logging/serialization.
|
|
8753
|
+
*/
|
|
8754
|
+
toJSON() {
|
|
8755
|
+
return {
|
|
8756
|
+
name: this.name,
|
|
8757
|
+
message: this.message,
|
|
8758
|
+
code: this.code,
|
|
8759
|
+
details: this.details,
|
|
8760
|
+
serverName: this.serverName,
|
|
8761
|
+
toolName: this.toolName
|
|
8762
|
+
};
|
|
8763
|
+
}
|
|
8764
|
+
/**
|
|
8765
|
+
* Create a string representation of the error.
|
|
8766
|
+
*/
|
|
8767
|
+
toString() {
|
|
8768
|
+
let str2 = `${this.name} [${this.code}]: ${this.message}`;
|
|
8769
|
+
if (this.serverName) str2 += ` (server: ${this.serverName})`;
|
|
8770
|
+
if (this.toolName) str2 += ` (tool: ${this.toolName})`;
|
|
8771
|
+
return str2;
|
|
8772
|
+
}
|
|
8773
|
+
};
|
|
8774
|
+
function createServerError(message, serverName, details) {
|
|
8775
|
+
return new MCPError(message, "SERVER_ERROR" /* SERVER_ERROR */, details, { serverName });
|
|
8776
|
+
}
|
|
8777
|
+
|
|
8778
|
+
// src/facade/mcp-core/client.ts
|
|
8779
|
+
var DEFAULT_REQUEST_TIMEOUT = 6e4;
|
|
8780
|
+
var DEFAULT_CONNECT_TIMEOUT = 3e4;
|
|
8781
|
+
var MCPClient = class {
|
|
8782
|
+
constructor(options) {
|
|
8783
|
+
this.options = options;
|
|
8784
|
+
this.serverName = options.name;
|
|
8785
|
+
this.config = options.config;
|
|
8786
|
+
this.timeout = options.timeout ?? DEFAULT_REQUEST_TIMEOUT;
|
|
8787
|
+
this.logHandler = options.logger;
|
|
8788
|
+
this.enableServerLogs = options.enableServerLogs ?? true;
|
|
8789
|
+
this.enableProgressTracking = options.enableProgressTracking ?? false;
|
|
8790
|
+
this._roots = options.roots ?? [];
|
|
8791
|
+
}
|
|
8792
|
+
options;
|
|
8793
|
+
client;
|
|
8794
|
+
transport;
|
|
8795
|
+
isConnected = false;
|
|
8796
|
+
connectionPromise = null;
|
|
8797
|
+
serverCapabilities;
|
|
8798
|
+
logHandler;
|
|
8799
|
+
enableServerLogs;
|
|
8800
|
+
enableProgressTracking;
|
|
8801
|
+
_roots;
|
|
8802
|
+
// --- Host-overridable handlers (Core of #13) --------------------------------
|
|
8803
|
+
// Server-initiated REQUESTS are answered by request handlers registered on the
|
|
8804
|
+
// SDK client; the handlers delegate to these host-supplied callbacks when set,
|
|
8805
|
+
// and fall back to sensible defaults otherwise.
|
|
8806
|
+
elicitationHandler;
|
|
8807
|
+
samplingHandler;
|
|
8808
|
+
// Server-initiated NOTIFICATIONS are dispatched to these (so the host can, for
|
|
8809
|
+
// example, re-list tools when a `tools/list_changed` arrives).
|
|
8810
|
+
resourceUpdatedHandler;
|
|
8811
|
+
resourceListChangedHandler;
|
|
8812
|
+
toolListChangedHandler;
|
|
8813
|
+
promptListChangedHandler;
|
|
8814
|
+
progressHandler;
|
|
8815
|
+
/** Server name */
|
|
8816
|
+
serverName;
|
|
8817
|
+
/** Server config */
|
|
8818
|
+
config;
|
|
8819
|
+
/** Request timeout */
|
|
8820
|
+
timeout;
|
|
8821
|
+
// ========================================================================
|
|
8822
|
+
// Connection Lifecycle
|
|
8823
|
+
// ========================================================================
|
|
8824
|
+
/**
|
|
8825
|
+
* Connect to the MCP server.
|
|
8826
|
+
* Safe to call multiple times - returns existing connection if already connected.
|
|
8827
|
+
*/
|
|
8828
|
+
async connect() {
|
|
8829
|
+
if (this.isConnected) return;
|
|
8830
|
+
if (this.connectionPromise) {
|
|
8831
|
+
return this.connectionPromise;
|
|
8832
|
+
}
|
|
8833
|
+
this.connectionPromise = this.doConnect();
|
|
8834
|
+
try {
|
|
8835
|
+
await this.connectionPromise;
|
|
8836
|
+
} finally {
|
|
8837
|
+
this.connectionPromise = null;
|
|
8838
|
+
}
|
|
8839
|
+
}
|
|
8840
|
+
async doConnect() {
|
|
8841
|
+
const transport = this.createTransport(this.config);
|
|
8842
|
+
this.transport = transport;
|
|
8843
|
+
const client = new Client2(
|
|
8844
|
+
{ name: "indusagi-coding-agent", version: "0.13.0" },
|
|
8845
|
+
{
|
|
8846
|
+
capabilities: {
|
|
8847
|
+
roots: { listChanged: true },
|
|
8848
|
+
elicitation: {},
|
|
8849
|
+
sampling: {}
|
|
8850
|
+
}
|
|
8851
|
+
}
|
|
8852
|
+
);
|
|
8853
|
+
this.client = client;
|
|
8854
|
+
this.registerServerHandlers(client);
|
|
8855
|
+
const prevOnClose = transport.onclose;
|
|
8856
|
+
transport.onclose = () => {
|
|
8857
|
+
this.isConnected = false;
|
|
8858
|
+
this.log("debug", "Transport closed");
|
|
8859
|
+
prevOnClose?.();
|
|
8860
|
+
};
|
|
8861
|
+
try {
|
|
8862
|
+
await client.connect(transport, { timeout: DEFAULT_CONNECT_TIMEOUT });
|
|
8863
|
+
} catch (err) {
|
|
8864
|
+
const recovered = await this.tryFinishOAuthAndReconnect(client, transport, err);
|
|
8865
|
+
if (!recovered) {
|
|
8866
|
+
throw this.wrapConnectError(err);
|
|
8867
|
+
}
|
|
8868
|
+
}
|
|
8869
|
+
this.serverCapabilities = client.getServerCapabilities();
|
|
8870
|
+
this.isConnected = true;
|
|
8871
|
+
this.log("info", `Connected to MCP server`);
|
|
8872
|
+
}
|
|
8873
|
+
/**
|
|
8874
|
+
* If `err` is an OAuth challenge and we have a provider + finishAuth-capable
|
|
8875
|
+
* transport, wait for the browser code, exchange tokens, then reconnect on a
|
|
8876
|
+
* **fresh** transport (the first transport is already `start()`ed and cannot
|
|
8877
|
+
* be reused — see StreamableHTTPClientTransport.start).
|
|
8878
|
+
*
|
|
8879
|
+
* Matches the official SDK CLI example (`simpleOAuthClient`):
|
|
8880
|
+
* finishAuth(code) → new StreamableHTTPClientTransport → client.connect again
|
|
8881
|
+
*
|
|
8882
|
+
* Client.connect already called `close()` on init failure, so the Client is
|
|
8883
|
+
* free to attach a new transport.
|
|
8884
|
+
*/
|
|
8885
|
+
async tryFinishOAuthAndReconnect(client, transport, err) {
|
|
8886
|
+
if (!isAuthRequiredError(err)) return false;
|
|
8887
|
+
const provider = this.options.authProvider;
|
|
8888
|
+
if (!provider) return false;
|
|
8889
|
+
const finishAuth = transport.finishAuth;
|
|
8890
|
+
const waitForCode = provider.waitForAuthorizationCode;
|
|
8891
|
+
if (typeof finishAuth !== "function" || typeof waitForCode !== "function") {
|
|
8892
|
+
this.log(
|
|
8893
|
+
"error",
|
|
8894
|
+
"MCP server requires authentication but OAuth finish path is incomplete (need authProvider.waitForAuthorizationCode + transport.finishAuth)."
|
|
8895
|
+
);
|
|
8896
|
+
return false;
|
|
8897
|
+
}
|
|
8898
|
+
this.log("info", "MCP server requires OAuth \u2014 complete login in the browser\u2026");
|
|
8899
|
+
try {
|
|
8900
|
+
const code = await waitForCode.call(provider);
|
|
8901
|
+
this.log("info", "OAuth authorization code received \u2014 exchanging tokens\u2026");
|
|
8902
|
+
await finishAuth.call(transport, code);
|
|
8903
|
+
try {
|
|
8904
|
+
await transport.close?.();
|
|
8905
|
+
} catch {
|
|
8906
|
+
}
|
|
8907
|
+
const fresh = this.createTransport(this.config);
|
|
8908
|
+
this.transport = fresh;
|
|
8909
|
+
const prevOnClose = fresh.onclose;
|
|
8910
|
+
fresh.onclose = () => {
|
|
8911
|
+
this.isConnected = false;
|
|
8912
|
+
this.log("debug", "Transport closed");
|
|
8913
|
+
prevOnClose?.();
|
|
8914
|
+
};
|
|
8915
|
+
this.log("info", "Reconnecting MCP session with OAuth tokens\u2026");
|
|
8916
|
+
await client.connect(fresh, { timeout: DEFAULT_CONNECT_TIMEOUT });
|
|
8917
|
+
return true;
|
|
8918
|
+
} catch (oauthErr) {
|
|
8919
|
+
this.log(
|
|
8920
|
+
"error",
|
|
8921
|
+
`OAuth finish failed: ${oauthErr instanceof Error ? oauthErr.message : String(oauthErr)}`
|
|
8922
|
+
);
|
|
8923
|
+
throw this.wrapConnectError(oauthErr);
|
|
8924
|
+
}
|
|
8925
|
+
}
|
|
8926
|
+
/** Turn raw SDK / network errors into a clearer MCPError for callers. */
|
|
8927
|
+
wrapConnectError(err) {
|
|
8928
|
+
if (err instanceof MCPError) return err;
|
|
8929
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8930
|
+
if (isAuthRequiredError(err)) {
|
|
8931
|
+
return new MCPError(
|
|
8932
|
+
`Authentication required for MCP server "${this.serverName}". Pass auth: "auto" (default) to run the OAuth browser flow, or set a Bearer token via headers. Original: ${message}`,
|
|
8933
|
+
"CONNECTION_FAILED" /* CONNECTION_FAILED */,
|
|
8934
|
+
err,
|
|
8935
|
+
{ serverName: this.serverName, cause: err instanceof Error ? err : void 0 }
|
|
8936
|
+
);
|
|
8937
|
+
}
|
|
8938
|
+
return new MCPError(
|
|
8939
|
+
`Failed to connect to MCP server "${this.serverName}": ${message}`,
|
|
8940
|
+
"CONNECTION_FAILED" /* CONNECTION_FAILED */,
|
|
8941
|
+
err,
|
|
8942
|
+
{ serverName: this.serverName, cause: err instanceof Error ? err : void 0 }
|
|
8943
|
+
);
|
|
8944
|
+
}
|
|
8945
|
+
/**
|
|
8946
|
+
* Build the SDK transport for the configured server.
|
|
8947
|
+
*
|
|
8948
|
+
* - stdio: `StdioClientTransport` (inherits the parent env, merges config.env).
|
|
8949
|
+
* - http: `StreamableHTTPClientTransport` (POST + server-push SSE channel,
|
|
8950
|
+
* reconnect + session-expiry handled by the SDK), unless the config
|
|
8951
|
+
* asks for the legacy `SSEClientTransport`.
|
|
8952
|
+
*/
|
|
8953
|
+
createTransport(config) {
|
|
8954
|
+
if ("command" in config) {
|
|
8955
|
+
return new StdioClientTransport2({
|
|
8956
|
+
command: config.command,
|
|
8957
|
+
args: config.args ?? [],
|
|
8958
|
+
env: { ...sanitizeEnv(process.env), ...config.env ?? {} },
|
|
8959
|
+
cwd: config.cwd,
|
|
8960
|
+
stderr: "pipe"
|
|
8961
|
+
});
|
|
8962
|
+
}
|
|
8963
|
+
if ("url" in config) {
|
|
8964
|
+
const requestInit = config.headers ? { headers: config.headers } : void 0;
|
|
8965
|
+
if (config.transport === "sse") {
|
|
8966
|
+
return new SSEClientTransport2(config.url, {
|
|
8967
|
+
requestInit,
|
|
8968
|
+
...this.options.authProvider ? { authProvider: this.options.authProvider } : {}
|
|
8969
|
+
});
|
|
8970
|
+
}
|
|
8971
|
+
return new StreamableHTTPClientTransport(config.url, {
|
|
8972
|
+
requestInit,
|
|
8973
|
+
...this.options.authProvider ? { authProvider: this.options.authProvider } : {}
|
|
8974
|
+
});
|
|
8975
|
+
}
|
|
8976
|
+
throw new MCPError(
|
|
8977
|
+
"Server configuration must include either a command or a url",
|
|
8978
|
+
"CONFIG_ERROR" /* CONFIG_ERROR */,
|
|
8979
|
+
void 0,
|
|
8980
|
+
{ serverName: this.serverName }
|
|
8981
|
+
);
|
|
8982
|
+
}
|
|
8983
|
+
/**
|
|
8984
|
+
* Register handlers for server-initiated REQUESTS and NOTIFICATIONS.
|
|
8985
|
+
*
|
|
8986
|
+
* REQUESTS (have both `.method` AND `.id`) must be ANSWERED with a JSON-RPC
|
|
8987
|
+
* RESPONSE carrying the matching id — the SDK does this automatically for the
|
|
8988
|
+
* value a request handler returns (or rejects). The previous hand-rolled
|
|
8989
|
+
* transport routed these into the notification path, so they were never
|
|
8990
|
+
* answered (bug #13).
|
|
8991
|
+
*
|
|
8992
|
+
* Defaults:
|
|
8993
|
+
* - ping → {}
|
|
8994
|
+
* - roots/list → the configured roots
|
|
8995
|
+
* - elicitation/create→ decline ({action:"cancel"}) unless a host handler is set
|
|
8996
|
+
* - sampling → reject "Method not found" unless a host handler is set
|
|
8997
|
+
*/
|
|
8998
|
+
registerServerHandlers(client) {
|
|
8999
|
+
client.setRequestHandler(PingRequestSchema, async () => ({}));
|
|
9000
|
+
client.setRequestHandler(ListRootsRequestSchema, async () => ({
|
|
9001
|
+
roots: this._roots.map((r) => ({ uri: r.uri, name: r.name }))
|
|
9002
|
+
}));
|
|
9003
|
+
client.setRequestHandler(ElicitRequestSchema, async (request) => {
|
|
9004
|
+
if (!this.elicitationHandler) {
|
|
9005
|
+
return { action: "cancel" };
|
|
9006
|
+
}
|
|
9007
|
+
const params = request.params;
|
|
9008
|
+
const elicitRequest = {
|
|
9009
|
+
message: params.message,
|
|
9010
|
+
requestedSchema: params.requestedSchema ?? {}
|
|
9011
|
+
};
|
|
9012
|
+
const result = await this.elicitationHandler(elicitRequest);
|
|
9013
|
+
return mapToSdkElicitResult(result);
|
|
9014
|
+
});
|
|
9015
|
+
client.setRequestHandler(CreateMessageRequestSchema, async (request) => {
|
|
9016
|
+
if (!this.samplingHandler) {
|
|
9017
|
+
throw new MCPError(
|
|
9018
|
+
"Method not found: sampling/createMessage",
|
|
9019
|
+
"SERVER_ERROR" /* SERVER_ERROR */,
|
|
9020
|
+
{ method: "sampling/createMessage" },
|
|
9021
|
+
{ serverName: this.serverName }
|
|
9022
|
+
);
|
|
9023
|
+
}
|
|
9024
|
+
return await this.samplingHandler(request.params);
|
|
9025
|
+
});
|
|
9026
|
+
client.setNotificationHandler(ToolListChangedNotificationSchema, () => {
|
|
9027
|
+
this.log("debug", "Tool list changed");
|
|
9028
|
+
this.toolListChangedHandler?.();
|
|
9029
|
+
});
|
|
9030
|
+
client.setNotificationHandler(ResourceListChangedNotificationSchema, () => {
|
|
9031
|
+
this.log("debug", "Resource list changed");
|
|
9032
|
+
this.resourceListChangedHandler?.();
|
|
9033
|
+
});
|
|
9034
|
+
client.setNotificationHandler(ResourceUpdatedNotificationSchema, (n) => {
|
|
9035
|
+
const uri = n.params?.uri;
|
|
9036
|
+
this.log("debug", `Resource updated: ${uri}`);
|
|
9037
|
+
if (uri) this.resourceUpdatedHandler?.({ uri });
|
|
9038
|
+
});
|
|
9039
|
+
client.setNotificationHandler(PromptListChangedNotificationSchema, () => {
|
|
9040
|
+
this.log("debug", "Prompt list changed");
|
|
9041
|
+
this.promptListChangedHandler?.();
|
|
9042
|
+
});
|
|
9043
|
+
client.setNotificationHandler(ProgressNotificationSchema, (n) => {
|
|
9044
|
+
const params = n.params;
|
|
9045
|
+
this.log("debug", `Progress: ${JSON.stringify(params)}`);
|
|
9046
|
+
if (this.progressHandler && params) {
|
|
9047
|
+
const notification = {
|
|
9048
|
+
progressToken: params.progressToken,
|
|
9049
|
+
progress: params.progress,
|
|
9050
|
+
total: params.total,
|
|
9051
|
+
message: params.message
|
|
9052
|
+
};
|
|
9053
|
+
this.progressHandler(notification);
|
|
9054
|
+
}
|
|
9055
|
+
});
|
|
9056
|
+
client.setNotificationHandler(LoggingMessageNotificationSchema, (n) => {
|
|
9057
|
+
if (this.enableServerLogs && n.params) {
|
|
9058
|
+
const { level, ...rest } = n.params;
|
|
9059
|
+
this.log(level || "info", "[SERVER]", rest);
|
|
9060
|
+
}
|
|
9061
|
+
});
|
|
9062
|
+
client.setNotificationHandler(CancelledNotificationSchema, (n) => {
|
|
9063
|
+
this.log("debug", `Request cancelled: ${JSON.stringify(n.params)}`);
|
|
9064
|
+
});
|
|
9065
|
+
}
|
|
9066
|
+
/**
|
|
9067
|
+
* Disconnect from the MCP server.
|
|
9068
|
+
*
|
|
9069
|
+
* The SDK's `client.close()` closes the transport and rejects any pending
|
|
9070
|
+
* requests — a clean teardown for both stdio (graceful subprocess shutdown)
|
|
9071
|
+
* and HTTP (close the push channel / end the session).
|
|
9072
|
+
*/
|
|
9073
|
+
async disconnect() {
|
|
9074
|
+
if (!this.client) {
|
|
9075
|
+
this.log("debug", "Disconnect called but not connected");
|
|
9076
|
+
this.isConnected = false;
|
|
9077
|
+
return;
|
|
9078
|
+
}
|
|
9079
|
+
this.log("debug", "Disconnecting from MCP server");
|
|
9080
|
+
try {
|
|
9081
|
+
await this.client.close();
|
|
9082
|
+
} catch (error) {
|
|
9083
|
+
this.log("error", `Error during disconnect: ${error}`);
|
|
9084
|
+
} finally {
|
|
9085
|
+
this.client = void 0;
|
|
9086
|
+
this.transport = void 0;
|
|
9087
|
+
this.isConnected = false;
|
|
9088
|
+
this.log("debug", "Successfully disconnected");
|
|
9089
|
+
}
|
|
9090
|
+
}
|
|
9091
|
+
/**
|
|
9092
|
+
* Whether the client is connected.
|
|
9093
|
+
*/
|
|
9094
|
+
get connected() {
|
|
9095
|
+
return this.isConnected;
|
|
9096
|
+
}
|
|
9097
|
+
// ========================================================================
|
|
9098
|
+
// Tool Operations
|
|
9099
|
+
// ========================================================================
|
|
9100
|
+
/**
|
|
9101
|
+
* List all tools available from the server.
|
|
9102
|
+
*/
|
|
9103
|
+
async listTools() {
|
|
9104
|
+
this.ensureConnected();
|
|
9105
|
+
const result = await this.client.listTools(void 0, {
|
|
9106
|
+
timeout: this.timeout
|
|
9107
|
+
});
|
|
9108
|
+
return result.tools;
|
|
9109
|
+
}
|
|
9110
|
+
/**
|
|
9111
|
+
* Call a tool on the server.
|
|
9112
|
+
*/
|
|
9113
|
+
async callTool(name, args) {
|
|
9114
|
+
this.ensureConnected();
|
|
9115
|
+
this.log("debug", `Calling tool: ${name}`);
|
|
9116
|
+
try {
|
|
9117
|
+
const result = await this.client.callTool(
|
|
9118
|
+
{ name, arguments: args },
|
|
9119
|
+
void 0,
|
|
9120
|
+
{ timeout: this.timeout }
|
|
9121
|
+
);
|
|
9122
|
+
this.log("debug", `Tool ${name} executed successfully`);
|
|
9123
|
+
return result;
|
|
9124
|
+
} catch (error) {
|
|
9125
|
+
this.log("error", `Tool ${name} failed: ${error}`);
|
|
9126
|
+
throw this.wrapError(error);
|
|
9127
|
+
}
|
|
9128
|
+
}
|
|
9129
|
+
// ========================================================================
|
|
9130
|
+
// Resource Operations
|
|
9131
|
+
// ========================================================================
|
|
9132
|
+
/**
|
|
9133
|
+
* List all resources available from the server.
|
|
9134
|
+
*/
|
|
9135
|
+
async listResources() {
|
|
9136
|
+
this.ensureConnected();
|
|
9137
|
+
if (!this.serverCapabilities?.resources) {
|
|
9138
|
+
return [];
|
|
9139
|
+
}
|
|
9140
|
+
const result = await this.client.listResources(void 0, {
|
|
9141
|
+
timeout: this.timeout
|
|
9142
|
+
});
|
|
9143
|
+
return result.resources;
|
|
9144
|
+
}
|
|
9145
|
+
/**
|
|
9146
|
+
* Read a resource from the server.
|
|
9147
|
+
*/
|
|
9148
|
+
async readResource(uri) {
|
|
9149
|
+
this.ensureConnected();
|
|
9150
|
+
return await this.client.readResource({ uri }, { timeout: this.timeout });
|
|
9151
|
+
}
|
|
9152
|
+
/**
|
|
9153
|
+
* Subscribe to resource updates.
|
|
9154
|
+
*/
|
|
9155
|
+
async subscribeResource(uri) {
|
|
9156
|
+
this.ensureConnected();
|
|
9157
|
+
await this.client.subscribeResource({ uri }, { timeout: this.timeout });
|
|
9158
|
+
}
|
|
9159
|
+
/**
|
|
9160
|
+
* Unsubscribe from resource updates.
|
|
9161
|
+
*/
|
|
9162
|
+
async unsubscribeResource(uri) {
|
|
9163
|
+
this.ensureConnected();
|
|
9164
|
+
await this.client.unsubscribeResource({ uri }, { timeout: this.timeout });
|
|
9165
|
+
}
|
|
9166
|
+
// ========================================================================
|
|
9167
|
+
// Prompt Operations
|
|
9168
|
+
// ========================================================================
|
|
9169
|
+
/**
|
|
9170
|
+
* List all prompts available from the server.
|
|
9171
|
+
*/
|
|
9172
|
+
async listPrompts() {
|
|
9173
|
+
this.ensureConnected();
|
|
9174
|
+
if (!this.serverCapabilities?.prompts) {
|
|
9175
|
+
return [];
|
|
9176
|
+
}
|
|
9177
|
+
const result = await this.client.listPrompts(void 0, {
|
|
9178
|
+
timeout: this.timeout
|
|
9179
|
+
});
|
|
9180
|
+
return result.prompts;
|
|
9181
|
+
}
|
|
9182
|
+
/**
|
|
9183
|
+
* Get a prompt from the server.
|
|
9184
|
+
*/
|
|
9185
|
+
async getPrompt(name, args) {
|
|
9186
|
+
this.ensureConnected();
|
|
9187
|
+
return await this.client.getPrompt(
|
|
9188
|
+
{ name, arguments: args },
|
|
9189
|
+
{ timeout: this.timeout }
|
|
9190
|
+
);
|
|
9191
|
+
}
|
|
9192
|
+
// ========================================================================
|
|
9193
|
+
// Roots Operations
|
|
9194
|
+
// ========================================================================
|
|
9195
|
+
/**
|
|
9196
|
+
* Get the configured roots.
|
|
9197
|
+
*/
|
|
9198
|
+
get roots() {
|
|
9199
|
+
return [...this._roots];
|
|
9200
|
+
}
|
|
9201
|
+
/**
|
|
9202
|
+
* Update the roots and notify the server.
|
|
9203
|
+
*/
|
|
9204
|
+
async setRoots(roots) {
|
|
9205
|
+
this.log("debug", `Updating roots to ${roots.length} entries`);
|
|
9206
|
+
this._roots = [...roots];
|
|
9207
|
+
if (this.isConnected && this.client) {
|
|
9208
|
+
try {
|
|
9209
|
+
await this.client.sendRootsListChanged();
|
|
9210
|
+
} catch (error) {
|
|
9211
|
+
this.log("debug", `Failed to send roots/list_changed: ${error}`);
|
|
9212
|
+
}
|
|
9213
|
+
}
|
|
9214
|
+
}
|
|
9215
|
+
// ========================================================================
|
|
9216
|
+
// Handler Registration
|
|
9217
|
+
//
|
|
9218
|
+
// Setters STORE the host handler (and, for notifications, the SDK handler
|
|
9219
|
+
// registered in `registerServerHandlers` delegates to the stored callback).
|
|
9220
|
+
// This is the host-overridable layer at the core of bug #13.
|
|
9221
|
+
// ========================================================================
|
|
9222
|
+
/**
|
|
9223
|
+
* Set a handler for resource updated notifications.
|
|
9224
|
+
*/
|
|
9225
|
+
setResourceUpdatedHandler(handler) {
|
|
9226
|
+
this.resourceUpdatedHandler = handler;
|
|
9227
|
+
this.log("debug", "Resource updated handler registered");
|
|
9228
|
+
}
|
|
9229
|
+
/**
|
|
9230
|
+
* Set a handler for resource list changed notifications.
|
|
9231
|
+
*/
|
|
9232
|
+
setResourceListChangedHandler(handler) {
|
|
9233
|
+
this.resourceListChangedHandler = handler;
|
|
9234
|
+
this.log("debug", "Resource list changed handler registered");
|
|
9235
|
+
}
|
|
9236
|
+
/**
|
|
9237
|
+
* Set a handler for tool list changed notifications.
|
|
9238
|
+
*/
|
|
9239
|
+
setToolListChangedHandler(handler) {
|
|
9240
|
+
this.toolListChangedHandler = handler;
|
|
9241
|
+
this.log("debug", "Tool list changed handler registered");
|
|
9242
|
+
}
|
|
9243
|
+
/**
|
|
9244
|
+
* Set a handler for prompt list changed notifications.
|
|
9245
|
+
*/
|
|
9246
|
+
setPromptListChangedHandler(handler) {
|
|
9247
|
+
this.promptListChangedHandler = handler;
|
|
9248
|
+
this.log("debug", "Prompt list changed handler registered");
|
|
9249
|
+
}
|
|
9250
|
+
/**
|
|
9251
|
+
* Set a handler for elicitation requests. When set, the server's
|
|
9252
|
+
* `elicitation/create` REQUEST is answered with the host's decision; when
|
|
9253
|
+
* unset the request is declined ({action:"cancel"}).
|
|
9254
|
+
*/
|
|
9255
|
+
setElicitationHandler(handler) {
|
|
9256
|
+
this.elicitationHandler = handler;
|
|
9257
|
+
this.log("debug", "Elicitation handler registered");
|
|
9258
|
+
}
|
|
9259
|
+
/**
|
|
9260
|
+
* Set a handler for sampling (`sampling/createMessage`) requests. When set,
|
|
9261
|
+
* the server request is answered with the host's result; when unset the
|
|
9262
|
+
* request is rejected with "Method not found".
|
|
9263
|
+
*/
|
|
9264
|
+
setSamplingHandler(handler) {
|
|
9265
|
+
this.samplingHandler = handler;
|
|
9266
|
+
this.log("debug", "Sampling handler registered");
|
|
9267
|
+
}
|
|
9268
|
+
/**
|
|
9269
|
+
* Set a handler for progress notifications.
|
|
9270
|
+
*/
|
|
9271
|
+
setProgressHandler(handler) {
|
|
9272
|
+
this.progressHandler = handler;
|
|
9273
|
+
this.log("debug", "Progress handler registered");
|
|
9274
|
+
}
|
|
9275
|
+
// ========================================================================
|
|
9276
|
+
// Private Methods
|
|
9277
|
+
// ========================================================================
|
|
9278
|
+
ensureConnected() {
|
|
9279
|
+
if (!this.isConnected || !this.client) {
|
|
9280
|
+
throw new MCPError(
|
|
9281
|
+
"Not connected to MCP server",
|
|
9282
|
+
"NOT_CONNECTED" /* NOT_CONNECTED */,
|
|
9283
|
+
void 0,
|
|
9284
|
+
{ serverName: this.serverName }
|
|
9285
|
+
);
|
|
9286
|
+
}
|
|
9287
|
+
}
|
|
9288
|
+
/**
|
|
9289
|
+
* Normalize an SDK/transport error into an MCPError. Session-expiry style
|
|
9290
|
+
* failures (404 session / -32001) are surfaced as SESSION_ERROR so callers
|
|
9291
|
+
* (the pool) can decide to reconnect; the SDK's StreamableHTTP transport
|
|
9292
|
+
* already attempts a bounded reconnect re-using the session id before this.
|
|
9293
|
+
*/
|
|
9294
|
+
wrapError(error) {
|
|
9295
|
+
if (error instanceof MCPError) return error;
|
|
9296
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9297
|
+
const code = error.code;
|
|
9298
|
+
if (code === -32001 || /\b404\b|session/i.test(message)) {
|
|
9299
|
+
return new MCPError(message, "SESSION_ERROR" /* SESSION_ERROR */, { code }, {
|
|
9300
|
+
serverName: this.serverName
|
|
9301
|
+
});
|
|
9302
|
+
}
|
|
9303
|
+
return new MCPError(message, "SERVER_ERROR" /* SERVER_ERROR */, { code }, {
|
|
9304
|
+
serverName: this.serverName
|
|
9305
|
+
});
|
|
9306
|
+
}
|
|
9307
|
+
log(level, message, details) {
|
|
9308
|
+
const msg = `[${this.serverName}] ${message}`;
|
|
9309
|
+
if (this.logHandler) {
|
|
9310
|
+
this.logHandler({
|
|
9311
|
+
level,
|
|
9312
|
+
message: msg,
|
|
9313
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
9314
|
+
serverName: this.serverName,
|
|
9315
|
+
details
|
|
9316
|
+
});
|
|
9317
|
+
} else {
|
|
9318
|
+
if (process.env.INDUSAGI_DEBUG || level === "error") {
|
|
9319
|
+
const prefix = `[MCP:${level.toUpperCase()}]`;
|
|
9320
|
+
if (details) {
|
|
9321
|
+
if (level === "error") {
|
|
9322
|
+
console.error(prefix, msg, details);
|
|
9323
|
+
} else {
|
|
9324
|
+
console.log(prefix, msg, details);
|
|
9325
|
+
}
|
|
9326
|
+
} else {
|
|
9327
|
+
if (level === "error") {
|
|
9328
|
+
console.error(prefix, msg);
|
|
9329
|
+
} else {
|
|
9330
|
+
console.log(prefix, msg);
|
|
9331
|
+
}
|
|
9332
|
+
}
|
|
9333
|
+
}
|
|
9334
|
+
}
|
|
9335
|
+
}
|
|
9336
|
+
};
|
|
9337
|
+
function mapToSdkElicitResult(result) {
|
|
9338
|
+
if (result.action === "accept") {
|
|
9339
|
+
return {
|
|
9340
|
+
action: "accept",
|
|
9341
|
+
content: result.content ?? {}
|
|
9342
|
+
};
|
|
9343
|
+
}
|
|
9344
|
+
return { action: result.action };
|
|
9345
|
+
}
|
|
9346
|
+
function isAuthRequiredError(err) {
|
|
9347
|
+
if (err == null) return false;
|
|
9348
|
+
if (err instanceof UnauthorizedError) return true;
|
|
9349
|
+
if (typeof err === "object") {
|
|
9350
|
+
const code = err.code;
|
|
9351
|
+
if (code === 401 || code === "401") return true;
|
|
9352
|
+
}
|
|
9353
|
+
if (err instanceof Error) {
|
|
9354
|
+
const msg = err.message;
|
|
9355
|
+
if (/unauthorized/i.test(msg)) return true;
|
|
9356
|
+
if (/authentication required/i.test(msg)) return true;
|
|
9357
|
+
if (/auth required/i.test(msg)) return true;
|
|
9358
|
+
if (err.name === "UnauthorizedError") return true;
|
|
9359
|
+
}
|
|
9360
|
+
return false;
|
|
9361
|
+
}
|
|
9362
|
+
function sanitizeEnv(env) {
|
|
9363
|
+
const out = {};
|
|
9364
|
+
for (const [k, v] of Object.entries(env)) {
|
|
9365
|
+
if (typeof v === "string") out[k] = v;
|
|
9366
|
+
}
|
|
9367
|
+
return out;
|
|
9368
|
+
}
|
|
9369
|
+
|
|
9370
|
+
// src/facade/mcp-core/oauth/flow.ts
|
|
9371
|
+
import { createServer } from "node:http";
|
|
9372
|
+
var CALLBACK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
9373
|
+
var SUCCESS_HTML = '<!doctype html><html><body style="font-family:system-ui;padding:2rem"><h1>Authorization complete</h1><p>You can close this tab and return to your terminal.</p></body></html>';
|
|
9374
|
+
var ERROR_HTML = (msg) => `<!doctype html><html><body style="font-family:system-ui;padding:2rem"><h1>Authorization failed</h1><pre>${escapeHtml(msg)}</pre></body></html>`;
|
|
9375
|
+
function escapeHtml(s) {
|
|
9376
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
9377
|
+
}
|
|
9378
|
+
async function startOAuthCallbackServer(options) {
|
|
9379
|
+
const timeoutMs = options?.timeoutMs ?? CALLBACK_TIMEOUT_MS;
|
|
9380
|
+
const logger = options?.logger;
|
|
9381
|
+
let settled2 = false;
|
|
9382
|
+
let timer;
|
|
9383
|
+
let resolveCode;
|
|
9384
|
+
let rejectCode;
|
|
9385
|
+
const codePromise = new Promise((res, rej) => {
|
|
9386
|
+
resolveCode = res;
|
|
9387
|
+
rejectCode = rej;
|
|
9388
|
+
});
|
|
9389
|
+
codePromise.catch(() => {
|
|
9390
|
+
});
|
|
9391
|
+
let expectedState;
|
|
9392
|
+
const clearTimer = () => {
|
|
9393
|
+
if (timer !== void 0) {
|
|
9394
|
+
clearTimeout(timer);
|
|
9395
|
+
timer = void 0;
|
|
9396
|
+
}
|
|
9397
|
+
};
|
|
9398
|
+
const settleReject = (err) => {
|
|
9399
|
+
if (settled2) return;
|
|
9400
|
+
settled2 = true;
|
|
9401
|
+
clearTimer();
|
|
9402
|
+
try {
|
|
9403
|
+
server.close();
|
|
9404
|
+
} catch {
|
|
9405
|
+
}
|
|
9406
|
+
rejectCode(err);
|
|
9407
|
+
};
|
|
9408
|
+
const settleResolve = (code) => {
|
|
9409
|
+
if (settled2) return;
|
|
9410
|
+
settled2 = true;
|
|
9411
|
+
clearTimer();
|
|
9412
|
+
resolveCode(code);
|
|
9413
|
+
setTimeout(() => {
|
|
9414
|
+
try {
|
|
9415
|
+
server.close();
|
|
9416
|
+
} catch {
|
|
9417
|
+
}
|
|
9418
|
+
}, 50);
|
|
9419
|
+
};
|
|
9420
|
+
const server = createServer((req, res) => {
|
|
9421
|
+
if (settled2) {
|
|
9422
|
+
res.statusCode = 410;
|
|
9423
|
+
res.end("already used");
|
|
9424
|
+
return;
|
|
9425
|
+
}
|
|
9426
|
+
handleRequest(
|
|
9427
|
+
req,
|
|
9428
|
+
res,
|
|
9429
|
+
expectedState,
|
|
9430
|
+
(code) => settleResolve(code),
|
|
9431
|
+
(err) => settleReject(err),
|
|
9432
|
+
logger
|
|
9433
|
+
);
|
|
9434
|
+
});
|
|
9435
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
9436
|
+
server.once("error", rejectListen);
|
|
9437
|
+
server.listen(0, "127.0.0.1", () => resolveListen());
|
|
9438
|
+
});
|
|
9439
|
+
const address = server.address();
|
|
9440
|
+
if (!address) {
|
|
9441
|
+
server.close();
|
|
9442
|
+
throw new Error("oauth-callback: server bound to no address");
|
|
9443
|
+
}
|
|
9444
|
+
const port = address.port;
|
|
9445
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
9446
|
+
const close = () => {
|
|
9447
|
+
if (!settled2) {
|
|
9448
|
+
settleReject(new Error("oauth-callback: closed before browser login completed"));
|
|
9449
|
+
} else {
|
|
9450
|
+
clearTimer();
|
|
9451
|
+
try {
|
|
9452
|
+
server.close();
|
|
9453
|
+
} catch {
|
|
9454
|
+
}
|
|
9455
|
+
}
|
|
9456
|
+
};
|
|
9457
|
+
return {
|
|
9458
|
+
redirectUri,
|
|
9459
|
+
port,
|
|
9460
|
+
waitForCode(state) {
|
|
9461
|
+
expectedState = state;
|
|
9462
|
+
logger?.debug(`oauth-callback: waiting for browser at ${redirectUri}`);
|
|
9463
|
+
if (!settled2 && timer === void 0) {
|
|
9464
|
+
timer = setTimeout(() => {
|
|
9465
|
+
settleReject(
|
|
9466
|
+
new Error(
|
|
9467
|
+
`oauth-callback: timed out after ${timeoutMs}ms waiting for browser login at ${redirectUri}`
|
|
9468
|
+
)
|
|
9469
|
+
);
|
|
9470
|
+
}, timeoutMs);
|
|
9471
|
+
timer.unref();
|
|
9472
|
+
}
|
|
9473
|
+
return codePromise;
|
|
9474
|
+
},
|
|
9475
|
+
close
|
|
9476
|
+
};
|
|
9477
|
+
}
|
|
9478
|
+
function handleRequest(req, res, expectedState, onCode, onError, logger) {
|
|
9479
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
9480
|
+
if (url.pathname !== "/callback") {
|
|
9481
|
+
if (url.pathname === "/favicon.ico") {
|
|
9482
|
+
res.statusCode = 404;
|
|
9483
|
+
res.end();
|
|
9484
|
+
return;
|
|
9485
|
+
}
|
|
9486
|
+
res.statusCode = 404;
|
|
9487
|
+
res.end("not found");
|
|
9488
|
+
return;
|
|
9489
|
+
}
|
|
9490
|
+
const errorParam = url.searchParams.get("error");
|
|
9491
|
+
if (errorParam) {
|
|
9492
|
+
const desc = url.searchParams.get("error_description") ?? "";
|
|
9493
|
+
logger?.warn(`oauth-callback: server returned error ${errorParam} ${desc}`);
|
|
9494
|
+
res.statusCode = 400;
|
|
9495
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
9496
|
+
res.end(ERROR_HTML(`${errorParam}${desc ? ": " + desc : ""}`));
|
|
9497
|
+
onError(new Error(`OAuth provider returned error: ${errorParam}${desc ? " \u2014 " + desc : ""}`));
|
|
9498
|
+
return;
|
|
9499
|
+
}
|
|
9500
|
+
const code = url.searchParams.get("code");
|
|
9501
|
+
const state = url.searchParams.get("state");
|
|
9502
|
+
if (!code) {
|
|
9503
|
+
res.statusCode = 400;
|
|
9504
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
9505
|
+
res.end(ERROR_HTML("missing code"));
|
|
9506
|
+
return;
|
|
9507
|
+
}
|
|
9508
|
+
if (expectedState && state && state !== expectedState) {
|
|
9509
|
+
res.statusCode = 400;
|
|
9510
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
9511
|
+
res.end(ERROR_HTML("state mismatch \u2014 possible CSRF"));
|
|
9512
|
+
onError(new Error("OAuth callback state did not match \u2014 refusing to exchange code"));
|
|
9513
|
+
return;
|
|
9514
|
+
}
|
|
9515
|
+
res.statusCode = 200;
|
|
9516
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
9517
|
+
res.end(SUCCESS_HTML);
|
|
9518
|
+
logger?.debug(`oauth-callback: received authorization code (${code.length} chars)`);
|
|
9519
|
+
onCode(code);
|
|
9520
|
+
}
|
|
9521
|
+
async function openBrowser(url) {
|
|
9522
|
+
try {
|
|
9523
|
+
const mod = await import("node:child_process").catch(() => null);
|
|
9524
|
+
if (!mod) return false;
|
|
9525
|
+
const cmd = platformOpenCommand(url);
|
|
9526
|
+
if (!cmd) return false;
|
|
9527
|
+
const child = mod.spawn(cmd.cmd, cmd.args, { detached: true, stdio: "ignore" });
|
|
9528
|
+
child.unref();
|
|
9529
|
+
return true;
|
|
9530
|
+
} catch {
|
|
9531
|
+
return false;
|
|
9532
|
+
}
|
|
9533
|
+
}
|
|
9534
|
+
function platformOpenCommand(url) {
|
|
9535
|
+
switch (process.platform) {
|
|
9536
|
+
case "darwin":
|
|
9537
|
+
return { cmd: "open", args: [url] };
|
|
9538
|
+
case "win32":
|
|
9539
|
+
return { cmd: "rundll32", args: ["url.dll,FileProtocolHandler", url] };
|
|
9540
|
+
default:
|
|
9541
|
+
return { cmd: "xdg-open", args: [url] };
|
|
9542
|
+
}
|
|
9543
|
+
}
|
|
9544
|
+
|
|
9545
|
+
// src/facade/mcp-core/oauth/token-store.ts
|
|
9546
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
9547
|
+
import { dirname, join as join2 } from "node:path";
|
|
9548
|
+
function defaultCredentialsDir() {
|
|
9549
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
9550
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
9551
|
+
if (xdg && xdg.length > 0) return join2(xdg, "indusagi", "credentials", "mcp");
|
|
9552
|
+
if (home.length > 0) return join2(home, ".config", "indusagi", "credentials", "mcp");
|
|
9553
|
+
return join2(process.cwd(), ".indusagi", "credentials", "mcp");
|
|
9554
|
+
}
|
|
9555
|
+
function resolveCredentialsDir(override) {
|
|
9556
|
+
return override && override.length > 0 ? override : defaultCredentialsDir();
|
|
9557
|
+
}
|
|
9558
|
+
function credentialsPath(credentialsDir, id) {
|
|
9559
|
+
const safe = id.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
9560
|
+
return join2(credentialsDir, `${safe}.json`);
|
|
9561
|
+
}
|
|
9562
|
+
function readMcpOAuthState(credentialsDir, id) {
|
|
9563
|
+
const path2 = credentialsPath(credentialsDir, id);
|
|
9564
|
+
if (!existsSync(path2)) return void 0;
|
|
9565
|
+
let raw;
|
|
9566
|
+
try {
|
|
9567
|
+
raw = readFileSync(path2, "utf8");
|
|
9568
|
+
} catch (e) {
|
|
9569
|
+
return void 0;
|
|
9570
|
+
}
|
|
9571
|
+
let parsed;
|
|
9572
|
+
try {
|
|
9573
|
+
parsed = JSON.parse(raw);
|
|
9574
|
+
} catch (e) {
|
|
9575
|
+
return void 0;
|
|
9576
|
+
}
|
|
9577
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
9578
|
+
return { id, updatedAt: Date.now(), ...parsed };
|
|
9579
|
+
}
|
|
9580
|
+
function writeMcpOAuthState(credentialsDir, state) {
|
|
9581
|
+
const path2 = credentialsPath(credentialsDir, state.id);
|
|
9582
|
+
mkdirSync(dirname(path2), { recursive: true });
|
|
9583
|
+
const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
|
|
9584
|
+
const json = JSON.stringify({ ...state, updatedAt: Date.now() }, null, 2);
|
|
9585
|
+
writeFileSync(tmp, json, { mode: 384 });
|
|
9586
|
+
renameSync(tmp, path2);
|
|
9587
|
+
try {
|
|
9588
|
+
chmodSync(path2, 384);
|
|
9589
|
+
} catch {
|
|
9590
|
+
}
|
|
9591
|
+
}
|
|
9592
|
+
function clearMcpOAuthState(credentialsDir, id) {
|
|
9593
|
+
const path2 = credentialsPath(credentialsDir, id);
|
|
9594
|
+
try {
|
|
9595
|
+
const fs2 = __require("node:fs");
|
|
9596
|
+
fs2.rmSync(path2, { force: true });
|
|
9597
|
+
} catch {
|
|
9598
|
+
}
|
|
9599
|
+
}
|
|
9600
|
+
|
|
9601
|
+
// src/facade/mcp-core/oauth/provider.ts
|
|
9602
|
+
function randomState() {
|
|
9603
|
+
const bytes = new Uint8Array(16);
|
|
9604
|
+
crypto.getRandomValues(bytes);
|
|
9605
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
9606
|
+
}
|
|
9607
|
+
function toSdkTokens(t) {
|
|
9608
|
+
return {
|
|
9609
|
+
access_token: t.access_token,
|
|
9610
|
+
refresh_token: t.refresh_token,
|
|
9611
|
+
scope: t.scope,
|
|
9612
|
+
token_type: t.token_type ?? "Bearer",
|
|
9613
|
+
expires_in: t.expires_at !== void 0 ? Math.max(0, Math.floor((t.expires_at - Date.now()) / 1e3)) : void 0
|
|
9614
|
+
};
|
|
9615
|
+
}
|
|
9616
|
+
function toSdkClientInfo(c) {
|
|
9617
|
+
return {
|
|
9618
|
+
client_id: c.client_id,
|
|
9619
|
+
client_secret: c.client_secret,
|
|
9620
|
+
client_id_issued_at: c.client_id_issued_at,
|
|
9621
|
+
client_secret_expires_at: c.client_secret_expires_at
|
|
9622
|
+
};
|
|
9623
|
+
}
|
|
9624
|
+
async function createMcpOAuthProvider(config) {
|
|
9625
|
+
const credentialsDir = resolveCredentialsDir(config.credentialsDir);
|
|
9626
|
+
const openBrowserEnabled = config.openBrowser ?? Boolean(process.env.CI !== "true" && process.stdout.isTTY);
|
|
9627
|
+
let callback;
|
|
9628
|
+
let redirectUrl;
|
|
9629
|
+
if (config.redirectUrl) {
|
|
9630
|
+
redirectUrl = config.redirectUrl;
|
|
9631
|
+
} else {
|
|
9632
|
+
callback = await startOAuthCallbackServer({
|
|
9633
|
+
logger: config.logger ? { debug: config.logger.debug, warn: config.logger.warn } : void 0
|
|
9634
|
+
});
|
|
9635
|
+
redirectUrl = callback.redirectUri;
|
|
9636
|
+
}
|
|
9637
|
+
const clientMetadata = {
|
|
9638
|
+
client_name: config.clientMetadata?.client_name ?? "indusagi-mcp-client",
|
|
9639
|
+
client_uri: config.clientMetadata?.client_uri,
|
|
9640
|
+
logo_uri: config.clientMetadata?.logo_uri,
|
|
9641
|
+
contacts: config.clientMetadata?.contacts ? [...config.clientMetadata.contacts] : void 0,
|
|
9642
|
+
tos_uri: config.clientMetadata?.tos_uri,
|
|
9643
|
+
policy_uri: config.clientMetadata?.policy_uri,
|
|
9644
|
+
redirect_uris: config.clientMetadata?.redirect_uris ? [...config.clientMetadata.redirect_uris] : [redirectUrl],
|
|
9645
|
+
token_endpoint_auth_method: config.clientMetadata?.token_endpoint_auth_method ?? "none",
|
|
9646
|
+
grant_types: config.clientMetadata?.grant_types ? [...config.clientMetadata.grant_types] : ["authorization_code", "refresh_token"],
|
|
9647
|
+
response_types: config.clientMetadata?.response_types ? [...config.clientMetadata.response_types] : ["code"],
|
|
9648
|
+
scope: config.clientMetadata?.scope
|
|
9649
|
+
};
|
|
9650
|
+
let lastAuthUrl;
|
|
9651
|
+
let lastState;
|
|
9652
|
+
let pendingCode;
|
|
9653
|
+
const provider = {
|
|
9654
|
+
id: config.id,
|
|
9655
|
+
credentialsDir,
|
|
9656
|
+
redirectUrl,
|
|
9657
|
+
get clientMetadata() {
|
|
9658
|
+
return clientMetadata;
|
|
9659
|
+
},
|
|
9660
|
+
state() {
|
|
9661
|
+
const s = randomState();
|
|
9662
|
+
lastState = s;
|
|
9663
|
+
return s;
|
|
9664
|
+
},
|
|
9665
|
+
clientInformation() {
|
|
9666
|
+
const state = readMcpOAuthState(credentialsDir, config.id);
|
|
9667
|
+
return state?.client ? toSdkClientInfo(state.client) : void 0;
|
|
9668
|
+
},
|
|
9669
|
+
saveClientInformation(client) {
|
|
9670
|
+
const state = readMcpOAuthState(credentialsDir, config.id) ?? {
|
|
9671
|
+
id: config.id,
|
|
9672
|
+
updatedAt: Date.now()
|
|
9673
|
+
};
|
|
9674
|
+
writeMcpOAuthState(credentialsDir, {
|
|
9675
|
+
...state,
|
|
9676
|
+
client: {
|
|
9677
|
+
client_id: client.client_id,
|
|
9678
|
+
client_secret: client.client_secret,
|
|
9679
|
+
client_id_issued_at: client.client_id_issued_at,
|
|
9680
|
+
client_secret_expires_at: client.client_secret_expires_at
|
|
9681
|
+
}
|
|
9682
|
+
});
|
|
9683
|
+
},
|
|
9684
|
+
tokens() {
|
|
9685
|
+
const state = readMcpOAuthState(credentialsDir, config.id);
|
|
9686
|
+
return state?.tokens ? toSdkTokens(state.tokens) : void 0;
|
|
9687
|
+
},
|
|
9688
|
+
saveTokens(tokens) {
|
|
9689
|
+
const state = readMcpOAuthState(credentialsDir, config.id) ?? {
|
|
9690
|
+
id: config.id,
|
|
9691
|
+
updatedAt: Date.now()
|
|
9692
|
+
};
|
|
9693
|
+
writeMcpOAuthState(credentialsDir, {
|
|
9694
|
+
...state,
|
|
9695
|
+
tokens: {
|
|
9696
|
+
access_token: tokens.access_token,
|
|
9697
|
+
refresh_token: tokens.refresh_token,
|
|
9698
|
+
scope: tokens.scope,
|
|
9699
|
+
token_type: tokens.token_type,
|
|
9700
|
+
expires_at: tokens.expires_in !== void 0 ? Date.now() + Math.trunc(tokens.expires_in) * 1e3 : void 0
|
|
9701
|
+
}
|
|
9702
|
+
});
|
|
9703
|
+
},
|
|
9704
|
+
async redirectToAuthorization(authorizationUrl) {
|
|
9705
|
+
const url = authorizationUrl.toString();
|
|
9706
|
+
lastAuthUrl = url;
|
|
9707
|
+
try {
|
|
9708
|
+
lastState = new URL(url).searchParams.get("state") ?? lastState;
|
|
9709
|
+
} catch {
|
|
9710
|
+
}
|
|
9711
|
+
const prompt = `oauth: open this URL in your browser to authorize Zoho MCP:
|
|
9712
|
+
${url}`;
|
|
9713
|
+
config.logger?.info?.(prompt);
|
|
9714
|
+
config.logger?.debug?.(prompt);
|
|
9715
|
+
console.error(`
|
|
9716
|
+
[zoho oauth] Authorize in browser:
|
|
9717
|
+
${url}
|
|
9718
|
+
`);
|
|
9719
|
+
let opened = false;
|
|
9720
|
+
if (openBrowserEnabled) opened = await openBrowser(url);
|
|
9721
|
+
if (!opened) {
|
|
9722
|
+
const tip = "oauth: could not auto-open browser. Visit the URL above manually.";
|
|
9723
|
+
config.logger?.info?.(tip);
|
|
9724
|
+
config.logger?.debug?.(tip);
|
|
9725
|
+
console.error(`[zoho oauth] ${tip}`);
|
|
9726
|
+
} else {
|
|
9727
|
+
config.logger?.debug?.("oauth: browser open launched");
|
|
9728
|
+
}
|
|
9729
|
+
},
|
|
9730
|
+
saveCodeVerifier(verifier) {
|
|
9731
|
+
const state = readMcpOAuthState(credentialsDir, config.id) ?? {
|
|
9732
|
+
id: config.id,
|
|
9733
|
+
updatedAt: Date.now()
|
|
9734
|
+
};
|
|
9735
|
+
writeMcpOAuthState(credentialsDir, { ...state, codeVerifier: verifier });
|
|
9736
|
+
},
|
|
9737
|
+
codeVerifier() {
|
|
9738
|
+
const state = readMcpOAuthState(credentialsDir, config.id);
|
|
9739
|
+
const verifier = state?.codeVerifier;
|
|
9740
|
+
if (!verifier) throw new Error("No PKCE code verifier saved");
|
|
9741
|
+
return verifier;
|
|
9742
|
+
},
|
|
9743
|
+
invalidateCredentials(scope) {
|
|
9744
|
+
if (scope === "all") {
|
|
9745
|
+
clearMcpOAuthState(credentialsDir, config.id);
|
|
9746
|
+
return;
|
|
9747
|
+
}
|
|
9748
|
+
const state = readMcpOAuthState(credentialsDir, config.id);
|
|
9749
|
+
if (!state) return;
|
|
9750
|
+
const next = { ...state };
|
|
9751
|
+
if (scope === "tokens" || scope === "verifier") next.tokens = void 0;
|
|
9752
|
+
if (scope === "verifier") next.codeVerifier = void 0;
|
|
9753
|
+
if (scope === "discovery") {
|
|
9754
|
+
next.resourceMetadata = void 0;
|
|
9755
|
+
next.authServerMetadata = void 0;
|
|
9756
|
+
}
|
|
9757
|
+
writeMcpOAuthState(credentialsDir, next);
|
|
9758
|
+
},
|
|
9759
|
+
async waitForAuthorizationCode() {
|
|
9760
|
+
if (pendingCode) return pendingCode;
|
|
9761
|
+
if (!callback) {
|
|
9762
|
+
throw new Error(
|
|
9763
|
+
"oauth: no local callback server (custom redirectUrl set). Pass the authorization code via finishAuth yourself."
|
|
9764
|
+
);
|
|
9765
|
+
}
|
|
9766
|
+
config.logger?.info("oauth: waiting for browser authorization\u2026");
|
|
9767
|
+
console.error("[zoho oauth] Waiting for browser authorization\u2026");
|
|
9768
|
+
const code = await callback.waitForCode(lastState);
|
|
9769
|
+
pendingCode = code;
|
|
9770
|
+
return code;
|
|
9771
|
+
},
|
|
9772
|
+
dispose() {
|
|
9773
|
+
callback?.close();
|
|
9774
|
+
callback = void 0;
|
|
9775
|
+
},
|
|
9776
|
+
lastAuthorizationUrl() {
|
|
9777
|
+
return lastAuthUrl;
|
|
9778
|
+
}
|
|
9779
|
+
};
|
|
9780
|
+
return provider;
|
|
9781
|
+
}
|
|
9782
|
+
|
|
9783
|
+
// src/facade/mcp-core/oauth/index.ts
|
|
9784
|
+
import {
|
|
9785
|
+
auth,
|
|
9786
|
+
discoverOAuthServerInfo,
|
|
9787
|
+
startAuthorization,
|
|
9788
|
+
exchangeAuthorization,
|
|
9789
|
+
refreshAuthorization,
|
|
9790
|
+
registerClient,
|
|
9791
|
+
extractWWWAuthenticateParams
|
|
9792
|
+
} from "@modelcontextprotocol/sdk/client/auth.js";
|
|
9793
|
+
|
|
9794
|
+
// src/facade/mcp-core/toolbox-bridge.ts
|
|
9795
|
+
var NOOP_FS_NOT_SUPPORTED = "not supported in MCP-wrapped tools";
|
|
9796
|
+
var noopFs = {
|
|
9797
|
+
readFile: () => {
|
|
9798
|
+
throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
|
|
9799
|
+
},
|
|
9800
|
+
writeFile: () => {
|
|
9801
|
+
throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
|
|
9802
|
+
},
|
|
9803
|
+
stat: () => {
|
|
9804
|
+
throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
|
|
9805
|
+
},
|
|
9806
|
+
readdir: () => {
|
|
9807
|
+
throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
|
|
9808
|
+
},
|
|
9809
|
+
mkdir: () => {
|
|
9810
|
+
throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
|
|
9811
|
+
},
|
|
9812
|
+
rm: () => {
|
|
9813
|
+
throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
|
|
9814
|
+
},
|
|
9815
|
+
exists: () => {
|
|
9816
|
+
throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
|
|
9817
|
+
}
|
|
9818
|
+
};
|
|
9819
|
+
var noopShell = {
|
|
9820
|
+
exec: () => {
|
|
9821
|
+
throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
|
|
9822
|
+
},
|
|
9823
|
+
spawn: () => {
|
|
9824
|
+
throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
|
|
9825
|
+
}
|
|
9826
|
+
};
|
|
9827
|
+
function makeToolContext(signal) {
|
|
9828
|
+
return {
|
|
9829
|
+
cwd: process.cwd(),
|
|
9830
|
+
fs: noopFs,
|
|
9831
|
+
shell: noopShell,
|
|
9832
|
+
signal,
|
|
9833
|
+
budget: { kind: "tail", maxBytes: 5e4 }
|
|
9834
|
+
};
|
|
9835
|
+
}
|
|
9836
|
+
function describeThrow2(err) {
|
|
9837
|
+
if (err instanceof Error) return err.message;
|
|
9838
|
+
if (typeof err === "string") return err;
|
|
9839
|
+
try {
|
|
9840
|
+
return JSON.stringify(err);
|
|
9841
|
+
} catch {
|
|
9842
|
+
return String(err);
|
|
9843
|
+
}
|
|
9844
|
+
}
|
|
9845
|
+
function projectMcpResult(result, logger) {
|
|
9846
|
+
const blocks = [];
|
|
9847
|
+
if (result.content && result.content.length > 0) {
|
|
9848
|
+
for (const block of result.content) {
|
|
9849
|
+
if (block.type === "text") {
|
|
9850
|
+
blocks.push({ kind: "text", text: block.text });
|
|
9851
|
+
} else if (block.type === "image") {
|
|
9852
|
+
const len = typeof block.data === "string" ? block.data.length : 0;
|
|
9853
|
+
blocks.push({ kind: "text", text: `[image ${block.mimeType}, ${len} chars base64]` });
|
|
9854
|
+
logger?.debug(`mcp bridge: image block collapsed to text mention (${block.mimeType})`);
|
|
9855
|
+
} else if (block.type === "resource") {
|
|
9856
|
+
blocks.push({ kind: "text", text: `[resource ${block.resource.uri}]` });
|
|
9857
|
+
} else {
|
|
9858
|
+
blocks.push({ kind: "text", text: "[unknown content block]" });
|
|
9859
|
+
}
|
|
9860
|
+
}
|
|
9861
|
+
}
|
|
9862
|
+
if (result.structuredContent !== void 0) {
|
|
9863
|
+
blocks.push({ kind: "json", value: result.structuredContent });
|
|
9864
|
+
}
|
|
9865
|
+
const content = blocks.length > 0 ? blocks : [{ kind: "text", text: "(no content)" }];
|
|
9866
|
+
return {
|
|
9867
|
+
content,
|
|
9868
|
+
...result.isError === true ? { isError: true } : {}
|
|
9869
|
+
};
|
|
9870
|
+
}
|
|
9871
|
+
function makeRunner(client, remoteName, logger) {
|
|
9872
|
+
return async (input, ctx) => {
|
|
9873
|
+
if (ctx.signal.aborted) {
|
|
9874
|
+
return {
|
|
9875
|
+
content: [{ kind: "text", text: "Cancelled before tool could start." }],
|
|
9876
|
+
isError: true
|
|
9877
|
+
};
|
|
9878
|
+
}
|
|
9879
|
+
try {
|
|
9880
|
+
const args = input ?? {};
|
|
9881
|
+
const result = await client.callTool(remoteName, args);
|
|
9882
|
+
return projectMcpResult(result, logger);
|
|
9883
|
+
} catch (err) {
|
|
9884
|
+
const wrapped = err instanceof MCPError ? err : createServerError(describeThrow2(err), client.serverName);
|
|
9885
|
+
logger?.error(`mcp bridge: tool "${remoteName}" failed: ${wrapped.message}`, wrapped);
|
|
9886
|
+
return {
|
|
9887
|
+
content: [{ kind: "text", text: `MCP tool failed: ${wrapped.message}` }],
|
|
9888
|
+
isError: true
|
|
9889
|
+
};
|
|
9890
|
+
}
|
|
9891
|
+
};
|
|
9892
|
+
}
|
|
9893
|
+
async function mcpClientToToolBox(client, options = {}) {
|
|
9894
|
+
const prefix = options.namePrefix ?? "mcp";
|
|
9895
|
+
const logger = options.logger;
|
|
9896
|
+
const tools = await client.listTools();
|
|
9897
|
+
const filtered = typeof options.filter === "function" ? tools.filter((t) => options.filter(t)) : tools;
|
|
9898
|
+
const byName = /* @__PURE__ */ new Map();
|
|
9899
|
+
for (const tool of filtered) {
|
|
9900
|
+
const remoteName = tool.name;
|
|
9901
|
+
const prefixed = `${prefix}__${remoteName}`;
|
|
9902
|
+
const description = tool.description && tool.description.length > 0 ? tool.description : `<tool "${remoteName}" from MCP server "${client.serverName}">`;
|
|
9903
|
+
const parameters = tool.inputSchema;
|
|
9904
|
+
const def = defineTool({
|
|
9905
|
+
name: prefixed,
|
|
9906
|
+
description,
|
|
9907
|
+
parameters,
|
|
9908
|
+
run: makeRunner(client, remoteName, logger)
|
|
9909
|
+
});
|
|
9910
|
+
byName.set(prefixed, def);
|
|
9911
|
+
}
|
|
9912
|
+
const runner = {
|
|
9913
|
+
async run(call, signal) {
|
|
9914
|
+
const def = byName.get(call.name);
|
|
9915
|
+
if (!def) {
|
|
9916
|
+
return {
|
|
9917
|
+
id: call.id,
|
|
9918
|
+
output: `No tool "${call.name}" on MCP server "${client.serverName}".`,
|
|
9919
|
+
isError: true
|
|
9920
|
+
};
|
|
9921
|
+
}
|
|
9922
|
+
return def.invoke(call, makeToolContext(signal));
|
|
9923
|
+
}
|
|
9924
|
+
};
|
|
9925
|
+
return {
|
|
9926
|
+
descriptors() {
|
|
9927
|
+
return Array.from(byName.values()).map((d) => d.descriptor());
|
|
9928
|
+
},
|
|
9929
|
+
runner
|
|
9930
|
+
};
|
|
9931
|
+
}
|
|
9932
|
+
|
|
9933
|
+
// src/connectors-zoho/connect.ts
|
|
9934
|
+
async function connectZohoMcp(options) {
|
|
9935
|
+
const id = options.id ?? "zoho";
|
|
9936
|
+
const authMode = options.auth ?? "auto";
|
|
9937
|
+
let parsedUrl;
|
|
9938
|
+
try {
|
|
9939
|
+
parsedUrl = new URL(options.url);
|
|
9940
|
+
} catch {
|
|
9941
|
+
throw new Error(`connectZohoMcp: invalid url "${options.url}"`);
|
|
9942
|
+
}
|
|
9943
|
+
if (parsedUrl.protocol !== "https:" && parsedUrl.protocol !== "http:") {
|
|
9944
|
+
throw new Error(`connectZohoMcp: url must be http(s), got "${parsedUrl.protocol}"`);
|
|
9945
|
+
}
|
|
9946
|
+
let oauthProvider;
|
|
9947
|
+
if (authMode === "auto" || authMode === "oauth") {
|
|
9948
|
+
oauthProvider = await createMcpOAuthProvider({
|
|
9949
|
+
id,
|
|
9950
|
+
credentialsDir: options.tokenStorePath,
|
|
9951
|
+
redirectUrl: options.redirectUrl,
|
|
9952
|
+
// Default true outside CI so TUI sessions always get a browser redirect.
|
|
9953
|
+
openBrowser: options.openBrowser ?? (process.env.CI !== "true" && process.env.ZOHO_OPEN_BROWSER !== "0"),
|
|
9954
|
+
clientMetadata: {
|
|
9955
|
+
client_name: "indusagi-zoho-agent",
|
|
9956
|
+
scope: options.scopes?.join(" ")
|
|
9957
|
+
},
|
|
9958
|
+
logger: options.logger ? {
|
|
9959
|
+
debug: options.logger.debug,
|
|
9960
|
+
info: (msg) => {
|
|
9961
|
+
options.logger?.debug(msg);
|
|
9962
|
+
console.error(`[zoho] ${msg}`);
|
|
9963
|
+
},
|
|
9964
|
+
warn: (msg) => options.logger?.debug(msg),
|
|
9965
|
+
error: options.logger.error
|
|
9966
|
+
} : {
|
|
9967
|
+
debug: (msg) => {
|
|
9968
|
+
if (process.env.INDUSAGI_DEBUG) console.error(`[zoho debug] ${msg}`);
|
|
9969
|
+
},
|
|
9970
|
+
info: (msg) => console.error(`[zoho] ${msg}`),
|
|
9971
|
+
warn: (msg) => console.error(`[zoho] ${msg}`),
|
|
9972
|
+
error: (msg) => console.error(`[zoho error] ${msg}`)
|
|
9973
|
+
}
|
|
9974
|
+
});
|
|
9975
|
+
}
|
|
9976
|
+
const client = new MCPClient({
|
|
9977
|
+
name: id,
|
|
9978
|
+
config: {
|
|
9979
|
+
url: parsedUrl,
|
|
9980
|
+
headers: options.headers ? { ...options.headers } : void 0,
|
|
9981
|
+
...options.useSse ? { transport: "sse" } : {}
|
|
9982
|
+
},
|
|
9983
|
+
timeout: options.timeoutMs ?? 3e4,
|
|
9984
|
+
...oauthProvider ? {
|
|
9985
|
+
authProvider: oauthProvider
|
|
9986
|
+
} : {}
|
|
9987
|
+
});
|
|
9988
|
+
try {
|
|
9989
|
+
await client.connect();
|
|
9990
|
+
} catch (e) {
|
|
9991
|
+
oauthProvider?.dispose();
|
|
9992
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
9993
|
+
if (/authentication required|unauthorized|401/i.test(msg)) {
|
|
9994
|
+
throw new Error(
|
|
9995
|
+
`Zoho MCP authentication failed for "${id}".
|
|
9996
|
+
\u2022 Ensure ZOHO_MCP_URL is the full Connect URL from https://mcp.zoho.com
|
|
9997
|
+
\u2022 Complete the browser OAuth login when prompted
|
|
9998
|
+
\u2022 Or set auth: "none" and pass Authorization headers if you use a pre-authorized URL
|
|
9999
|
+
Original: ${msg}`,
|
|
10000
|
+
{ cause: e instanceof Error ? e : void 0 }
|
|
10001
|
+
);
|
|
10002
|
+
}
|
|
10003
|
+
throw e;
|
|
10004
|
+
}
|
|
10005
|
+
try {
|
|
10006
|
+
const toolBox2 = await mcpClientToToolBox(client, {
|
|
10007
|
+
namePrefix: id,
|
|
10008
|
+
logger: options.logger
|
|
10009
|
+
});
|
|
10010
|
+
const tools = await client.listTools();
|
|
10011
|
+
const prefixed = tools.map((t) => `${id}__${t.name}`);
|
|
10012
|
+
try {
|
|
10013
|
+
oauthProvider?.dispose();
|
|
10014
|
+
} catch {
|
|
10015
|
+
}
|
|
10016
|
+
let closed = false;
|
|
10017
|
+
return {
|
|
10018
|
+
id,
|
|
10019
|
+
toolBox: toolBox2,
|
|
10020
|
+
toolNames: prefixed,
|
|
10021
|
+
client,
|
|
10022
|
+
async close() {
|
|
10023
|
+
if (closed) return;
|
|
10024
|
+
closed = true;
|
|
10025
|
+
try {
|
|
10026
|
+
await client.disconnect();
|
|
10027
|
+
} catch (err) {
|
|
10028
|
+
options.logger?.error(`zoho close failed: ${err.message}`, err);
|
|
10029
|
+
} finally {
|
|
10030
|
+
try {
|
|
10031
|
+
oauthProvider?.dispose();
|
|
10032
|
+
} catch {
|
|
10033
|
+
}
|
|
10034
|
+
}
|
|
10035
|
+
}
|
|
10036
|
+
};
|
|
10037
|
+
} catch (e) {
|
|
10038
|
+
oauthProvider?.dispose();
|
|
10039
|
+
try {
|
|
10040
|
+
await client.disconnect();
|
|
10041
|
+
} catch {
|
|
10042
|
+
}
|
|
10043
|
+
throw e;
|
|
10044
|
+
}
|
|
10045
|
+
}
|
|
10046
|
+
|
|
10047
|
+
// src/connectors-zoho/toolbox.ts
|
|
10048
|
+
async function zohoToolBoxFromClient(client, options) {
|
|
10049
|
+
return mcpClientToToolBox(client, {
|
|
10050
|
+
...options,
|
|
10051
|
+
namePrefix: options?.id ?? "zoho"
|
|
10052
|
+
});
|
|
10053
|
+
}
|
|
10054
|
+
|
|
10055
|
+
// src/connectors-sarvam/index.ts
|
|
10056
|
+
var connectors_sarvam_exports = {};
|
|
10057
|
+
__export(connectors_sarvam_exports, {
|
|
10058
|
+
connectSarvamMcp: () => connectSarvamMcp,
|
|
10059
|
+
sarvamToolBoxFromClient: () => sarvamToolBoxFromClient
|
|
10060
|
+
});
|
|
10061
|
+
|
|
10062
|
+
// src/connectors-sarvam/connect.ts
|
|
10063
|
+
function resolveHome(p) {
|
|
10064
|
+
if (!p.startsWith("~")) return p;
|
|
10065
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
10066
|
+
return home.length > 0 ? p.replace(/^~/, home) : p;
|
|
10067
|
+
}
|
|
10068
|
+
async function connectSarvamMcp(options) {
|
|
10069
|
+
const id = options.id ?? "sarvam";
|
|
10070
|
+
const apiKey = (options.apiKey?.trim() || process.env.SARVAM_API_KEY?.trim() || "").trim();
|
|
10071
|
+
if (!apiKey) {
|
|
10072
|
+
throw new Error(
|
|
10073
|
+
"connectSarvamMcp: SARVAM_API_KEY missing (pass apiKey or set the env var)."
|
|
10074
|
+
);
|
|
10075
|
+
}
|
|
10076
|
+
const baseUrl = options.baseUrl ?? process.env.SARVAM_API_BASE_URL ?? "https://api.sarvam.ai";
|
|
10077
|
+
const basePath = resolveHome(options.basePath ?? process.env.SARVAM_MCP_BASE_PATH ?? "~/Desktop");
|
|
10078
|
+
const command = options.command ?? "uvx";
|
|
10079
|
+
const args = [...options.args ?? ["sarvam-mcp"]];
|
|
10080
|
+
const env = {
|
|
10081
|
+
...options.env ? { ...options.env } : {},
|
|
10082
|
+
SARVAM_API_KEY: apiKey,
|
|
10083
|
+
SARVAM_API_BASE_URL: baseUrl,
|
|
10084
|
+
SARVAM_MCP_BASE_PATH: basePath
|
|
10085
|
+
};
|
|
10086
|
+
const client = new MCPClient({
|
|
10087
|
+
name: id,
|
|
10088
|
+
config: { command, args, env },
|
|
10089
|
+
timeout: options.timeoutMs ?? 6e4
|
|
10090
|
+
});
|
|
10091
|
+
await client.connect();
|
|
10092
|
+
const toolBox2 = await mcpClientToToolBox(client, {
|
|
10093
|
+
namePrefix: id,
|
|
10094
|
+
logger: options.logger
|
|
10095
|
+
});
|
|
10096
|
+
const tools = await client.listTools();
|
|
10097
|
+
const prefixed = tools.map((t) => `${id}__${t.name}`);
|
|
10098
|
+
let closed = false;
|
|
10099
|
+
return {
|
|
10100
|
+
id,
|
|
10101
|
+
toolBox: toolBox2,
|
|
10102
|
+
toolNames: prefixed,
|
|
10103
|
+
client,
|
|
10104
|
+
async close() {
|
|
10105
|
+
if (closed) return;
|
|
10106
|
+
closed = true;
|
|
10107
|
+
try {
|
|
10108
|
+
await client.disconnect();
|
|
10109
|
+
} catch (e) {
|
|
10110
|
+
options.logger?.error(`sarvam close failed: ${e.message}`, e);
|
|
10111
|
+
}
|
|
10112
|
+
}
|
|
10113
|
+
};
|
|
10114
|
+
}
|
|
10115
|
+
|
|
10116
|
+
// src/connectors-sarvam/toolbox.ts
|
|
10117
|
+
async function sarvamToolBoxFromClient(client, options) {
|
|
10118
|
+
return mcpClientToToolBox(client, {
|
|
10119
|
+
...options,
|
|
10120
|
+
namePrefix: options?.id ?? "sarvam"
|
|
10121
|
+
});
|
|
10122
|
+
}
|
|
10123
|
+
|
|
8598
10124
|
// src/swarm/index.ts
|
|
8599
10125
|
var swarm_exports = {};
|
|
8600
10126
|
__export(swarm_exports, {
|
|
@@ -8624,14 +10150,14 @@ __export(swarm_exports, {
|
|
|
8624
10150
|
});
|
|
8625
10151
|
|
|
8626
10152
|
// src/swarm/coordinator.ts
|
|
8627
|
-
import { join as
|
|
10153
|
+
import { join as join5 } from "node:path";
|
|
8628
10154
|
|
|
8629
10155
|
// src/swarm/roster/manifest.ts
|
|
8630
10156
|
import { z as z2 } from "zod";
|
|
8631
10157
|
|
|
8632
10158
|
// src/swarm/kernel/json-cell.ts
|
|
8633
10159
|
import { mkdir as mkdir2, readFile as readFile2, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
8634
|
-
import { dirname, join as
|
|
10160
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
8635
10161
|
import { z } from "zod";
|
|
8636
10162
|
|
|
8637
10163
|
// src/swarm/kernel/faults.ts
|
|
@@ -8702,7 +10228,7 @@ var JsonCell = class _JsonCell {
|
|
|
8702
10228
|
parsed.error
|
|
8703
10229
|
);
|
|
8704
10230
|
}
|
|
8705
|
-
await mkdir2(
|
|
10231
|
+
await mkdir2(dirname2(path2), { recursive: true });
|
|
8706
10232
|
const opts = { ...GUARD_DEFAULTS, ...guard };
|
|
8707
10233
|
return new _JsonCell(path2, schema, parsed.data, opts);
|
|
8708
10234
|
}
|
|
@@ -8813,7 +10339,7 @@ var JsonCell = class _JsonCell {
|
|
|
8813
10339
|
try {
|
|
8814
10340
|
await mkdir2(this.lockDir);
|
|
8815
10341
|
await writeFile(
|
|
8816
|
-
|
|
10342
|
+
join3(this.lockDir, "owner.json"),
|
|
8817
10343
|
JSON.stringify({
|
|
8818
10344
|
pid: process.pid,
|
|
8819
10345
|
takenAt: Date.now(),
|
|
@@ -8851,7 +10377,7 @@ var JsonCell = class _JsonCell {
|
|
|
8851
10377
|
const now = Date.now();
|
|
8852
10378
|
try {
|
|
8853
10379
|
const marker = lockMarkerSchema.safeParse(
|
|
8854
|
-
JSON.parse(await readFile2(
|
|
10380
|
+
JSON.parse(await readFile2(join3(this.lockDir, "owner.json"), "utf8"))
|
|
8855
10381
|
);
|
|
8856
10382
|
if (marker.success) return now - marker.data.takenAt > staleMs;
|
|
8857
10383
|
} catch {
|
|
@@ -8877,7 +10403,7 @@ var JsonCell = class _JsonCell {
|
|
|
8877
10403
|
|
|
8878
10404
|
// src/swarm/kernel/jsonl-log.ts
|
|
8879
10405
|
import { appendFile as appendFile2, mkdir as mkdir3, readFile as readFile3 } from "node:fs/promises";
|
|
8880
|
-
import { dirname as
|
|
10406
|
+
import { dirname as dirname3 } from "node:path";
|
|
8881
10407
|
function hasCode2(err, code) {
|
|
8882
10408
|
return typeof err === "object" && err !== null && "code" in err && err.code === code;
|
|
8883
10409
|
}
|
|
@@ -8891,7 +10417,7 @@ var JsonlLog = class _JsonlLog {
|
|
|
8891
10417
|
}
|
|
8892
10418
|
/** Open (or adopt) the log at `path`, ensuring its parent directory exists. */
|
|
8893
10419
|
static async open(path2, schema) {
|
|
8894
|
-
await mkdir3(
|
|
10420
|
+
await mkdir3(dirname3(path2), { recursive: true });
|
|
8895
10421
|
return new _JsonlLog(path2, schema);
|
|
8896
10422
|
}
|
|
8897
10423
|
/**
|
|
@@ -9536,7 +11062,7 @@ function isTicket(value) {
|
|
|
9536
11062
|
}
|
|
9537
11063
|
|
|
9538
11064
|
// src/swarm/postbox/channel.ts
|
|
9539
|
-
import { join as
|
|
11065
|
+
import { join as join4 } from "node:path";
|
|
9540
11066
|
import { z as z5 } from "zod";
|
|
9541
11067
|
|
|
9542
11068
|
// src/swarm/postbox/codecs.ts
|
|
@@ -9650,10 +11176,10 @@ var cursorSchema = z5.object({
|
|
|
9650
11176
|
seen: z5.number().int().nonnegative()
|
|
9651
11177
|
});
|
|
9652
11178
|
function transcriptPath(root) {
|
|
9653
|
-
return
|
|
11179
|
+
return join4(root, "transcript.jsonl");
|
|
9654
11180
|
}
|
|
9655
11181
|
function cursorPath(root, readerId) {
|
|
9656
|
-
return
|
|
11182
|
+
return join4(root, "cursors", `${encodeReaderId(readerId)}.json`);
|
|
9657
11183
|
}
|
|
9658
11184
|
function encodeReaderId(readerId) {
|
|
9659
11185
|
return encodeURIComponent(readerId);
|
|
@@ -10083,14 +11609,14 @@ var Crew = class _Crew {
|
|
|
10083
11609
|
*/
|
|
10084
11610
|
static async open(opts) {
|
|
10085
11611
|
const now = opts.now ?? Date.now;
|
|
10086
|
-
const manifest = await CrewManifest.open(
|
|
11612
|
+
const manifest = await CrewManifest.open(join5(opts.dir, "manifest.json"));
|
|
10087
11613
|
await manifest.create().catch((err) => {
|
|
10088
11614
|
if (SwarmFault.is(err, "conflict")) return;
|
|
10089
11615
|
throw err;
|
|
10090
11616
|
});
|
|
10091
|
-
const board = await TicketBoard.open(
|
|
10092
|
-
const channel = await Channel.open(
|
|
10093
|
-
const activity = await ActivityLog.open(
|
|
11617
|
+
const board = await TicketBoard.open(join5(opts.dir, "board.json"), now);
|
|
11618
|
+
const channel = await Channel.open(join5(opts.dir, "channel"));
|
|
11619
|
+
const activity = await ActivityLog.open(join5(opts.dir, "activity.jsonl"), now);
|
|
10094
11620
|
const workspace = opts.baseRepo !== void 0 ? new Workspace() : null;
|
|
10095
11621
|
const spawnAgent = opts.spawnAgent ?? defaultSpawnAgent;
|
|
10096
11622
|
return new _Crew(
|
|
@@ -10659,7 +12185,7 @@ function toAgentConfig(blueprint, cwd) {
|
|
|
10659
12185
|
|
|
10660
12186
|
// src/smithy/knowledge/loader.ts
|
|
10661
12187
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
10662
|
-
import { dirname as
|
|
12188
|
+
import { dirname as dirname4, isAbsolute, join as join6, resolve } from "node:path";
|
|
10663
12189
|
import { fileURLToPath } from "node:url";
|
|
10664
12190
|
var KnowledgeError = class extends Error {
|
|
10665
12191
|
constructor(message) {
|
|
@@ -10668,21 +12194,21 @@ var KnowledgeError = class extends Error {
|
|
|
10668
12194
|
}
|
|
10669
12195
|
};
|
|
10670
12196
|
function defaultDir() {
|
|
10671
|
-
return
|
|
12197
|
+
return dirname4(fileURLToPath(import.meta.url));
|
|
10672
12198
|
}
|
|
10673
12199
|
async function loadKnowledge(dir) {
|
|
10674
12200
|
const root = dir ? resolve(dir) : defaultDir();
|
|
10675
12201
|
const manifest = await readManifest(root);
|
|
10676
12202
|
const guides = [];
|
|
10677
12203
|
for (const entry of manifest.guides) {
|
|
10678
|
-
const path2 = isAbsolute(entry.file) ? entry.file :
|
|
12204
|
+
const path2 = isAbsolute(entry.file) ? entry.file : join6(root, entry.file);
|
|
10679
12205
|
const body = await readGuideBody(path2, entry.id);
|
|
10680
12206
|
guides.push({ ...entry, body });
|
|
10681
12207
|
}
|
|
10682
12208
|
return freezePack(manifest.version, guides);
|
|
10683
12209
|
}
|
|
10684
12210
|
async function readManifest(root) {
|
|
10685
|
-
const path2 =
|
|
12211
|
+
const path2 = join6(root, "manifest.json");
|
|
10686
12212
|
let raw;
|
|
10687
12213
|
try {
|
|
10688
12214
|
raw = await readFile4(path2, "utf8");
|
|
@@ -11487,6 +13013,43 @@ var DEFAULT_SECRET_PATTERNS = Object.freeze([
|
|
|
11487
13013
|
label: "private-key-block-value",
|
|
11488
13014
|
// a PEM-armoured private key body
|
|
11489
13015
|
value: /-----BEGIN(?:[A-Z ]+)?PRIVATE KEY-----/
|
|
13016
|
+
},
|
|
13017
|
+
// -----------------------------------------------------------------------
|
|
13018
|
+
// MCP / Zoho MCP additions.
|
|
13019
|
+
// -----------------------------------------------------------------------
|
|
13020
|
+
{
|
|
13021
|
+
label: "mcp-url-key-name",
|
|
13022
|
+
// mcp_url, mcpUrl, mcp_url_secret, MCP_URL, ZOHO_MCP_URL, ZOHO_MCP_TOKEN, etc.
|
|
13023
|
+
key: /(?:mcp[_-]?url|zoho[_-]?mcp|mcp[_-]?token)/i
|
|
13024
|
+
},
|
|
13025
|
+
{
|
|
13026
|
+
label: "pkce-verifier-key-name",
|
|
13027
|
+
// code_verifier, codeVerifier, pkce_verifier, etc.
|
|
13028
|
+
key: /(?:code[_-]?verifier|pkce[_-]?verifier)/i
|
|
13029
|
+
},
|
|
13030
|
+
{
|
|
13031
|
+
label: "mcp-server-url-value",
|
|
13032
|
+
// Zoho MCP URLs — the host is enough to make the URL sensitive.
|
|
13033
|
+
value: /\bhttps?:\/\/(?:[A-Za-z0-9-]+\.)*mcp\.zoho\.(?:com|in|eu|net|com\.au|jp)\b[^\s"'<>]*/i
|
|
13034
|
+
},
|
|
13035
|
+
// -----------------------------------------------------------------------
|
|
13036
|
+
// Sarvam MCP additions.
|
|
13037
|
+
// -----------------------------------------------------------------------
|
|
13038
|
+
{
|
|
13039
|
+
label: "sarvam-api-key-key-name",
|
|
13040
|
+
// SARVAM_API_KEY, sarvamApiKey, sarvam_api_key
|
|
13041
|
+
key: /sarvam[_-]?api[_-]?key/i
|
|
13042
|
+
},
|
|
13043
|
+
{
|
|
13044
|
+
label: "sarvam-api-key-value",
|
|
13045
|
+
// dashboard-issued key shape if Sarvam ever uses a prefix; fallback so
|
|
13046
|
+
// SARVAM_API_KEY=<anything> gets redacted even if no key pattern matched.
|
|
13047
|
+
value: /SARVAM_API_KEY\s*=\s*[^\s"'<>]+/i
|
|
13048
|
+
},
|
|
13049
|
+
{
|
|
13050
|
+
label: "api-subscription-key-header",
|
|
13051
|
+
// Sarvam's REST auth header name.
|
|
13052
|
+
value: /\bapi-subscription-key\s*[:=]\s*[A-Za-z0-9._~+/-]{8,}=*\b/i
|
|
11490
13053
|
}
|
|
11491
13054
|
]);
|
|
11492
13055
|
function isContainer(value) {
|
|
@@ -11986,8 +13549,8 @@ __export(shell_app_exports, {
|
|
|
11986
13549
|
|
|
11987
13550
|
// src/shell-app/cli.ts
|
|
11988
13551
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
11989
|
-
import { readFileSync, realpathSync } from "node:fs";
|
|
11990
|
-
import { dirname as
|
|
13552
|
+
import { readFileSync as readFileSync2, realpathSync } from "node:fs";
|
|
13553
|
+
import { dirname as dirname7, join as join9 } from "node:path";
|
|
11991
13554
|
|
|
11992
13555
|
// src/shell-app/invocation/flags.ts
|
|
11993
13556
|
var FLAG_SPECS = [
|
|
@@ -12225,7 +13788,7 @@ async function runStages(initial2, stages) {
|
|
|
12225
13788
|
|
|
12226
13789
|
// src/shell-app/config/locator.ts
|
|
12227
13790
|
import { homedir } from "node:os";
|
|
12228
|
-
import { join as
|
|
13791
|
+
import { join as join7 } from "node:path";
|
|
12229
13792
|
var BRAND = {
|
|
12230
13793
|
name: "indusagi",
|
|
12231
13794
|
configDir: ".indusagi",
|
|
@@ -12240,7 +13803,7 @@ var NodeLocator = class {
|
|
|
12240
13803
|
}
|
|
12241
13804
|
/** Join segments under the profile directory. */
|
|
12242
13805
|
under(...segments) {
|
|
12243
|
-
return
|
|
13806
|
+
return join7(this.home, this.brand.configDir, ...segments);
|
|
12244
13807
|
}
|
|
12245
13808
|
profileDir() {
|
|
12246
13809
|
return this.under();
|
|
@@ -12249,7 +13812,7 @@ var NodeLocator = class {
|
|
|
12249
13812
|
return this.under("settings.json");
|
|
12250
13813
|
}
|
|
12251
13814
|
projectSettingsPath(cwd) {
|
|
12252
|
-
return
|
|
13815
|
+
return join7(cwd, this.brand.configDir, "settings.json");
|
|
12253
13816
|
}
|
|
12254
13817
|
authPath() {
|
|
12255
13818
|
return this.under("auth.json");
|
|
@@ -12419,7 +13982,7 @@ function resolveModelId(settings, invocationModel) {
|
|
|
12419
13982
|
|
|
12420
13983
|
// src/shell-app/upgrade/upgrades.ts
|
|
12421
13984
|
import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile2 } from "node:fs/promises";
|
|
12422
|
-
import { dirname as
|
|
13985
|
+
import { dirname as dirname5 } from "node:path";
|
|
12423
13986
|
var UPGRADES = [
|
|
12424
13987
|
{
|
|
12425
13988
|
id: "ensure-profile-dir",
|
|
@@ -12455,7 +14018,7 @@ async function readApplied(path2) {
|
|
|
12455
14018
|
return new Set(parsed.filter((entry) => typeof entry === "string"));
|
|
12456
14019
|
}
|
|
12457
14020
|
async function writeApplied(path2, applied) {
|
|
12458
|
-
await mkdir4(
|
|
14021
|
+
await mkdir4(dirname5(path2), { recursive: true });
|
|
12459
14022
|
const ids = [...applied].sort();
|
|
12460
14023
|
await writeFile2(path2, `${JSON.stringify(ids, null, 2)}
|
|
12461
14024
|
`, "utf8");
|
|
@@ -15943,7 +17506,7 @@ function envName(suffix, brand = BRAND2) {
|
|
|
15943
17506
|
|
|
15944
17507
|
// src/shell-app/locate/locator.ts
|
|
15945
17508
|
import { mkdir as mkdir5 } from "node:fs/promises";
|
|
15946
|
-
import { dirname as
|
|
17509
|
+
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join8, resolve as resolve2 } from "node:path";
|
|
15947
17510
|
import { homedir as homedir3 } from "node:os";
|
|
15948
17511
|
var Locator = class {
|
|
15949
17512
|
home;
|
|
@@ -15993,12 +17556,12 @@ var Locator = class {
|
|
|
15993
17556
|
* is computed relative to this.
|
|
15994
17557
|
*/
|
|
15995
17558
|
profileDir() {
|
|
15996
|
-
return
|
|
17559
|
+
return join8(this.home, this.brand.profileDirName);
|
|
15997
17560
|
}
|
|
15998
17561
|
// --- Settings ----------------------------------------------------------
|
|
15999
17562
|
/** The global settings file inside the profile (e.g. `~/.indusagi/settings.json`). */
|
|
16000
17563
|
settingsPath() {
|
|
16001
|
-
return
|
|
17564
|
+
return join8(this.profileDir(), this.brand.settingsFileName);
|
|
16002
17565
|
}
|
|
16003
17566
|
/**
|
|
16004
17567
|
* The per-project settings file for a given working directory.
|
|
@@ -16012,20 +17575,20 @@ var Locator = class {
|
|
|
16012
17575
|
*/
|
|
16013
17576
|
projectSettingsPath(cwd = this.cwd) {
|
|
16014
17577
|
const root = isAbsolute2(cwd) ? cwd : resolve2(this.cwd, cwd);
|
|
16015
|
-
return
|
|
17578
|
+
return join8(root, this.brand.projectDirName, this.brand.projectSettingsFileName);
|
|
16016
17579
|
}
|
|
16017
17580
|
// --- Stores & directories ---------------------------------------------
|
|
16018
17581
|
/** Directory holding persisted conversation sessions (e.g. `~/.indusagi/sessions`). */
|
|
16019
17582
|
sessionsDir() {
|
|
16020
|
-
return
|
|
17583
|
+
return join8(this.profileDir(), this.brand.sessionsDirName);
|
|
16021
17584
|
}
|
|
16022
17585
|
/** The credential/auth store file inside the profile (e.g. `~/.indusagi/auth.json`). */
|
|
16023
17586
|
authStorePath() {
|
|
16024
|
-
return
|
|
17587
|
+
return join8(this.profileDir(), this.brand.authStoreFileName);
|
|
16025
17588
|
}
|
|
16026
17589
|
/** Directory holding diagnostic and crash logs (e.g. `~/.indusagi/logs`). */
|
|
16027
17590
|
logsDir() {
|
|
16028
|
-
return
|
|
17591
|
+
return join8(this.profileDir(), this.brand.logsDirName);
|
|
16029
17592
|
}
|
|
16030
17593
|
// --- I/O helpers (the only members that touch disk) -------------------
|
|
16031
17594
|
/**
|
|
@@ -16055,7 +17618,7 @@ var Locator = class {
|
|
|
16055
17618
|
* never fails on a missing directory.
|
|
16056
17619
|
*/
|
|
16057
17620
|
async ensureParentOf(filePath) {
|
|
16058
|
-
await this.ensureDir(
|
|
17621
|
+
await this.ensureDir(dirname6(filePath));
|
|
16059
17622
|
return filePath;
|
|
16060
17623
|
}
|
|
16061
17624
|
/**
|
|
@@ -16132,11 +17695,11 @@ function renderUsage() {
|
|
|
16132
17695
|
function resolveVersion() {
|
|
16133
17696
|
const FALLBACK = "0.0.0";
|
|
16134
17697
|
try {
|
|
16135
|
-
const here =
|
|
17698
|
+
const here = dirname7(fileURLToPath3(import.meta.url));
|
|
16136
17699
|
for (const up of ["..", "../..", "../../.."]) {
|
|
16137
|
-
const candidate =
|
|
17700
|
+
const candidate = join9(here, up, "package.json");
|
|
16138
17701
|
try {
|
|
16139
|
-
const raw =
|
|
17702
|
+
const raw = readFileSync2(candidate, "utf8");
|
|
16140
17703
|
const parsed = JSON.parse(raw);
|
|
16141
17704
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
16142
17705
|
return parsed.version;
|
|
@@ -16570,6 +18133,8 @@ var KNOWN_PROVIDERS = [
|
|
|
16570
18133
|
"azure",
|
|
16571
18134
|
"nvidia",
|
|
16572
18135
|
"kimi",
|
|
18136
|
+
"minimax",
|
|
18137
|
+
"minimax-cn",
|
|
16573
18138
|
"ollama",
|
|
16574
18139
|
"mock"
|
|
16575
18140
|
];
|
|
@@ -16637,8 +18202,10 @@ export {
|
|
|
16637
18202
|
interop_exports as interop,
|
|
16638
18203
|
runtime_exports as runtime,
|
|
16639
18204
|
connectors_saas_exports as saas,
|
|
18205
|
+
connectors_sarvam_exports as sarvamMcp,
|
|
16640
18206
|
shell_app_exports as shell,
|
|
16641
18207
|
smithy_exports as smithy,
|
|
16642
18208
|
swarm_exports as swarm,
|
|
16643
|
-
tracing_exports as tracing
|
|
18209
|
+
tracing_exports as tracing,
|
|
18210
|
+
connectors_zoho_exports as zoho
|
|
16644
18211
|
};
|