@mcp-ts/sdk 1.3.10 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +20 -27
  2. package/dist/adapters/agui-adapter.d.mts +16 -0
  3. package/dist/adapters/agui-adapter.d.ts +16 -0
  4. package/dist/adapters/agui-adapter.js +185 -0
  5. package/dist/adapters/agui-adapter.js.map +1 -1
  6. package/dist/adapters/agui-adapter.mjs +185 -0
  7. package/dist/adapters/agui-adapter.mjs.map +1 -1
  8. package/dist/adapters/agui-middleware.d.mts +2 -0
  9. package/dist/adapters/agui-middleware.d.ts +2 -0
  10. package/dist/adapters/agui-middleware.js.map +1 -1
  11. package/dist/adapters/agui-middleware.mjs.map +1 -1
  12. package/dist/adapters/ai-adapter.d.mts +21 -0
  13. package/dist/adapters/ai-adapter.d.ts +21 -0
  14. package/dist/adapters/ai-adapter.js +175 -0
  15. package/dist/adapters/ai-adapter.js.map +1 -1
  16. package/dist/adapters/ai-adapter.mjs +175 -0
  17. package/dist/adapters/ai-adapter.mjs.map +1 -1
  18. package/dist/adapters/langchain-adapter.d.mts +16 -0
  19. package/dist/adapters/langchain-adapter.d.ts +16 -0
  20. package/dist/adapters/langchain-adapter.js +179 -0
  21. package/dist/adapters/langchain-adapter.js.map +1 -1
  22. package/dist/adapters/langchain-adapter.mjs +179 -0
  23. package/dist/adapters/langchain-adapter.mjs.map +1 -1
  24. package/dist/client/index.d.mts +4 -190
  25. package/dist/client/index.d.ts +4 -190
  26. package/dist/client/index.js +218 -54
  27. package/dist/client/index.js.map +1 -1
  28. package/dist/client/index.mjs +215 -55
  29. package/dist/client/index.mjs.map +1 -1
  30. package/dist/client/react.d.mts +31 -17
  31. package/dist/client/react.d.ts +31 -17
  32. package/dist/client/react.js +447 -103
  33. package/dist/client/react.js.map +1 -1
  34. package/dist/client/react.mjs +443 -105
  35. package/dist/client/react.mjs.map +1 -1
  36. package/dist/client/vue.d.mts +5 -4
  37. package/dist/client/vue.d.ts +5 -4
  38. package/dist/client/vue.js +239 -63
  39. package/dist/client/vue.js.map +1 -1
  40. package/dist/client/vue.mjs +236 -64
  41. package/dist/client/vue.mjs.map +1 -1
  42. package/dist/index-DcYfpY3H.d.mts +295 -0
  43. package/dist/index-GfC_eNEv.d.ts +295 -0
  44. package/dist/index.d.mts +5 -3
  45. package/dist/index.d.ts +5 -3
  46. package/dist/index.js +1120 -59
  47. package/dist/index.js.map +1 -1
  48. package/dist/index.mjs +1097 -60
  49. package/dist/index.mjs.map +1 -1
  50. package/dist/server/index.d.mts +2 -2
  51. package/dist/server/index.d.ts +2 -2
  52. package/dist/server/index.js +18 -5
  53. package/dist/server/index.js.map +1 -1
  54. package/dist/server/index.mjs +18 -5
  55. package/dist/server/index.mjs.map +1 -1
  56. package/dist/shared/index.d.mts +86 -4
  57. package/dist/shared/index.d.ts +86 -4
  58. package/dist/shared/index.js +874 -0
  59. package/dist/shared/index.js.map +1 -1
  60. package/dist/shared/index.mjs +865 -1
  61. package/dist/shared/index.mjs.map +1 -1
  62. package/dist/tool-router-Bo8qZbsD.d.ts +325 -0
  63. package/dist/tool-router-XnWVxPzv.d.mts +325 -0
  64. package/dist/{types-CW6lghof.d.mts → types-CfCoIsWI.d.mts} +27 -1
  65. package/dist/{types-CW6lghof.d.ts → types-CfCoIsWI.d.ts} +27 -1
  66. package/package.json +15 -12
  67. package/src/adapters/agui-adapter.ts +79 -0
  68. package/src/adapters/ai-adapter.ts +75 -0
  69. package/src/adapters/langchain-adapter.ts +75 -1
  70. package/src/client/core/app-host.ts +252 -65
  71. package/src/client/core/constants.ts +30 -0
  72. package/src/client/index.ts +6 -1
  73. package/src/client/react/index.ts +3 -0
  74. package/src/client/react/use-app-host.ts +8 -15
  75. package/src/client/react/use-mcp-apps.tsx +262 -49
  76. package/src/client/react/use-mcp.ts +23 -12
  77. package/src/client/utils/app-host-utils.ts +62 -0
  78. package/src/client/vue/use-mcp.ts +23 -12
  79. package/src/server/index.ts +2 -0
  80. package/src/server/mcp/oauth-client.ts +34 -9
  81. package/src/shared/index.ts +36 -0
  82. package/src/shared/meta-tools.ts +387 -0
  83. package/src/shared/schema-compressor.ts +124 -0
  84. package/src/shared/tool-index.ts +499 -0
  85. package/src/shared/tool-router.ts +469 -0
  86. package/src/shared/types.ts +30 -0
package/dist/index.mjs CHANGED
@@ -1725,13 +1725,24 @@ var MCPClient = class _MCPClient {
1725
1725
  }
1726
1726
  } catch (error) {
1727
1727
  if (error instanceof UnauthorizedError$1 || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
1728
- this.emitStateChange("AUTHENTICATING");
1729
- console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
1730
- await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3), false);
1731
1728
  let authUrl = "";
1732
1729
  if (this.oauthProvider) {
1733
- authUrl = this.oauthProvider.authUrl || "";
1730
+ authUrl = (this.oauthProvider.authUrl || "").trim();
1731
+ }
1732
+ if (!authUrl) {
1733
+ const detail = error instanceof Error && error.message.trim().length > 0 ? error.message.trim() : "Unauthorized";
1734
+ const message = detail.toLowerCase() === "unauthorized" ? "OAuth authorization URL not available" : `OAuth authorization URL not available: ${detail}`;
1735
+ this.emitError(message, "auth");
1736
+ this.emitStateChange("FAILED");
1737
+ try {
1738
+ await storage.removeSession(this.identity, this.sessionId);
1739
+ } catch {
1740
+ }
1741
+ throw new Error(message);
1734
1742
  }
1743
+ this.emitStateChange("AUTHENTICATING");
1744
+ console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
1745
+ await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3), false);
1735
1746
  if (this.serverId) {
1736
1747
  this._onConnectionEvent.fire({
1737
1748
  type: "auth_required",
@@ -2184,7 +2195,9 @@ var MCPClient = class _MCPClient {
2184
2195
  * @returns Server name or undefined
2185
2196
  */
2186
2197
  getServerName() {
2187
- return this.serverName;
2198
+ const info = this.client?.getServerVersion();
2199
+ console.log("server info ->", info);
2200
+ return info?.title ?? info?.name ?? this.serverName;
2188
2201
  }
2189
2202
  /**
2190
2203
  * Gets the server ID
@@ -3269,22 +3282,88 @@ var SSEClient = class {
3269
3282
  }
3270
3283
  }
3271
3284
  };
3272
- var HOST_INFO = { name: "mcp-ts-host", version: "1.0.0" };
3273
- var SANDBOX_PERMISSIONS = [
3274
- "allow-scripts",
3275
- // Required for app JavaScript execution
3276
- "allow-forms",
3277
- // Required for form submissions
3278
- "allow-same-origin",
3279
- // Required for Blob URL correctness
3280
- "allow-modals",
3281
- // Required for dialogs/alerts
3282
- "allow-popups",
3283
- // Required for opening links
3284
- "allow-downloads"
3285
- // Required for file downloads
3286
- ].join(" ");
3287
- var MCP_URI_SCHEMES = ["ui://", "mcp-app://"];
3285
+
3286
+ // src/client/core/constants.ts
3287
+ var SANDBOX_PROXY_READY_METHOD = "ui/notifications/sandbox-proxy-ready";
3288
+ var SANDBOX_RESOURCE_READY_METHOD = "ui/notifications/sandbox-resource-ready";
3289
+ var APP_HOST_DEFAULTS = {
3290
+ /** Default timeout for waiting for the sandbox proxy to be ready (ms). */
3291
+ SANDBOX_TIMEOUT_MS: 1e4,
3292
+ /** Default host info reported to guest apps. */
3293
+ HOST_INFO: { name: "mcp-ts-host", version: "1.0.0" },
3294
+ /** Supported MCP App URI schemes. */
3295
+ URI_SCHEMES: ["ui://", "mcp-app://"],
3296
+ /** Default theme for the host context. */
3297
+ THEME: "dark",
3298
+ /** Default platform for the host context. */
3299
+ PLATFORM: "web",
3300
+ /** Default max height for the iframe container (px). */
3301
+ MAX_HEIGHT: 6e3
3302
+ };
3303
+
3304
+ // src/client/utils/app-host-utils.ts
3305
+ var DEFAULT_SANDBOX_TIMEOUT_MS = APP_HOST_DEFAULTS.SANDBOX_TIMEOUT_MS;
3306
+ async function setupSandboxProxyIframe(iframe, sandboxProxyUrl) {
3307
+ iframe.style.width = "100%";
3308
+ iframe.style.height = "100%";
3309
+ iframe.style.border = "none";
3310
+ iframe.style.backgroundColor = "transparent";
3311
+ iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads");
3312
+ const onReady = new Promise((resolve, reject) => {
3313
+ let settled = false;
3314
+ const cleanup = () => {
3315
+ window.removeEventListener("message", messageListener);
3316
+ iframe.removeEventListener("error", errorListener);
3317
+ };
3318
+ const timeoutId = setTimeout(() => {
3319
+ if (!settled) {
3320
+ settled = true;
3321
+ cleanup();
3322
+ reject(new Error("Timed out waiting for sandbox proxy iframe to be ready"));
3323
+ }
3324
+ }, DEFAULT_SANDBOX_TIMEOUT_MS);
3325
+ const messageListener = (event) => {
3326
+ if (event.source === iframe.contentWindow) {
3327
+ if (event.data?.method === SANDBOX_PROXY_READY_METHOD) {
3328
+ if (!settled) {
3329
+ settled = true;
3330
+ clearTimeout(timeoutId);
3331
+ cleanup();
3332
+ resolve();
3333
+ }
3334
+ }
3335
+ }
3336
+ };
3337
+ const errorListener = () => {
3338
+ if (!settled) {
3339
+ settled = true;
3340
+ clearTimeout(timeoutId);
3341
+ cleanup();
3342
+ reject(new Error("Failed to load sandbox proxy iframe"));
3343
+ }
3344
+ };
3345
+ window.addEventListener("message", messageListener);
3346
+ iframe.addEventListener("error", errorListener);
3347
+ });
3348
+ iframe.src = sandboxProxyUrl.href;
3349
+ return { onReady };
3350
+ }
3351
+
3352
+ // src/client/core/app-host.ts
3353
+ var DEFAULT_MCP_APP_CSP = {
3354
+ "default-src": "'self'",
3355
+ "script-src": "'self' 'unsafe-inline' 'unsafe-eval' https: blob:",
3356
+ "style-src": "'self' 'unsafe-inline' https:",
3357
+ "connect-src": "'self' https: wss:",
3358
+ "img-src": "'self' data: https: blob:",
3359
+ "font-src": "'self' data: https:",
3360
+ "media-src": "'self' https: blob:",
3361
+ "frame-src": "'none'",
3362
+ "object-src": "'none'",
3363
+ "base-uri": "'self'"
3364
+ };
3365
+ var HOST_INFO = APP_HOST_DEFAULTS.HOST_INFO;
3366
+ var MCP_URI_SCHEMES = APP_HOST_DEFAULTS.URI_SCHEMES;
3288
3367
  var AppHost = class {
3289
3368
  constructor(client, iframe, options) {
3290
3369
  this.client = client;
@@ -3293,10 +3372,12 @@ var AppHost = class {
3293
3372
  __publicField(this, "sessionId");
3294
3373
  __publicField(this, "resourceCache", /* @__PURE__ */ new Map());
3295
3374
  __publicField(this, "debug");
3296
- /** Callback for app messages (e.g., chat messages from the app) */
3375
+ __publicField(this, "sandboxConfig");
3376
+ __publicField(this, "options");
3297
3377
  __publicField(this, "onAppMessage");
3298
- this.debug = options?.debug ?? false;
3299
- this.configureSandbox();
3378
+ this.options = options || {};
3379
+ this.debug = this.options.debug ?? false;
3380
+ this.sandboxConfig = this.options.sandbox;
3300
3381
  this.bridge = this.initializeBridge();
3301
3382
  }
3302
3383
  // ============================================
@@ -3323,19 +3404,35 @@ var AppHost = class {
3323
3404
  }
3324
3405
  }
3325
3406
  /**
3326
- * Launch an MCP App from a URL or MCP resource URI.
3407
+ * Launch an MCP App from a URL, MCP resource URI, or RAW HTML.
3327
3408
  * Loads the HTML first, then establishes bridge connection.
3328
3409
  */
3329
- async launch(url, sessionId) {
3410
+ async launch(source, sessionId) {
3330
3411
  if (sessionId) this.sessionId = sessionId;
3331
3412
  const initializedPromise = this.onAppReady();
3332
- if (this.isMcpUri(url)) {
3333
- await this.launchMcpApp(url);
3334
- } else {
3335
- this.iframe.src = url;
3413
+ let htmlToRender = source.html;
3414
+ if (!htmlToRender && source.uri) {
3415
+ if (this.isMcpUri(source.uri)) {
3416
+ htmlToRender = await this.readMcpAppHtml(source.uri);
3417
+ }
3418
+ }
3419
+ if (!htmlToRender && source.uri && !this.isMcpUri(source.uri)) {
3420
+ this.iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads");
3421
+ this.iframe.src = source.uri;
3422
+ await this.onIframeReady();
3423
+ await this.connectBridge();
3424
+ } else if (htmlToRender) {
3425
+ if (!this.sandboxConfig) {
3426
+ throw new Error("Sandbox configuration requires a proxy URL to render HTML safely.");
3427
+ }
3428
+ await this.launchSandboxedHtml(htmlToRender, this.sandboxConfig);
3429
+ await this.connectBridge();
3430
+ this.log("Sending HTML resource to sandbox proxy (MCP Apps notification)");
3431
+ await this.bridge.sendSandboxResourceReady({
3432
+ html: htmlToRender,
3433
+ csp: this.sandboxConfig.csp
3434
+ });
3336
3435
  }
3337
- await this.onIframeReady();
3338
- await this.connectBridge();
3339
3436
  this.log("Waiting for app initialization");
3340
3437
  await Promise.race([
3341
3438
  initializedPromise,
@@ -3346,6 +3443,19 @@ var AppHost = class {
3346
3443
  ]);
3347
3444
  this.log("App launched and ready");
3348
3445
  }
3446
+ // Set host context manually
3447
+ setHostContext(context) {
3448
+ this.options.hostContext = context;
3449
+ if (this.bridge) {
3450
+ this.bridge.setHostContext(context);
3451
+ }
3452
+ }
3453
+ // Send streaming inputs manually
3454
+ sendToolInputPartial(params) {
3455
+ if (this.bridge) {
3456
+ this.bridge.sendToolInputPartial(params);
3457
+ }
3458
+ }
3349
3459
  /**
3350
3460
  * Wait for app to signal initialization complete
3351
3461
  */
@@ -3396,14 +3506,17 @@ var AppHost = class {
3396
3506
  this.log("Sending tool cancellation to app");
3397
3507
  this.bridge.sendToolCancelled({ reason });
3398
3508
  }
3509
+ /**
3510
+ * Tell the guest UI the resource is being torn down (unload / cleanup).
3511
+ * Forwards to {@link AppBridge.teardownResource} on `@modelcontextprotocol/ext-apps/app-bridge`.
3512
+ */
3513
+ teardownResource(params = {}) {
3514
+ this.log("Sending resource teardown to app");
3515
+ this.bridge.teardownResource(params);
3516
+ }
3399
3517
  // ============================================
3400
3518
  // Private: Initialization
3401
3519
  // ============================================
3402
- configureSandbox() {
3403
- if (this.iframe.sandbox.value !== SANDBOX_PERMISSIONS) {
3404
- this.iframe.sandbox.value = SANDBOX_PERMISSIONS;
3405
- }
3406
- }
3407
3520
  initializeBridge() {
3408
3521
  const bridge = new AppBridge(
3409
3522
  null,
@@ -3412,12 +3525,10 @@ var AppHost = class {
3412
3525
  openLinks: {},
3413
3526
  serverTools: {},
3414
3527
  logging: {},
3415
- // Declare support for model context updates
3416
3528
  updateModelContext: { text: {} }
3417
3529
  },
3418
3530
  {
3419
- // Initial host context
3420
- hostContext: {
3531
+ hostContext: this.options.hostContext || {
3421
3532
  theme: "dark",
3422
3533
  platform: "web",
3423
3534
  containerDimensions: { maxHeight: 6e3 },
@@ -3426,19 +3537,56 @@ var AppHost = class {
3426
3537
  }
3427
3538
  }
3428
3539
  );
3540
+ bridge.fallbackRequestHandler = this.options.onFallbackRequest;
3429
3541
  bridge.oncalltool = (params) => this.handleToolCall(params);
3430
- bridge.onopenlink = this.handleOpenLink.bind(this);
3431
- bridge.onmessage = this.handleMessage.bind(this);
3432
- bridge.onloggingmessage = (params) => this.log(`App log [${params.level}]: ${params.data}`);
3542
+ if (this.options.onReadResource) {
3543
+ bridge.onreadresource = async (params) => {
3544
+ const resp = await this.options.onReadResource(params.uri);
3545
+ return {
3546
+ contents: resp.contents.map((c) => ({
3547
+ uri: params.uri,
3548
+ text: c.text,
3549
+ blob: c.blob
3550
+ }))
3551
+ };
3552
+ };
3553
+ }
3554
+ bridge.onopenlink = async (params, extra) => {
3555
+ if (this.options.onOpenLink) {
3556
+ return await this.options.onOpenLink(params, extra);
3557
+ }
3558
+ return this.handleOpenLink(params);
3559
+ };
3560
+ bridge.onmessage = async (params, extra) => {
3561
+ if (this.options.onMessage) {
3562
+ return await this.options.onMessage(params, extra);
3563
+ }
3564
+ return this.handleMessage(params);
3565
+ };
3566
+ bridge.onloggingmessage = (params) => {
3567
+ this.log(`App log [${params.level}]: ${params.data}`);
3568
+ if (this.options.onLoggingMessage) {
3569
+ this.options.onLoggingMessage(params);
3570
+ }
3571
+ };
3433
3572
  bridge.onupdatemodelcontext = async () => ({});
3434
- bridge.onsizechange = async ({ width, height }) => {
3435
- if (height !== void 0) this.iframe.style.height = `${height}px`;
3436
- if (width !== void 0) this.iframe.style.minWidth = `min(${width}px, 100%)`;
3573
+ bridge.onsizechange = async (params) => {
3574
+ const { width, height } = params;
3575
+ if (height !== void 0 && height > 0) {
3576
+ this.iframe.style.height = `${height}px`;
3577
+ }
3578
+ if (width !== void 0 && width > 0) this.iframe.style.minWidth = `min(${width}px, 100%)`;
3579
+ if (this.options.onSizeChanged) {
3580
+ this.options.onSizeChanged(params);
3581
+ }
3437
3582
  return {};
3438
3583
  };
3439
- bridge.onrequestdisplaymode = async (params) => ({
3440
- mode: params.mode === "fullscreen" ? "fullscreen" : "inline"
3441
- });
3584
+ bridge.onrequestdisplaymode = async (params, extra) => {
3585
+ if (this.options.onRequestDisplayMode) {
3586
+ return await this.options.onRequestDisplayMode(params, extra);
3587
+ }
3588
+ return { mode: params.mode === "fullscreen" ? "fullscreen" : "inline" };
3589
+ };
3442
3590
  return bridge;
3443
3591
  }
3444
3592
  async connectBridge() {
@@ -3452,6 +3600,9 @@ var AppHost = class {
3452
3600
  this.log("Bridge connected successfully");
3453
3601
  } catch (error) {
3454
3602
  this.log("Bridge connection failed", "error");
3603
+ if (this.options.onError) {
3604
+ this.options.onError(error instanceof Error ? error : new Error(String(error)));
3605
+ }
3455
3606
  throw error;
3456
3607
  }
3457
3608
  }
@@ -3459,8 +3610,11 @@ var AppHost = class {
3459
3610
  // Private: Bridge Event Handlers
3460
3611
  // ============================================
3461
3612
  async handleToolCall(params) {
3462
- if (!this.client.isConnected()) {
3463
- throw new Error("Client disconnected");
3613
+ if (this.options.onCallTool) {
3614
+ return await this.options.onCallTool(params);
3615
+ }
3616
+ if (!this.client || !this.client.isConnected()) {
3617
+ throw new Error("Client disconnected or not provided");
3464
3618
  }
3465
3619
  const sessionId = await this.getSessionId();
3466
3620
  if (!sessionId) {
@@ -3484,13 +3638,19 @@ var AppHost = class {
3484
3638
  // ============================================
3485
3639
  // Private: Resource Loading
3486
3640
  // ============================================
3487
- async launchMcpApp(uri) {
3488
- if (!this.client.isConnected()) {
3489
- throw new Error("Client must be connected");
3641
+ async launchSandboxedHtml(html, config) {
3642
+ const sandboxUrlString = config.url instanceof URL ? config.url.href : config.url;
3643
+ const url = new URL(sandboxUrlString, globalThis.location?.href);
3644
+ if (config.csp && Object.keys(config.csp).length > 0) {
3645
+ url.searchParams.set("csp", JSON.stringify(config.csp));
3490
3646
  }
3647
+ const { onReady } = await setupSandboxProxyIframe(this.iframe, url);
3648
+ await onReady;
3649
+ }
3650
+ async readMcpAppHtml(uri) {
3491
3651
  const sessionId = await this.getSessionId();
3492
- if (!sessionId) {
3493
- throw new Error("No active session");
3652
+ if (!sessionId && !this.options.onReadResource) {
3653
+ throw new Error("No active session.");
3494
3654
  }
3495
3655
  const response = await this.fetchResourceWithCache(sessionId, uri);
3496
3656
  if (!response?.contents?.length) {
@@ -3501,10 +3661,18 @@ var AppHost = class {
3501
3661
  if (!html) {
3502
3662
  throw new Error(`Invalid content in resource: ${uri}`);
3503
3663
  }
3504
- const blob = new Blob([html], { type: "text/html" });
3505
- this.iframe.src = URL.createObjectURL(blob);
3664
+ return html;
3506
3665
  }
3507
3666
  async fetchResourceWithCache(sessionId, uri) {
3667
+ if (this.options.onReadResource) {
3668
+ return await this.options.onReadResource(uri);
3669
+ }
3670
+ if (!sessionId) {
3671
+ throw new Error("No active session");
3672
+ }
3673
+ if (!this.client) {
3674
+ throw new Error("No client to read resource from");
3675
+ }
3508
3676
  if (this.hasClientCache()) {
3509
3677
  return this.client.getOrFetchResource(sessionId, uri);
3510
3678
  }
@@ -3517,8 +3685,11 @@ var AppHost = class {
3517
3685
  }
3518
3686
  async preloadResource(uri) {
3519
3687
  try {
3688
+ if (this.options.onReadResource) {
3689
+ return await this.options.onReadResource(uri);
3690
+ }
3520
3691
  const sessionId = await this.getSessionId();
3521
- if (!sessionId) return null;
3692
+ if (!sessionId || !this.client) return null;
3522
3693
  return await this.client.readResource(sessionId, uri);
3523
3694
  } catch (error) {
3524
3695
  this.log(`Preload failed for ${uri}`, "warn");
@@ -3530,6 +3701,7 @@ var AppHost = class {
3530
3701
  // ============================================
3531
3702
  async getSessionId() {
3532
3703
  if (this.sessionId) return this.sessionId;
3704
+ if (!this.client) return void 0;
3533
3705
  const result = await this.client.getSessions();
3534
3706
  return result.sessions?.[0]?.sessionId;
3535
3707
  }
@@ -3537,6 +3709,7 @@ var AppHost = class {
3537
3709
  return MCP_URI_SCHEMES.some((scheme) => url.startsWith(scheme));
3538
3710
  }
3539
3711
  hasClientCache() {
3712
+ if (!this.client) return false;
3540
3713
  return "getOrFetchResource" in this.client && typeof this.client.getOrFetchResource === "function";
3541
3714
  }
3542
3715
  extractUiResourceUri(tool) {
@@ -3600,6 +3773,870 @@ function findToolByName(connections, toolName) {
3600
3773
  return void 0;
3601
3774
  }
3602
3775
 
3603
- export { AppHost, AuthenticationError, ConfigurationError, ConnectionError, DEFAULT_CLIENT_NAME, DEFAULT_CLIENT_URI, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_LOGO_URI, DEFAULT_POLICY_URI, DisposableStore, Emitter, InvalidStateError, MCPClient, MCP_CLIENT_NAME, MCP_CLIENT_VERSION, McpError, MultiSessionClient, NotConnectedError, REDIS_KEY_PREFIX, RpcErrorCodes, SESSION_TTL_SECONDS, SOFTWARE_ID, SOFTWARE_VERSION, SSEClient, SSEConnectionManager, STATE_EXPIRATION_MS, SessionNotFoundError, SessionValidationError, StorageOAuthClientProvider, TOKEN_EXPIRY_BUFFER_MS, ToolExecutionError, UnauthorizedError, createNextMcpHandler, createSSEHandler, findToolByName, getToolUiResourceUri, isCallToolSuccess, isConnectAuthRequired, isConnectError, isConnectSuccess, isListToolsSuccess, sanitizeServerLabel, storage };
3776
+ // src/shared/tool-index.ts
3777
+ var CALIBRATION_DIVISOR = 3.6;
3778
+ function classifyChar(ch) {
3779
+ const code = ch.charCodeAt(0);
3780
+ if (code <= 32 || ch === "{" || ch === "}" || ch === "[" || ch === "]" || ch === ":" || ch === ",") return 1;
3781
+ if (code >= 33 && code <= 47) return 1.5;
3782
+ if (code >= 48 && code <= 57) return 2;
3783
+ if (code >= 65 && code <= 90) return 3.5;
3784
+ if (code >= 97 && code <= 122) return 4;
3785
+ return 2.5;
3786
+ }
3787
+ var ToolIndex = class _ToolIndex {
3788
+ constructor(options = {}) {
3789
+ /** All indexed tools keyed by name (supports duplicates). */
3790
+ __publicField(this, "tools", /* @__PURE__ */ new Map());
3791
+ /** Precomputed lightweight summaries keyed by document. */
3792
+ __publicField(this, "toolSummaries", /* @__PURE__ */ new Map());
3793
+ /** Pre-computed search text for keyword matching (lowercase), keyed by document. */
3794
+ __publicField(this, "searchTexts", /* @__PURE__ */ new Map());
3795
+ /** Pre-computed IDF values per token (computed once on build). */
3796
+ __publicField(this, "idf", /* @__PURE__ */ new Map());
3797
+ /** Per-tool TF vectors (Map<token, tf>). */
3798
+ __publicField(this, "tfVectors", /* @__PURE__ */ new Map());
3799
+ /** Optional: pre-computed embedding vectors per tool. */
3800
+ __publicField(this, "embeddings", /* @__PURE__ */ new Map());
3801
+ /** BM25: document lengths in tokens for each tool. */
3802
+ __publicField(this, "docLengths", /* @__PURE__ */ new Map());
3803
+ /** BM25: average document length across the entire index. */
3804
+ __publicField(this, "avgDocLength", 0);
3805
+ /** Cached total estimated token cost across all indexed tools. */
3806
+ __publicField(this, "totalTokenCost", 0);
3807
+ __publicField(this, "options");
3808
+ this.options = {
3809
+ embedFn: options.embedFn ?? void 0,
3810
+ keywordWeight: options.keywordWeight ?? 0.4
3811
+ };
3812
+ }
3813
+ // -----------------------------------------------------------------------
3814
+ // Indexing
3815
+ // -----------------------------------------------------------------------
3816
+ /**
3817
+ * Build (or rebuild) the index from the given tool set.
3818
+ * Call this after connecting / reconnecting to MCP servers.
3819
+ */
3820
+ async buildIndex(tools) {
3821
+ this.tools.clear();
3822
+ this.toolSummaries.clear();
3823
+ this.searchTexts.clear();
3824
+ this.idf.clear();
3825
+ this.tfVectors.clear();
3826
+ this.embeddings.clear();
3827
+ this.docLengths.clear();
3828
+ this.avgDocLength = 0;
3829
+ this.totalTokenCost = 0;
3830
+ const allTokenSets = /* @__PURE__ */ new Map();
3831
+ let totalLength = 0;
3832
+ for (const tool of tools) {
3833
+ const docKey = this.getDocumentKey(tool);
3834
+ if (!this.tools.has(tool.name)) {
3835
+ this.tools.set(tool.name, []);
3836
+ }
3837
+ this.tools.get(tool.name).push(tool);
3838
+ const estimatedTokens = _ToolIndex.estimateTokens(tool);
3839
+ this.toolSummaries.set(docKey, {
3840
+ name: tool.name,
3841
+ description: tool.description ?? "",
3842
+ serverName: tool.serverName,
3843
+ sessionId: tool.sessionId,
3844
+ estimatedTokens
3845
+ });
3846
+ this.totalTokenCost += estimatedTokens;
3847
+ const text = this.buildSearchableText(tool).toLowerCase();
3848
+ this.searchTexts.set(docKey, text);
3849
+ const tokens = this.tokenize(text);
3850
+ const tf = /* @__PURE__ */ new Map();
3851
+ const uniqueTokens = /* @__PURE__ */ new Set();
3852
+ for (const tok of tokens) {
3853
+ tf.set(tok, (tf.get(tok) ?? 0) + 1);
3854
+ uniqueTokens.add(tok);
3855
+ }
3856
+ const maxTf = Math.max(...tf.values(), 1);
3857
+ for (const [k, v] of tf) {
3858
+ tf.set(k, v / maxTf);
3859
+ }
3860
+ this.tfVectors.set(docKey, tf);
3861
+ allTokenSets.set(docKey, uniqueTokens);
3862
+ const length = tokens.length;
3863
+ this.docLengths.set(docKey, length);
3864
+ totalLength += length;
3865
+ }
3866
+ this.avgDocLength = totalLength / (tools.length || 1);
3867
+ const totalDocs = tools.length || 1;
3868
+ const dfCounts = /* @__PURE__ */ new Map();
3869
+ for (const tokenSet of allTokenSets.values()) {
3870
+ for (const tok of tokenSet) {
3871
+ dfCounts.set(tok, (dfCounts.get(tok) ?? 0) + 1);
3872
+ }
3873
+ }
3874
+ for (const [tok, df] of dfCounts) {
3875
+ this.idf.set(tok, Math.log(totalDocs / df) + 1);
3876
+ }
3877
+ if (this.options.embedFn) {
3878
+ const names = [...this.searchTexts.keys()];
3879
+ const texts = names.map((n) => this.searchTexts.get(n));
3880
+ try {
3881
+ const vectors = await this.options.embedFn(texts);
3882
+ for (let i = 0; i < names.length; i++) {
3883
+ if (vectors[i]) {
3884
+ this.embeddings.set(names[i], vectors[i]);
3885
+ }
3886
+ }
3887
+ } catch (err) {
3888
+ console.warn("[ToolIndex] Embedding generation failed, falling back to keyword-only search:", err);
3889
+ }
3890
+ }
3891
+ }
3892
+ // -----------------------------------------------------------------------
3893
+ // Search
3894
+ // -----------------------------------------------------------------------
3895
+ /**
3896
+ * Search the index and return the top-K most relevant tools.
3897
+ *
3898
+ * When an `embedFn` is configured the final score is a weighted blend of
3899
+ * keyword TF-IDF similarity and embedding cosine-similarity:
3900
+ *
3901
+ * `score = keywordWeight × keyword_score + (1 - keywordWeight) × cosine_score`
3902
+ */
3903
+ async search(query, topK = 5) {
3904
+ if (this.tools.size === 0) return [];
3905
+ const queryLower = query.toLowerCase();
3906
+ const queryTokens = this.tokenize(queryLower);
3907
+ const keywordScores = /* @__PURE__ */ new Map();
3908
+ const k1 = 1.2;
3909
+ const b = 0.75;
3910
+ for (const [docKey, docTf] of this.tfVectors) {
3911
+ let score = 0;
3912
+ const docLen = this.docLengths.get(docKey) ?? 0;
3913
+ for (const tok of queryTokens) {
3914
+ const tfVal = docTf.get(tok) ?? 0;
3915
+ if (tfVal === 0) continue;
3916
+ const idf = this.idf.get(tok) ?? 0;
3917
+ const numerator = tfVal * (k1 + 1);
3918
+ const denominator = tfVal + k1 * (1 - b + b * (docLen / this.avgDocLength));
3919
+ score += idf * (numerator / denominator);
3920
+ }
3921
+ keywordScores.set(docKey, score);
3922
+ }
3923
+ let embeddingScores = null;
3924
+ if (this.options.embedFn && this.embeddings.size > 0) {
3925
+ try {
3926
+ const [queryEmbedding] = await this.options.embedFn([queryLower]);
3927
+ if (queryEmbedding) {
3928
+ embeddingScores = /* @__PURE__ */ new Map();
3929
+ for (const [docKey, vec] of this.embeddings) {
3930
+ embeddingScores.set(docKey, this.cosineSimilarity(queryEmbedding, vec));
3931
+ }
3932
+ }
3933
+ } catch {
3934
+ }
3935
+ }
3936
+ const kw = this.options.keywordWeight;
3937
+ const finalScores = [];
3938
+ for (const docKey of this.toolSummaries.keys()) {
3939
+ const kwScore = keywordScores.get(docKey) ?? 0;
3940
+ const embScore = embeddingScores?.get(docKey) ?? 0;
3941
+ const score = embeddingScores ? kw * kwScore + (1 - kw) * embScore : kwScore;
3942
+ if (score > 0) {
3943
+ finalScores.push({ docKey, score });
3944
+ }
3945
+ }
3946
+ finalScores.sort((a, b2) => b2.score - a.score);
3947
+ return finalScores.slice(0, topK).map(({ docKey }) => {
3948
+ return this.toolSummaries.get(docKey);
3949
+ });
3950
+ }
3951
+ /**
3952
+ * Search tools using a regex pattern.
3953
+ * Matches against name, description, and parameter metadata.
3954
+ */
3955
+ searchRegex(pattern, topK = 5) {
3956
+ if (this.tools.size === 0) return [];
3957
+ try {
3958
+ let flags = "";
3959
+ let cleanPattern = pattern;
3960
+ if (pattern.includes("(?i)")) {
3961
+ flags = "i";
3962
+ cleanPattern = pattern.replace(/\(\?i\)/g, "");
3963
+ }
3964
+ const regex = new RegExp(cleanPattern, flags || void 0);
3965
+ const matches = [];
3966
+ for (const [docKey, text] of this.searchTexts) {
3967
+ const tool = this.toolSummaries.get(docKey);
3968
+ if (!tool) continue;
3969
+ if (regex.test(text) || regex.test(tool.name)) {
3970
+ let score = 1;
3971
+ if (tool.name === cleanPattern) score = 10;
3972
+ else if (tool.name.startsWith(cleanPattern)) score = 5;
3973
+ else if (tool.name.toLowerCase().includes(cleanPattern.toLowerCase())) score = 2;
3974
+ matches.push({ docKey, score });
3975
+ }
3976
+ }
3977
+ matches.sort((a, b) => b.score - a.score);
3978
+ return matches.slice(0, topK).map(({ docKey }) => {
3979
+ return this.toolSummaries.get(docKey);
3980
+ });
3981
+ } catch (err) {
3982
+ console.warn("[ToolIndex] Regex search failed:", err);
3983
+ return [];
3984
+ }
3985
+ }
3986
+ // -----------------------------------------------------------------------
3987
+ // Accessors
3988
+ // -----------------------------------------------------------------------
3989
+ /**
3990
+ * Get tool definition(s) by name.
3991
+ * If namespace is provided, it tries to match sessionId or serverName.
3992
+ */
3993
+ getTool(name, namespace) {
3994
+ const list = this.tools.get(name) ?? [];
3995
+ if (!namespace) return list;
3996
+ return list.filter((t) => t.sessionId === namespace || t.serverName === namespace);
3997
+ }
3998
+ /** All indexed tool names. */
3999
+ getToolNames() {
4000
+ return [...this.tools.keys()];
4001
+ }
4002
+ /** Number of indexed tools (including duplicates). */
4003
+ get size() {
4004
+ let count = 0;
4005
+ for (const list of this.tools.values()) {
4006
+ count += list.length;
4007
+ }
4008
+ return count;
4009
+ }
4010
+ /** Total estimated token cost of all indexed tool schemas. */
4011
+ getTotalTokenCost() {
4012
+ return this.totalTokenCost;
4013
+ }
4014
+ // -----------------------------------------------------------------------
4015
+ // Static Helpers
4016
+ // -----------------------------------------------------------------------
4017
+ /**
4018
+ * Estimate token count of a tool's full schema (name + description + inputSchema).
4019
+ *
4020
+ * Uses character-class weighted counting calibrated against cl100k_base.
4021
+ * Accuracy is typically within ±10% for JSON Schema payloads.
4022
+ */
4023
+ static estimateTokens(tool) {
4024
+ const parts = [tool.name];
4025
+ if (tool.description) parts.push(tool.description);
4026
+ if (tool.inputSchema) parts.push(JSON.stringify(tool.inputSchema));
4027
+ const text = parts.join(" ");
4028
+ let weightedLen = 0;
4029
+ for (let i = 0; i < text.length; i++) {
4030
+ weightedLen += 1 / classifyChar(text[i]);
4031
+ }
4032
+ return Math.ceil(weightedLen / (1 / CALIBRATION_DIVISOR));
4033
+ }
4034
+ // -----------------------------------------------------------------------
4035
+ // Internals
4036
+ // -----------------------------------------------------------------------
4037
+ /** Build a single searchable string from tool metadata. */
4038
+ buildSearchableText(tool) {
4039
+ const parts = [tool.name];
4040
+ if (tool.description) parts.push(tool.description);
4041
+ if (tool.inputSchema && typeof tool.inputSchema === "object") {
4042
+ const schema = tool.inputSchema;
4043
+ const props = schema.properties;
4044
+ if (props) {
4045
+ for (const [key, val] of Object.entries(props)) {
4046
+ parts.push(key);
4047
+ if (val && typeof val === "object" && val.description) {
4048
+ parts.push(val.description);
4049
+ }
4050
+ }
4051
+ }
4052
+ }
4053
+ return parts.join(" ");
4054
+ }
4055
+ getDocumentKey(tool) {
4056
+ return `${tool.sessionId}::${tool.serverName}::${tool.name}`;
4057
+ }
4058
+ /** Simple whitespace + camelCase + snake_case tokenizer. */
4059
+ tokenize(text) {
4060
+ return text.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((t) => t.length > 1);
4061
+ }
4062
+ /** Cosine similarity between two vectors. */
4063
+ cosineSimilarity(a, b) {
4064
+ const len = Math.min(a.length, b.length);
4065
+ let dot = 0;
4066
+ let magA = 0;
4067
+ let magB = 0;
4068
+ for (let i = 0; i < len; i++) {
4069
+ dot += a[i] * b[i];
4070
+ magA += a[i] * a[i];
4071
+ magB += b[i] * b[i];
4072
+ }
4073
+ const denom = Math.sqrt(magA) * Math.sqrt(magB);
4074
+ return denom > 0 ? dot / denom : 0;
4075
+ }
4076
+ };
4077
+
4078
+ // src/shared/schema-compressor.ts
4079
+ var SchemaCompressor = class _SchemaCompressor {
4080
+ /**
4081
+ * Convert a full MCP Tool definition to a compact summary.
4082
+ *
4083
+ * The compact form omits `inputSchema` entirely and optionally generates
4084
+ * a short `parameterHint` from the schema's top-level properties.
4085
+ */
4086
+ static toCompact(tool) {
4087
+ const compact = {
4088
+ name: tool.name,
4089
+ description: tool.description
4090
+ };
4091
+ if (tool.inputSchema && typeof tool.inputSchema === "object") {
4092
+ const schema = tool.inputSchema;
4093
+ if (schema.properties) {
4094
+ const required = new Set(schema.required ?? []);
4095
+ const parts = [];
4096
+ for (const [key, val] of Object.entries(schema.properties)) {
4097
+ const type = val?.type ?? "any";
4098
+ const enumSuffix = val?.enum && Array.isArray(val.enum) ? `: ${val.enum.map((e) => `'${e}'`).join(" | ")}` : `: ${type}`;
4099
+ parts.push(required.has(key) ? `${key}${enumSuffix}` : `${key}?${enumSuffix}`);
4100
+ }
4101
+ if (parts.length > 0) {
4102
+ compact.parameterHint = `(${parts.join(", ")})`;
4103
+ }
4104
+ }
4105
+ }
4106
+ return compact;
4107
+ }
4108
+ /**
4109
+ * Convert an array of tools to compact form, optionally limiting the count.
4110
+ */
4111
+ static compactAll(tools, options) {
4112
+ const limited = options?.maxTools ? tools.slice(0, options.maxTools) : tools;
4113
+ return limited.map((t) => _SchemaCompressor.toCompact(t));
4114
+ }
4115
+ /**
4116
+ * Estimate token savings from using compact vs full tool schemas.
4117
+ */
4118
+ static estimateSavings(tools) {
4119
+ let fullTokens = 0;
4120
+ let compactTokens = 0;
4121
+ for (const tool of tools) {
4122
+ fullTokens += ToolIndex.estimateTokens(tool);
4123
+ const compact = _SchemaCompressor.toCompact(tool);
4124
+ const text = [compact.name, compact.description ?? "", compact.parameterHint ?? ""].join(" ");
4125
+ compactTokens += Math.ceil(text.length / 4);
4126
+ }
4127
+ const saved = fullTokens - compactTokens;
4128
+ const pct = fullTokens > 0 ? (saved / fullTokens * 100).toFixed(1) : "0.0";
4129
+ return {
4130
+ fullTokens,
4131
+ compactTokens,
4132
+ savedTokens: saved,
4133
+ savingsPercent: `${pct}%`
4134
+ };
4135
+ }
4136
+ };
4137
+
4138
+ // src/shared/meta-tools.ts
4139
+ function createSearchToolDefinition() {
4140
+ return {
4141
+ name: "mcp_search_tool_bm25",
4142
+ description: 'Search the catalog of available tools using BM25 natural language ranking. Returns tool names, descriptions, and server info. Use this FIRST to find relevant tools before calling them. Example queries: "database query", "send email", "github pull request".',
4143
+ inputSchema: {
4144
+ type: "object",
4145
+ properties: {
4146
+ query: {
4147
+ type: "string",
4148
+ description: "Natural language description of the capability you need."
4149
+ },
4150
+ limit: {
4151
+ type: "number",
4152
+ description: "Maximum number of results to return (default: 5, max: 20)."
4153
+ }
4154
+ },
4155
+ required: ["query"]
4156
+ }
4157
+ };
4158
+ }
4159
+ function createRegexSearchToolDefinition() {
4160
+ return {
4161
+ name: "mcp_search_tool_regex",
4162
+ description: 'Search the catalog of available tools using a Python-style regex pattern. Matches against tool names, descriptions, and parameter descriptions. Example patterns: "^github_", "weather", "(?i)slack".',
4163
+ inputSchema: {
4164
+ type: "object",
4165
+ properties: {
4166
+ query: {
4167
+ type: "string",
4168
+ description: 'Regex pattern to search for (e.g., "^get_.*_data", "database").'
4169
+ },
4170
+ limit: {
4171
+ type: "number",
4172
+ description: "Maximum number of results to return (default: 5, max: 20)."
4173
+ }
4174
+ },
4175
+ required: ["query"]
4176
+ }
4177
+ };
4178
+ }
4179
+ function createGetSchemaToolDefinition() {
4180
+ return {
4181
+ name: "mcp_get_tool_schema",
4182
+ description: "Get the full input schema (parameters) for a specific tool. Call this after mcp_search_tool_bm25 to get the parameter details needed to call a tool correctly.",
4183
+ inputSchema: {
4184
+ type: "object",
4185
+ properties: {
4186
+ toolName: {
4187
+ type: "string",
4188
+ description: "The exact tool name returned by mcp_search_tool_bm25."
4189
+ },
4190
+ serverName: {
4191
+ type: "string",
4192
+ description: "Optional: The server name provided in mcp_search_tool_bm25. Required if multiple tools have the same name."
4193
+ }
4194
+ },
4195
+ required: ["toolName"]
4196
+ }
4197
+ };
4198
+ }
4199
+ function createExecuteToolDefinition() {
4200
+ return {
4201
+ name: "mcp_execute_tool",
4202
+ description: "Execute a tool that was discovered via mcp_search_tool_bm25. You MUST call mcp_get_tool_schema first to know the correct parameters. Pass the exact tool name and its arguments.",
4203
+ inputSchema: {
4204
+ type: "object",
4205
+ properties: {
4206
+ toolName: {
4207
+ type: "string",
4208
+ description: "The exact tool name from mcp_search_tool_bm25 results."
4209
+ },
4210
+ serverName: {
4211
+ type: "string",
4212
+ description: "Optional: The server name provided in mcp_search_tool_bm25. Required if multiple tools have the same name."
4213
+ },
4214
+ args: {
4215
+ type: "object",
4216
+ description: "Arguments matching the tool's inputSchema. Omit or pass {} if the tool takes no parameters.",
4217
+ additionalProperties: true
4218
+ }
4219
+ },
4220
+ required: ["toolName"]
4221
+ }
4222
+ };
4223
+ }
4224
+ async function executeMetaTool(toolName, args, router, callToolFn) {
4225
+ const resolveToolSchema = (name, namespace) => {
4226
+ try {
4227
+ return { tool: router.getToolSchema(name, namespace) };
4228
+ } catch (err) {
4229
+ const errorMessage = err instanceof Error ? err.message : String(err);
4230
+ return {
4231
+ error: {
4232
+ content: [{ type: "text", text: errorMessage }],
4233
+ isError: true
4234
+ }
4235
+ };
4236
+ }
4237
+ };
4238
+ switch (toolName) {
4239
+ case "mcp_search_tool_bm25": {
4240
+ const query = String(args.query ?? "");
4241
+ const limit = Math.min(Number(args.limit) || 5, 20);
4242
+ const results = await router.searchTools(query, limit);
4243
+ const text = results.length === 0 ? "No tools found matching your query. Try different keywords." : results.map(
4244
+ (t, i) => `${i + 1}. **${t.name}** (server: ${t.serverName})
4245
+ ${t.description}
4246
+ Estimated tokens: ${t.estimatedTokens}`
4247
+ ).join("\n");
4248
+ return {
4249
+ content: [{ type: "text", text }],
4250
+ isError: false
4251
+ };
4252
+ }
4253
+ case "mcp_search_tool_regex": {
4254
+ const pattern = String(args.query ?? "");
4255
+ const limit = Math.min(Number(args.limit) || 5, 20);
4256
+ const results = await router.searchToolsRegex(pattern, limit);
4257
+ const text = results.length === 0 ? "No tools matched your regex pattern. Try a broader pattern." : results.map(
4258
+ (t, i) => `${i + 1}. **${t.name}** (server: ${t.serverName})
4259
+ ${t.description}
4260
+ Estimated tokens: ${t.estimatedTokens}`
4261
+ ).join("\n");
4262
+ return {
4263
+ content: [{ type: "text", text }],
4264
+ isError: false
4265
+ };
4266
+ }
4267
+ case "mcp_get_tool_schema": {
4268
+ const name = String(args.toolName ?? "");
4269
+ const namespace = String(args.serverName ?? "") || void 0;
4270
+ const { tool, error } = resolveToolSchema(name, namespace);
4271
+ if (error) {
4272
+ return error;
4273
+ }
4274
+ if (!tool) {
4275
+ return {
4276
+ content: [
4277
+ {
4278
+ type: "text",
4279
+ text: `Tool "${name}" not found. Use mcp_search_tool_bm25 to find available tools first.`
4280
+ }
4281
+ ],
4282
+ isError: true
4283
+ };
4284
+ }
4285
+ const schema = {
4286
+ name: tool.name,
4287
+ description: tool.description,
4288
+ inputSchema: tool.inputSchema
4289
+ };
4290
+ return {
4291
+ content: [{ type: "text", text: JSON.stringify(schema, null, 2) }],
4292
+ isError: false
4293
+ };
4294
+ }
4295
+ case "mcp_execute_tool": {
4296
+ const targetToolName = String(args.toolName ?? "");
4297
+ const namespace = String(args.serverName ?? "") || void 0;
4298
+ const toolArgs = args.args ?? {};
4299
+ if (!targetToolName) {
4300
+ return {
4301
+ content: [{ type: "text", text: 'Missing required parameter "toolName". Specify which tool to execute.' }],
4302
+ isError: true
4303
+ };
4304
+ }
4305
+ const { tool, error } = resolveToolSchema(targetToolName, namespace);
4306
+ if (error) {
4307
+ return error;
4308
+ }
4309
+ if (!tool) {
4310
+ return {
4311
+ content: [
4312
+ {
4313
+ type: "text",
4314
+ text: `Tool "${targetToolName}" not found. Use mcp_search_tool_bm25 to discover available tools first.`
4315
+ }
4316
+ ],
4317
+ isError: true
4318
+ };
4319
+ }
4320
+ if (!callToolFn) {
4321
+ return {
4322
+ content: [{ type: "text", text: "Tool execution is not available. No callToolFn was configured." }],
4323
+ isError: true
4324
+ };
4325
+ }
4326
+ try {
4327
+ const result = await callToolFn(targetToolName, toolArgs, namespace);
4328
+ if (result && typeof result === "object" && "content" in result) {
4329
+ return result;
4330
+ }
4331
+ const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
4332
+ return {
4333
+ content: [{ type: "text", text }],
4334
+ isError: false
4335
+ };
4336
+ } catch (err) {
4337
+ const errorMessage = err instanceof Error ? err.message : String(err);
4338
+ return {
4339
+ content: [{ type: "text", text: `Tool execution failed: ${errorMessage}` }],
4340
+ isError: true
4341
+ };
4342
+ }
4343
+ }
4344
+ default:
4345
+ return null;
4346
+ }
4347
+ }
4348
+ function isMetaTool(toolName) {
4349
+ return toolName === "mcp_search_tool_bm25" || toolName === "mcp_search_tool_regex" || toolName === "mcp_get_tool_schema" || toolName === "mcp_execute_tool";
4350
+ }
4351
+ function resolveMetaToolProxy(toolName, args) {
4352
+ if (toolName === "mcp_execute_tool") {
4353
+ const innerName = args?.toolName;
4354
+ const innerArgs = args?.args;
4355
+ return {
4356
+ toolName: typeof innerName === "string" && innerName ? innerName : toolName,
4357
+ args: innerArgs && typeof innerArgs === "object" && !Array.isArray(innerArgs) ? innerArgs : {}
4358
+ };
4359
+ }
4360
+ const match = toolName.match(/(?:tool_[^_]+_)?(.+)$/);
4361
+ const resolvedName = match?.[1] ?? toolName;
4362
+ return { toolName: resolvedName, args: args ?? {} };
4363
+ }
4364
+
4365
+ // src/shared/tool-router.ts
4366
+ var ToolRouter = class {
4367
+ constructor(client, options = {}) {
4368
+ this.client = client;
4369
+ this.options = options;
4370
+ __publicField(this, "index");
4371
+ __publicField(this, "allTools", []);
4372
+ __publicField(this, "groupsMap", /* @__PURE__ */ new Map());
4373
+ __publicField(this, "strategy");
4374
+ __publicField(this, "maxTools");
4375
+ __publicField(this, "compactSchemas");
4376
+ __publicField(this, "activeGroups");
4377
+ __publicField(this, "customGroups");
4378
+ __publicField(this, "initialized", false);
4379
+ this.strategy = options.strategy ?? "all";
4380
+ this.maxTools = options.maxTools ?? 40;
4381
+ this.compactSchemas = options.compactSchemas ?? false;
4382
+ this.activeGroups = new Set(options.activeGroups ?? []);
4383
+ this.customGroups = options.groups;
4384
+ this.index = new ToolIndex({
4385
+ embedFn: options.embedFn,
4386
+ keywordWeight: options.keywordWeight
4387
+ });
4388
+ }
4389
+ // -----------------------------------------------------------------------
4390
+ // Core Public API
4391
+ // -----------------------------------------------------------------------
4392
+ /**
4393
+ * Get tools filtered by the current strategy.
4394
+ * This is the main method adapters should call.
4395
+ *
4396
+ * - `all` → returns all tools (unchanged behavior)
4397
+ * - `search` → returns only meta-tools (mcp_search_tool_bm25, mcp_get_tool_schema, mcp_execute_tool)
4398
+ * - `groups` → returns tools from active groups only
4399
+ */
4400
+ async getFilteredTools() {
4401
+ await this.ensureInitialized();
4402
+ switch (this.strategy) {
4403
+ case "search":
4404
+ return this.getMetaToolDefinitions();
4405
+ case "groups":
4406
+ return this.getGroupFilteredTools();
4407
+ case "all":
4408
+ default:
4409
+ if (this.compactSchemas) {
4410
+ return this.allTools.map((t) => {
4411
+ const compact = SchemaCompressor.toCompact(t);
4412
+ return {
4413
+ name: compact.name,
4414
+ description: (compact.description ?? "") + (compact.parameterHint ? ` Parameters: ${compact.parameterHint}` : ""),
4415
+ inputSchema: { type: "object", properties: {} }
4416
+ };
4417
+ });
4418
+ }
4419
+ return [...this.allTools];
4420
+ }
4421
+ }
4422
+ /**
4423
+ * Search tools by natural-language query.
4424
+ * Works regardless of strategy.
4425
+ */
4426
+ async searchTools(query, topK) {
4427
+ await this.ensureInitialized();
4428
+ return this.index.search(query, topK ?? this.maxTools);
4429
+ }
4430
+ /**
4431
+ * Search tools by regex pattern.
4432
+ * Matches against name, description, and parameter metadata.
4433
+ */
4434
+ async searchToolsRegex(pattern, topK) {
4435
+ await this.ensureInitialized();
4436
+ return this.index.searchRegex(pattern, topK ?? this.maxTools);
4437
+ }
4438
+ /**
4439
+ * Get the full tool definition by name.
4440
+ * If tool name is ambiguous, use namespace to specify the server.
4441
+ */
4442
+ getToolSchema(toolName, namespace) {
4443
+ const matches = this.index.getTool(toolName, namespace);
4444
+ if (matches.length === 0) return void 0;
4445
+ if (matches.length > 1) {
4446
+ const servers = matches.map((m) => m.serverName).join(", ");
4447
+ throw new Error(
4448
+ `Tool "${toolName}" is provided by multiple servers: [${servers}]. Please specify the desired "serverName" as a namespace.`
4449
+ );
4450
+ }
4451
+ return matches[0];
4452
+ }
4453
+ /**
4454
+ * Get compact (schema-less) summaries for all tools.
4455
+ */
4456
+ getCompactTools() {
4457
+ return SchemaCompressor.compactAll(this.allTools);
4458
+ }
4459
+ // -----------------------------------------------------------------------
4460
+ // Group Management
4461
+ // -----------------------------------------------------------------------
4462
+ /** Get all available groups with their tool lists and active status. */
4463
+ getGroups() {
4464
+ return new Map(this.groupsMap);
4465
+ }
4466
+ /** Activate specific groups. Pass empty array to activate all. */
4467
+ setActiveGroups(groups) {
4468
+ this.activeGroups = new Set(groups);
4469
+ for (const [name, info] of this.groupsMap) {
4470
+ info.active = this.activeGroups.size === 0 || this.activeGroups.has(name);
4471
+ }
4472
+ }
4473
+ /** Get the names of currently active groups. */
4474
+ getActiveGroups() {
4475
+ return [...this.activeGroups];
4476
+ }
4477
+ // -----------------------------------------------------------------------
4478
+ // Stats & Introspection
4479
+ // -----------------------------------------------------------------------
4480
+ /** Total token cost of all tools if loaded without filtering. */
4481
+ getTotalTokenCost() {
4482
+ return this.index.getTotalTokenCost();
4483
+ }
4484
+ /** Estimate token cost of the currently filtered tool set. */
4485
+ async getFilteredTokenCost() {
4486
+ const tools = await this.getFilteredTools();
4487
+ let total = 0;
4488
+ for (const tool of tools) {
4489
+ total += ToolIndex.estimateTokens(tool);
4490
+ }
4491
+ return total;
4492
+ }
4493
+ /** Get compression stats showing savings from current strategy. */
4494
+ getCompressionStats() {
4495
+ return SchemaCompressor.estimateSavings(this.allTools);
4496
+ }
4497
+ /** Number of total indexed tools. */
4498
+ get totalToolCount() {
4499
+ return this.allTools.length;
4500
+ }
4501
+ /** Change strategy at runtime. */
4502
+ setStrategy(strategy) {
4503
+ this.strategy = strategy;
4504
+ }
4505
+ /**
4506
+ * Force a re-index of tools from all connected clients.
4507
+ * Call this after adding/removing MCP server connections.
4508
+ */
4509
+ async refresh() {
4510
+ this.initialized = false;
4511
+ await this.ensureInitialized();
4512
+ }
4513
+ /**
4514
+ * Execute a tool by routing to the correct MCP client.
4515
+ * Used by the `mcp_execute_tool` meta-tool to proxy tool calls.
4516
+ */
4517
+ async callTool(toolName, args, namespace) {
4518
+ await this.ensureInitialized();
4519
+ const indexedTool = this.getToolSchema(toolName, namespace);
4520
+ if (!indexedTool) {
4521
+ throw new Error(
4522
+ `Tool "${toolName}" not found${namespace ? ` on server "${namespace}"` : ""}. Use mcp_search_tool_bm25 or mcp_search_tool_regex to discover available tools.`
4523
+ );
4524
+ }
4525
+ const clients = this.getClients();
4526
+ const targetClient = clients.find(
4527
+ (c) => typeof c.getSessionId === "function" && c.getSessionId() === indexedTool.sessionId
4528
+ ) ?? clients.find((c) => c.isConnected());
4529
+ if (!targetClient) {
4530
+ throw new Error(`No connected client found for tool "${toolName}"`);
4531
+ }
4532
+ return await targetClient.callTool(toolName, args);
4533
+ }
4534
+ // -----------------------------------------------------------------------
4535
+ // Internals
4536
+ // -----------------------------------------------------------------------
4537
+ /** Lazy initialization — fetches tools from all connected clients. */
4538
+ async ensureInitialized() {
4539
+ if (this.initialized) return;
4540
+ this.allTools = await this.fetchAllTools();
4541
+ await this.index.buildIndex(this.allTools);
4542
+ this.buildGroups();
4543
+ this.initialized = true;
4544
+ }
4545
+ /** Fetch tools from all connected MCP clients. */
4546
+ async fetchAllTools() {
4547
+ const clients = this.getClients();
4548
+ const result = [];
4549
+ for (const client of clients) {
4550
+ if (!client.isConnected()) continue;
4551
+ try {
4552
+ const { tools } = await client.listTools();
4553
+ const serverId = typeof client.getServerId === "function" ? client.getServerId() ?? "unknown" : "unknown";
4554
+ const serverName = (typeof client.getServerName === "function" ? client.getServerName() : void 0) ?? serverId;
4555
+ const sessionId = typeof client.getSessionId === "function" ? client.getSessionId() ?? "unknown" : "unknown";
4556
+ for (const tool of tools) {
4557
+ result.push({
4558
+ ...tool,
4559
+ serverName,
4560
+ sessionId
4561
+ });
4562
+ }
4563
+ } catch (err) {
4564
+ console.warn("[ToolRouter] Failed to fetch tools from client:", err);
4565
+ }
4566
+ }
4567
+ return result;
4568
+ }
4569
+ /** Resolve the client input to a flat array of ToolClient instances. */
4570
+ getClients() {
4571
+ if (Array.isArray(this.client)) {
4572
+ return this.client;
4573
+ }
4574
+ if (typeof this.client.getClients === "function") {
4575
+ return this.client.getClients();
4576
+ }
4577
+ return [this.client];
4578
+ }
4579
+ /** Build group map from custom config or auto-detect from server names. */
4580
+ buildGroups() {
4581
+ this.groupsMap.clear();
4582
+ if (this.customGroups) {
4583
+ for (const [name, tools] of Object.entries(this.customGroups)) {
4584
+ this.groupsMap.set(name, {
4585
+ tools,
4586
+ active: this.activeGroups.size === 0 || this.activeGroups.has(name)
4587
+ });
4588
+ }
4589
+ } else {
4590
+ const serverTools = /* @__PURE__ */ new Map();
4591
+ for (const tool of this.allTools) {
4592
+ const group = tool.serverName;
4593
+ if (!serverTools.has(group)) {
4594
+ serverTools.set(group, []);
4595
+ }
4596
+ serverTools.get(group).push(tool.name);
4597
+ }
4598
+ for (const [serverName, tools] of serverTools) {
4599
+ this.groupsMap.set(serverName, {
4600
+ tools,
4601
+ active: this.activeGroups.size === 0 || this.activeGroups.has(serverName)
4602
+ });
4603
+ }
4604
+ }
4605
+ }
4606
+ /** Return only tools belonging to currently active groups. */
4607
+ getGroupFilteredTools() {
4608
+ const activeToolNames = /* @__PURE__ */ new Set();
4609
+ for (const [, info] of this.groupsMap) {
4610
+ if (info.active) {
4611
+ for (const name of info.tools) {
4612
+ activeToolNames.add(name);
4613
+ }
4614
+ }
4615
+ }
4616
+ const filtered = this.allTools.filter((t) => activeToolNames.has(t.name));
4617
+ if (this.compactSchemas) {
4618
+ return filtered.slice(0, this.maxTools).map((t) => {
4619
+ const compact = SchemaCompressor.toCompact(t);
4620
+ return {
4621
+ name: compact.name,
4622
+ description: (compact.description ?? "") + (compact.parameterHint ? ` Parameters: ${compact.parameterHint}` : ""),
4623
+ inputSchema: { type: "object", properties: {} }
4624
+ };
4625
+ });
4626
+ }
4627
+ return filtered.slice(0, this.maxTools);
4628
+ }
4629
+ /** The 4 meta-tool definitions exposed in `search` strategy. */
4630
+ getMetaToolDefinitions() {
4631
+ return [
4632
+ createSearchToolDefinition(),
4633
+ createRegexSearchToolDefinition(),
4634
+ createGetSchemaToolDefinition(),
4635
+ createExecuteToolDefinition()
4636
+ ];
4637
+ }
4638
+ };
4639
+
4640
+ export { APP_HOST_DEFAULTS, AppHost, AuthenticationError, ConfigurationError, ConnectionError, DEFAULT_CLIENT_NAME, DEFAULT_CLIENT_URI, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_LOGO_URI, DEFAULT_MCP_APP_CSP, DEFAULT_POLICY_URI, DisposableStore, Emitter, InvalidStateError, MCPClient, MCP_CLIENT_NAME, MCP_CLIENT_VERSION, McpError, MultiSessionClient, NotConnectedError, REDIS_KEY_PREFIX, RpcErrorCodes, SANDBOX_PROXY_READY_METHOD, SANDBOX_RESOURCE_READY_METHOD, SESSION_TTL_SECONDS, SOFTWARE_ID, SOFTWARE_VERSION, SSEClient, SSEConnectionManager, STATE_EXPIRATION_MS, SchemaCompressor, SessionNotFoundError, SessionValidationError, StorageOAuthClientProvider, TOKEN_EXPIRY_BUFFER_MS, ToolExecutionError, ToolIndex, ToolRouter, UnauthorizedError, createExecuteToolDefinition, createGetSchemaToolDefinition, createNextMcpHandler, createRegexSearchToolDefinition, createSSEHandler, createSearchToolDefinition, executeMetaTool, findToolByName, getToolUiResourceUri, isCallToolSuccess, isConnectAuthRequired, isConnectError, isConnectSuccess, isListToolsSuccess, isMetaTool, resolveMetaToolProxy, sanitizeServerLabel, storage };
3604
4641
  //# sourceMappingURL=index.mjs.map
3605
4642
  //# sourceMappingURL=index.mjs.map