@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.
- package/README.md +20 -27
- package/dist/adapters/agui-adapter.d.mts +16 -0
- package/dist/adapters/agui-adapter.d.ts +16 -0
- package/dist/adapters/agui-adapter.js +185 -0
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs +185 -0
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- package/dist/adapters/agui-middleware.d.mts +2 -0
- package/dist/adapters/agui-middleware.d.ts +2 -0
- package/dist/adapters/agui-middleware.js.map +1 -1
- package/dist/adapters/agui-middleware.mjs.map +1 -1
- package/dist/adapters/ai-adapter.d.mts +21 -0
- package/dist/adapters/ai-adapter.d.ts +21 -0
- package/dist/adapters/ai-adapter.js +175 -0
- package/dist/adapters/ai-adapter.js.map +1 -1
- package/dist/adapters/ai-adapter.mjs +175 -0
- package/dist/adapters/ai-adapter.mjs.map +1 -1
- package/dist/adapters/langchain-adapter.d.mts +16 -0
- package/dist/adapters/langchain-adapter.d.ts +16 -0
- package/dist/adapters/langchain-adapter.js +179 -0
- package/dist/adapters/langchain-adapter.js.map +1 -1
- package/dist/adapters/langchain-adapter.mjs +179 -0
- package/dist/adapters/langchain-adapter.mjs.map +1 -1
- package/dist/client/index.d.mts +4 -190
- package/dist/client/index.d.ts +4 -190
- package/dist/client/index.js +218 -54
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +215 -55
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +31 -17
- package/dist/client/react.d.ts +31 -17
- package/dist/client/react.js +447 -103
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +443 -105
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +5 -4
- package/dist/client/vue.d.ts +5 -4
- package/dist/client/vue.js +239 -63
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +236 -64
- package/dist/client/vue.mjs.map +1 -1
- package/dist/index-DcYfpY3H.d.mts +295 -0
- package/dist/index-GfC_eNEv.d.ts +295 -0
- package/dist/index.d.mts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1120 -59
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1097 -60
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +18 -5
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +18 -5
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +86 -4
- package/dist/shared/index.d.ts +86 -4
- package/dist/shared/index.js +874 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +865 -1
- package/dist/shared/index.mjs.map +1 -1
- package/dist/tool-router-Bo8qZbsD.d.ts +325 -0
- package/dist/tool-router-XnWVxPzv.d.mts +325 -0
- package/dist/{types-CW6lghof.d.mts → types-CfCoIsWI.d.mts} +27 -1
- package/dist/{types-CW6lghof.d.ts → types-CfCoIsWI.d.ts} +27 -1
- package/package.json +15 -12
- package/src/adapters/agui-adapter.ts +79 -0
- package/src/adapters/ai-adapter.ts +75 -0
- package/src/adapters/langchain-adapter.ts +75 -1
- package/src/client/core/app-host.ts +252 -65
- package/src/client/core/constants.ts +30 -0
- package/src/client/index.ts +6 -1
- package/src/client/react/index.ts +3 -0
- package/src/client/react/use-app-host.ts +8 -15
- package/src/client/react/use-mcp-apps.tsx +262 -49
- package/src/client/react/use-mcp.ts +23 -12
- package/src/client/utils/app-host-utils.ts +62 -0
- package/src/client/vue/use-mcp.ts +23 -12
- package/src/server/index.ts +2 -0
- package/src/server/mcp/oauth-client.ts +34 -9
- package/src/shared/index.ts +36 -0
- package/src/shared/meta-tools.ts +387 -0
- package/src/shared/schema-compressor.ts +124 -0
- package/src/shared/tool-index.ts +499 -0
- package/src/shared/tool-router.ts +469 -0
- package/src/shared/types.ts +30 -0
package/dist/index.js
CHANGED
|
@@ -1794,13 +1794,24 @@ var MCPClient = class _MCPClient {
|
|
|
1794
1794
|
}
|
|
1795
1795
|
} catch (error) {
|
|
1796
1796
|
if (error instanceof auth_js.UnauthorizedError || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
|
|
1797
|
-
this.emitStateChange("AUTHENTICATING");
|
|
1798
|
-
console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
|
|
1799
|
-
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3), false);
|
|
1800
1797
|
let authUrl = "";
|
|
1801
1798
|
if (this.oauthProvider) {
|
|
1802
|
-
authUrl = this.oauthProvider.authUrl || "";
|
|
1799
|
+
authUrl = (this.oauthProvider.authUrl || "").trim();
|
|
1800
|
+
}
|
|
1801
|
+
if (!authUrl) {
|
|
1802
|
+
const detail = error instanceof Error && error.message.trim().length > 0 ? error.message.trim() : "Unauthorized";
|
|
1803
|
+
const message = detail.toLowerCase() === "unauthorized" ? "OAuth authorization URL not available" : `OAuth authorization URL not available: ${detail}`;
|
|
1804
|
+
this.emitError(message, "auth");
|
|
1805
|
+
this.emitStateChange("FAILED");
|
|
1806
|
+
try {
|
|
1807
|
+
await storage.removeSession(this.identity, this.sessionId);
|
|
1808
|
+
} catch {
|
|
1809
|
+
}
|
|
1810
|
+
throw new Error(message);
|
|
1803
1811
|
}
|
|
1812
|
+
this.emitStateChange("AUTHENTICATING");
|
|
1813
|
+
console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
|
|
1814
|
+
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3), false);
|
|
1804
1815
|
if (this.serverId) {
|
|
1805
1816
|
this._onConnectionEvent.fire({
|
|
1806
1817
|
type: "auth_required",
|
|
@@ -2253,7 +2264,9 @@ var MCPClient = class _MCPClient {
|
|
|
2253
2264
|
* @returns Server name or undefined
|
|
2254
2265
|
*/
|
|
2255
2266
|
getServerName() {
|
|
2256
|
-
|
|
2267
|
+
const info = this.client?.getServerVersion();
|
|
2268
|
+
console.log("server info ->", info);
|
|
2269
|
+
return info?.title ?? info?.name ?? this.serverName;
|
|
2257
2270
|
}
|
|
2258
2271
|
/**
|
|
2259
2272
|
* Gets the server ID
|
|
@@ -3353,22 +3366,92 @@ var SSEClient = class {
|
|
|
3353
3366
|
|
|
3354
3367
|
// src/client/core/app-host.ts
|
|
3355
3368
|
init_cjs_shims();
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
"
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3369
|
+
|
|
3370
|
+
// src/client/utils/app-host-utils.ts
|
|
3371
|
+
init_cjs_shims();
|
|
3372
|
+
|
|
3373
|
+
// src/client/core/constants.ts
|
|
3374
|
+
init_cjs_shims();
|
|
3375
|
+
var SANDBOX_PROXY_READY_METHOD = "ui/notifications/sandbox-proxy-ready";
|
|
3376
|
+
var SANDBOX_RESOURCE_READY_METHOD = "ui/notifications/sandbox-resource-ready";
|
|
3377
|
+
var APP_HOST_DEFAULTS = {
|
|
3378
|
+
/** Default timeout for waiting for the sandbox proxy to be ready (ms). */
|
|
3379
|
+
SANDBOX_TIMEOUT_MS: 1e4,
|
|
3380
|
+
/** Default host info reported to guest apps. */
|
|
3381
|
+
HOST_INFO: { name: "mcp-ts-host", version: "1.0.0" },
|
|
3382
|
+
/** Supported MCP App URI schemes. */
|
|
3383
|
+
URI_SCHEMES: ["ui://", "mcp-app://"],
|
|
3384
|
+
/** Default theme for the host context. */
|
|
3385
|
+
THEME: "dark",
|
|
3386
|
+
/** Default platform for the host context. */
|
|
3387
|
+
PLATFORM: "web",
|
|
3388
|
+
/** Default max height for the iframe container (px). */
|
|
3389
|
+
MAX_HEIGHT: 6e3
|
|
3390
|
+
};
|
|
3391
|
+
|
|
3392
|
+
// src/client/utils/app-host-utils.ts
|
|
3393
|
+
var DEFAULT_SANDBOX_TIMEOUT_MS = APP_HOST_DEFAULTS.SANDBOX_TIMEOUT_MS;
|
|
3394
|
+
async function setupSandboxProxyIframe(iframe, sandboxProxyUrl) {
|
|
3395
|
+
iframe.style.width = "100%";
|
|
3396
|
+
iframe.style.height = "100%";
|
|
3397
|
+
iframe.style.border = "none";
|
|
3398
|
+
iframe.style.backgroundColor = "transparent";
|
|
3399
|
+
iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads");
|
|
3400
|
+
const onReady = new Promise((resolve, reject) => {
|
|
3401
|
+
let settled = false;
|
|
3402
|
+
const cleanup = () => {
|
|
3403
|
+
window.removeEventListener("message", messageListener);
|
|
3404
|
+
iframe.removeEventListener("error", errorListener);
|
|
3405
|
+
};
|
|
3406
|
+
const timeoutId = setTimeout(() => {
|
|
3407
|
+
if (!settled) {
|
|
3408
|
+
settled = true;
|
|
3409
|
+
cleanup();
|
|
3410
|
+
reject(new Error("Timed out waiting for sandbox proxy iframe to be ready"));
|
|
3411
|
+
}
|
|
3412
|
+
}, DEFAULT_SANDBOX_TIMEOUT_MS);
|
|
3413
|
+
const messageListener = (event) => {
|
|
3414
|
+
if (event.source === iframe.contentWindow) {
|
|
3415
|
+
if (event.data?.method === SANDBOX_PROXY_READY_METHOD) {
|
|
3416
|
+
if (!settled) {
|
|
3417
|
+
settled = true;
|
|
3418
|
+
clearTimeout(timeoutId);
|
|
3419
|
+
cleanup();
|
|
3420
|
+
resolve();
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
};
|
|
3425
|
+
const errorListener = () => {
|
|
3426
|
+
if (!settled) {
|
|
3427
|
+
settled = true;
|
|
3428
|
+
clearTimeout(timeoutId);
|
|
3429
|
+
cleanup();
|
|
3430
|
+
reject(new Error("Failed to load sandbox proxy iframe"));
|
|
3431
|
+
}
|
|
3432
|
+
};
|
|
3433
|
+
window.addEventListener("message", messageListener);
|
|
3434
|
+
iframe.addEventListener("error", errorListener);
|
|
3435
|
+
});
|
|
3436
|
+
iframe.src = sandboxProxyUrl.href;
|
|
3437
|
+
return { onReady };
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
// src/client/core/app-host.ts
|
|
3441
|
+
var DEFAULT_MCP_APP_CSP = {
|
|
3442
|
+
"default-src": "'self'",
|
|
3443
|
+
"script-src": "'self' 'unsafe-inline' 'unsafe-eval' https: blob:",
|
|
3444
|
+
"style-src": "'self' 'unsafe-inline' https:",
|
|
3445
|
+
"connect-src": "'self' https: wss:",
|
|
3446
|
+
"img-src": "'self' data: https: blob:",
|
|
3447
|
+
"font-src": "'self' data: https:",
|
|
3448
|
+
"media-src": "'self' https: blob:",
|
|
3449
|
+
"frame-src": "'none'",
|
|
3450
|
+
"object-src": "'none'",
|
|
3451
|
+
"base-uri": "'self'"
|
|
3452
|
+
};
|
|
3453
|
+
var HOST_INFO = APP_HOST_DEFAULTS.HOST_INFO;
|
|
3454
|
+
var MCP_URI_SCHEMES = APP_HOST_DEFAULTS.URI_SCHEMES;
|
|
3372
3455
|
var AppHost = class {
|
|
3373
3456
|
constructor(client, iframe, options) {
|
|
3374
3457
|
this.client = client;
|
|
@@ -3377,10 +3460,12 @@ var AppHost = class {
|
|
|
3377
3460
|
__publicField(this, "sessionId");
|
|
3378
3461
|
__publicField(this, "resourceCache", /* @__PURE__ */ new Map());
|
|
3379
3462
|
__publicField(this, "debug");
|
|
3380
|
-
|
|
3463
|
+
__publicField(this, "sandboxConfig");
|
|
3464
|
+
__publicField(this, "options");
|
|
3381
3465
|
__publicField(this, "onAppMessage");
|
|
3382
|
-
this.
|
|
3383
|
-
this.
|
|
3466
|
+
this.options = options || {};
|
|
3467
|
+
this.debug = this.options.debug ?? false;
|
|
3468
|
+
this.sandboxConfig = this.options.sandbox;
|
|
3384
3469
|
this.bridge = this.initializeBridge();
|
|
3385
3470
|
}
|
|
3386
3471
|
// ============================================
|
|
@@ -3407,19 +3492,35 @@ var AppHost = class {
|
|
|
3407
3492
|
}
|
|
3408
3493
|
}
|
|
3409
3494
|
/**
|
|
3410
|
-
* Launch an MCP App from a URL
|
|
3495
|
+
* Launch an MCP App from a URL, MCP resource URI, or RAW HTML.
|
|
3411
3496
|
* Loads the HTML first, then establishes bridge connection.
|
|
3412
3497
|
*/
|
|
3413
|
-
async launch(
|
|
3498
|
+
async launch(source, sessionId) {
|
|
3414
3499
|
if (sessionId) this.sessionId = sessionId;
|
|
3415
3500
|
const initializedPromise = this.onAppReady();
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3501
|
+
let htmlToRender = source.html;
|
|
3502
|
+
if (!htmlToRender && source.uri) {
|
|
3503
|
+
if (this.isMcpUri(source.uri)) {
|
|
3504
|
+
htmlToRender = await this.readMcpAppHtml(source.uri);
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
if (!htmlToRender && source.uri && !this.isMcpUri(source.uri)) {
|
|
3508
|
+
this.iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads");
|
|
3509
|
+
this.iframe.src = source.uri;
|
|
3510
|
+
await this.onIframeReady();
|
|
3511
|
+
await this.connectBridge();
|
|
3512
|
+
} else if (htmlToRender) {
|
|
3513
|
+
if (!this.sandboxConfig) {
|
|
3514
|
+
throw new Error("Sandbox configuration requires a proxy URL to render HTML safely.");
|
|
3515
|
+
}
|
|
3516
|
+
await this.launchSandboxedHtml(htmlToRender, this.sandboxConfig);
|
|
3517
|
+
await this.connectBridge();
|
|
3518
|
+
this.log("Sending HTML resource to sandbox proxy (MCP Apps notification)");
|
|
3519
|
+
await this.bridge.sendSandboxResourceReady({
|
|
3520
|
+
html: htmlToRender,
|
|
3521
|
+
csp: this.sandboxConfig.csp
|
|
3522
|
+
});
|
|
3420
3523
|
}
|
|
3421
|
-
await this.onIframeReady();
|
|
3422
|
-
await this.connectBridge();
|
|
3423
3524
|
this.log("Waiting for app initialization");
|
|
3424
3525
|
await Promise.race([
|
|
3425
3526
|
initializedPromise,
|
|
@@ -3430,6 +3531,19 @@ var AppHost = class {
|
|
|
3430
3531
|
]);
|
|
3431
3532
|
this.log("App launched and ready");
|
|
3432
3533
|
}
|
|
3534
|
+
// Set host context manually
|
|
3535
|
+
setHostContext(context) {
|
|
3536
|
+
this.options.hostContext = context;
|
|
3537
|
+
if (this.bridge) {
|
|
3538
|
+
this.bridge.setHostContext(context);
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
// Send streaming inputs manually
|
|
3542
|
+
sendToolInputPartial(params) {
|
|
3543
|
+
if (this.bridge) {
|
|
3544
|
+
this.bridge.sendToolInputPartial(params);
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3433
3547
|
/**
|
|
3434
3548
|
* Wait for app to signal initialization complete
|
|
3435
3549
|
*/
|
|
@@ -3480,14 +3594,17 @@ var AppHost = class {
|
|
|
3480
3594
|
this.log("Sending tool cancellation to app");
|
|
3481
3595
|
this.bridge.sendToolCancelled({ reason });
|
|
3482
3596
|
}
|
|
3597
|
+
/**
|
|
3598
|
+
* Tell the guest UI the resource is being torn down (unload / cleanup).
|
|
3599
|
+
* Forwards to {@link AppBridge.teardownResource} on `@modelcontextprotocol/ext-apps/app-bridge`.
|
|
3600
|
+
*/
|
|
3601
|
+
teardownResource(params = {}) {
|
|
3602
|
+
this.log("Sending resource teardown to app");
|
|
3603
|
+
this.bridge.teardownResource(params);
|
|
3604
|
+
}
|
|
3483
3605
|
// ============================================
|
|
3484
3606
|
// Private: Initialization
|
|
3485
3607
|
// ============================================
|
|
3486
|
-
configureSandbox() {
|
|
3487
|
-
if (this.iframe.sandbox.value !== SANDBOX_PERMISSIONS) {
|
|
3488
|
-
this.iframe.sandbox.value = SANDBOX_PERMISSIONS;
|
|
3489
|
-
}
|
|
3490
|
-
}
|
|
3491
3608
|
initializeBridge() {
|
|
3492
3609
|
const bridge = new appBridge.AppBridge(
|
|
3493
3610
|
null,
|
|
@@ -3496,12 +3613,10 @@ var AppHost = class {
|
|
|
3496
3613
|
openLinks: {},
|
|
3497
3614
|
serverTools: {},
|
|
3498
3615
|
logging: {},
|
|
3499
|
-
// Declare support for model context updates
|
|
3500
3616
|
updateModelContext: { text: {} }
|
|
3501
3617
|
},
|
|
3502
3618
|
{
|
|
3503
|
-
|
|
3504
|
-
hostContext: {
|
|
3619
|
+
hostContext: this.options.hostContext || {
|
|
3505
3620
|
theme: "dark",
|
|
3506
3621
|
platform: "web",
|
|
3507
3622
|
containerDimensions: { maxHeight: 6e3 },
|
|
@@ -3510,19 +3625,56 @@ var AppHost = class {
|
|
|
3510
3625
|
}
|
|
3511
3626
|
}
|
|
3512
3627
|
);
|
|
3628
|
+
bridge.fallbackRequestHandler = this.options.onFallbackRequest;
|
|
3513
3629
|
bridge.oncalltool = (params) => this.handleToolCall(params);
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3630
|
+
if (this.options.onReadResource) {
|
|
3631
|
+
bridge.onreadresource = async (params) => {
|
|
3632
|
+
const resp = await this.options.onReadResource(params.uri);
|
|
3633
|
+
return {
|
|
3634
|
+
contents: resp.contents.map((c) => ({
|
|
3635
|
+
uri: params.uri,
|
|
3636
|
+
text: c.text,
|
|
3637
|
+
blob: c.blob
|
|
3638
|
+
}))
|
|
3639
|
+
};
|
|
3640
|
+
};
|
|
3641
|
+
}
|
|
3642
|
+
bridge.onopenlink = async (params, extra) => {
|
|
3643
|
+
if (this.options.onOpenLink) {
|
|
3644
|
+
return await this.options.onOpenLink(params, extra);
|
|
3645
|
+
}
|
|
3646
|
+
return this.handleOpenLink(params);
|
|
3647
|
+
};
|
|
3648
|
+
bridge.onmessage = async (params, extra) => {
|
|
3649
|
+
if (this.options.onMessage) {
|
|
3650
|
+
return await this.options.onMessage(params, extra);
|
|
3651
|
+
}
|
|
3652
|
+
return this.handleMessage(params);
|
|
3653
|
+
};
|
|
3654
|
+
bridge.onloggingmessage = (params) => {
|
|
3655
|
+
this.log(`App log [${params.level}]: ${params.data}`);
|
|
3656
|
+
if (this.options.onLoggingMessage) {
|
|
3657
|
+
this.options.onLoggingMessage(params);
|
|
3658
|
+
}
|
|
3659
|
+
};
|
|
3517
3660
|
bridge.onupdatemodelcontext = async () => ({});
|
|
3518
|
-
bridge.onsizechange = async (
|
|
3519
|
-
|
|
3520
|
-
if (
|
|
3661
|
+
bridge.onsizechange = async (params) => {
|
|
3662
|
+
const { width, height } = params;
|
|
3663
|
+
if (height !== void 0 && height > 0) {
|
|
3664
|
+
this.iframe.style.height = `${height}px`;
|
|
3665
|
+
}
|
|
3666
|
+
if (width !== void 0 && width > 0) this.iframe.style.minWidth = `min(${width}px, 100%)`;
|
|
3667
|
+
if (this.options.onSizeChanged) {
|
|
3668
|
+
this.options.onSizeChanged(params);
|
|
3669
|
+
}
|
|
3521
3670
|
return {};
|
|
3522
3671
|
};
|
|
3523
|
-
bridge.onrequestdisplaymode = async (params) =>
|
|
3524
|
-
|
|
3525
|
-
|
|
3672
|
+
bridge.onrequestdisplaymode = async (params, extra) => {
|
|
3673
|
+
if (this.options.onRequestDisplayMode) {
|
|
3674
|
+
return await this.options.onRequestDisplayMode(params, extra);
|
|
3675
|
+
}
|
|
3676
|
+
return { mode: params.mode === "fullscreen" ? "fullscreen" : "inline" };
|
|
3677
|
+
};
|
|
3526
3678
|
return bridge;
|
|
3527
3679
|
}
|
|
3528
3680
|
async connectBridge() {
|
|
@@ -3536,6 +3688,9 @@ var AppHost = class {
|
|
|
3536
3688
|
this.log("Bridge connected successfully");
|
|
3537
3689
|
} catch (error) {
|
|
3538
3690
|
this.log("Bridge connection failed", "error");
|
|
3691
|
+
if (this.options.onError) {
|
|
3692
|
+
this.options.onError(error instanceof Error ? error : new Error(String(error)));
|
|
3693
|
+
}
|
|
3539
3694
|
throw error;
|
|
3540
3695
|
}
|
|
3541
3696
|
}
|
|
@@ -3543,8 +3698,11 @@ var AppHost = class {
|
|
|
3543
3698
|
// Private: Bridge Event Handlers
|
|
3544
3699
|
// ============================================
|
|
3545
3700
|
async handleToolCall(params) {
|
|
3546
|
-
if (
|
|
3547
|
-
|
|
3701
|
+
if (this.options.onCallTool) {
|
|
3702
|
+
return await this.options.onCallTool(params);
|
|
3703
|
+
}
|
|
3704
|
+
if (!this.client || !this.client.isConnected()) {
|
|
3705
|
+
throw new Error("Client disconnected or not provided");
|
|
3548
3706
|
}
|
|
3549
3707
|
const sessionId = await this.getSessionId();
|
|
3550
3708
|
if (!sessionId) {
|
|
@@ -3568,13 +3726,19 @@ var AppHost = class {
|
|
|
3568
3726
|
// ============================================
|
|
3569
3727
|
// Private: Resource Loading
|
|
3570
3728
|
// ============================================
|
|
3571
|
-
async
|
|
3572
|
-
|
|
3573
|
-
|
|
3729
|
+
async launchSandboxedHtml(html, config) {
|
|
3730
|
+
const sandboxUrlString = config.url instanceof URL ? config.url.href : config.url;
|
|
3731
|
+
const url = new URL(sandboxUrlString, globalThis.location?.href);
|
|
3732
|
+
if (config.csp && Object.keys(config.csp).length > 0) {
|
|
3733
|
+
url.searchParams.set("csp", JSON.stringify(config.csp));
|
|
3574
3734
|
}
|
|
3735
|
+
const { onReady } = await setupSandboxProxyIframe(this.iframe, url);
|
|
3736
|
+
await onReady;
|
|
3737
|
+
}
|
|
3738
|
+
async readMcpAppHtml(uri) {
|
|
3575
3739
|
const sessionId = await this.getSessionId();
|
|
3576
|
-
if (!sessionId) {
|
|
3577
|
-
throw new Error("No active session");
|
|
3740
|
+
if (!sessionId && !this.options.onReadResource) {
|
|
3741
|
+
throw new Error("No active session.");
|
|
3578
3742
|
}
|
|
3579
3743
|
const response = await this.fetchResourceWithCache(sessionId, uri);
|
|
3580
3744
|
if (!response?.contents?.length) {
|
|
@@ -3585,10 +3749,18 @@ var AppHost = class {
|
|
|
3585
3749
|
if (!html) {
|
|
3586
3750
|
throw new Error(`Invalid content in resource: ${uri}`);
|
|
3587
3751
|
}
|
|
3588
|
-
|
|
3589
|
-
this.iframe.src = URL.createObjectURL(blob);
|
|
3752
|
+
return html;
|
|
3590
3753
|
}
|
|
3591
3754
|
async fetchResourceWithCache(sessionId, uri) {
|
|
3755
|
+
if (this.options.onReadResource) {
|
|
3756
|
+
return await this.options.onReadResource(uri);
|
|
3757
|
+
}
|
|
3758
|
+
if (!sessionId) {
|
|
3759
|
+
throw new Error("No active session");
|
|
3760
|
+
}
|
|
3761
|
+
if (!this.client) {
|
|
3762
|
+
throw new Error("No client to read resource from");
|
|
3763
|
+
}
|
|
3592
3764
|
if (this.hasClientCache()) {
|
|
3593
3765
|
return this.client.getOrFetchResource(sessionId, uri);
|
|
3594
3766
|
}
|
|
@@ -3601,8 +3773,11 @@ var AppHost = class {
|
|
|
3601
3773
|
}
|
|
3602
3774
|
async preloadResource(uri) {
|
|
3603
3775
|
try {
|
|
3776
|
+
if (this.options.onReadResource) {
|
|
3777
|
+
return await this.options.onReadResource(uri);
|
|
3778
|
+
}
|
|
3604
3779
|
const sessionId = await this.getSessionId();
|
|
3605
|
-
if (!sessionId) return null;
|
|
3780
|
+
if (!sessionId || !this.client) return null;
|
|
3606
3781
|
return await this.client.readResource(sessionId, uri);
|
|
3607
3782
|
} catch (error) {
|
|
3608
3783
|
this.log(`Preload failed for ${uri}`, "warn");
|
|
@@ -3614,6 +3789,7 @@ var AppHost = class {
|
|
|
3614
3789
|
// ============================================
|
|
3615
3790
|
async getSessionId() {
|
|
3616
3791
|
if (this.sessionId) return this.sessionId;
|
|
3792
|
+
if (!this.client) return void 0;
|
|
3617
3793
|
const result = await this.client.getSessions();
|
|
3618
3794
|
return result.sessions?.[0]?.sessionId;
|
|
3619
3795
|
}
|
|
@@ -3621,6 +3797,7 @@ var AppHost = class {
|
|
|
3621
3797
|
return MCP_URI_SCHEMES.some((scheme) => url.startsWith(scheme));
|
|
3622
3798
|
}
|
|
3623
3799
|
hasClientCache() {
|
|
3800
|
+
if (!this.client) return false;
|
|
3624
3801
|
return "getOrFetchResource" in this.client && typeof this.client.getOrFetchResource === "function";
|
|
3625
3802
|
}
|
|
3626
3803
|
extractUiResourceUri(tool) {
|
|
@@ -3689,6 +3866,877 @@ function findToolByName(connections, toolName) {
|
|
|
3689
3866
|
return void 0;
|
|
3690
3867
|
}
|
|
3691
3868
|
|
|
3869
|
+
// src/shared/tool-router.ts
|
|
3870
|
+
init_cjs_shims();
|
|
3871
|
+
|
|
3872
|
+
// src/shared/tool-index.ts
|
|
3873
|
+
init_cjs_shims();
|
|
3874
|
+
var CALIBRATION_DIVISOR = 3.6;
|
|
3875
|
+
function classifyChar(ch) {
|
|
3876
|
+
const code = ch.charCodeAt(0);
|
|
3877
|
+
if (code <= 32 || ch === "{" || ch === "}" || ch === "[" || ch === "]" || ch === ":" || ch === ",") return 1;
|
|
3878
|
+
if (code >= 33 && code <= 47) return 1.5;
|
|
3879
|
+
if (code >= 48 && code <= 57) return 2;
|
|
3880
|
+
if (code >= 65 && code <= 90) return 3.5;
|
|
3881
|
+
if (code >= 97 && code <= 122) return 4;
|
|
3882
|
+
return 2.5;
|
|
3883
|
+
}
|
|
3884
|
+
var ToolIndex = class _ToolIndex {
|
|
3885
|
+
constructor(options = {}) {
|
|
3886
|
+
/** All indexed tools keyed by name (supports duplicates). */
|
|
3887
|
+
__publicField(this, "tools", /* @__PURE__ */ new Map());
|
|
3888
|
+
/** Precomputed lightweight summaries keyed by document. */
|
|
3889
|
+
__publicField(this, "toolSummaries", /* @__PURE__ */ new Map());
|
|
3890
|
+
/** Pre-computed search text for keyword matching (lowercase), keyed by document. */
|
|
3891
|
+
__publicField(this, "searchTexts", /* @__PURE__ */ new Map());
|
|
3892
|
+
/** Pre-computed IDF values per token (computed once on build). */
|
|
3893
|
+
__publicField(this, "idf", /* @__PURE__ */ new Map());
|
|
3894
|
+
/** Per-tool TF vectors (Map<token, tf>). */
|
|
3895
|
+
__publicField(this, "tfVectors", /* @__PURE__ */ new Map());
|
|
3896
|
+
/** Optional: pre-computed embedding vectors per tool. */
|
|
3897
|
+
__publicField(this, "embeddings", /* @__PURE__ */ new Map());
|
|
3898
|
+
/** BM25: document lengths in tokens for each tool. */
|
|
3899
|
+
__publicField(this, "docLengths", /* @__PURE__ */ new Map());
|
|
3900
|
+
/** BM25: average document length across the entire index. */
|
|
3901
|
+
__publicField(this, "avgDocLength", 0);
|
|
3902
|
+
/** Cached total estimated token cost across all indexed tools. */
|
|
3903
|
+
__publicField(this, "totalTokenCost", 0);
|
|
3904
|
+
__publicField(this, "options");
|
|
3905
|
+
this.options = {
|
|
3906
|
+
embedFn: options.embedFn ?? void 0,
|
|
3907
|
+
keywordWeight: options.keywordWeight ?? 0.4
|
|
3908
|
+
};
|
|
3909
|
+
}
|
|
3910
|
+
// -----------------------------------------------------------------------
|
|
3911
|
+
// Indexing
|
|
3912
|
+
// -----------------------------------------------------------------------
|
|
3913
|
+
/**
|
|
3914
|
+
* Build (or rebuild) the index from the given tool set.
|
|
3915
|
+
* Call this after connecting / reconnecting to MCP servers.
|
|
3916
|
+
*/
|
|
3917
|
+
async buildIndex(tools) {
|
|
3918
|
+
this.tools.clear();
|
|
3919
|
+
this.toolSummaries.clear();
|
|
3920
|
+
this.searchTexts.clear();
|
|
3921
|
+
this.idf.clear();
|
|
3922
|
+
this.tfVectors.clear();
|
|
3923
|
+
this.embeddings.clear();
|
|
3924
|
+
this.docLengths.clear();
|
|
3925
|
+
this.avgDocLength = 0;
|
|
3926
|
+
this.totalTokenCost = 0;
|
|
3927
|
+
const allTokenSets = /* @__PURE__ */ new Map();
|
|
3928
|
+
let totalLength = 0;
|
|
3929
|
+
for (const tool of tools) {
|
|
3930
|
+
const docKey = this.getDocumentKey(tool);
|
|
3931
|
+
if (!this.tools.has(tool.name)) {
|
|
3932
|
+
this.tools.set(tool.name, []);
|
|
3933
|
+
}
|
|
3934
|
+
this.tools.get(tool.name).push(tool);
|
|
3935
|
+
const estimatedTokens = _ToolIndex.estimateTokens(tool);
|
|
3936
|
+
this.toolSummaries.set(docKey, {
|
|
3937
|
+
name: tool.name,
|
|
3938
|
+
description: tool.description ?? "",
|
|
3939
|
+
serverName: tool.serverName,
|
|
3940
|
+
sessionId: tool.sessionId,
|
|
3941
|
+
estimatedTokens
|
|
3942
|
+
});
|
|
3943
|
+
this.totalTokenCost += estimatedTokens;
|
|
3944
|
+
const text = this.buildSearchableText(tool).toLowerCase();
|
|
3945
|
+
this.searchTexts.set(docKey, text);
|
|
3946
|
+
const tokens = this.tokenize(text);
|
|
3947
|
+
const tf = /* @__PURE__ */ new Map();
|
|
3948
|
+
const uniqueTokens = /* @__PURE__ */ new Set();
|
|
3949
|
+
for (const tok of tokens) {
|
|
3950
|
+
tf.set(tok, (tf.get(tok) ?? 0) + 1);
|
|
3951
|
+
uniqueTokens.add(tok);
|
|
3952
|
+
}
|
|
3953
|
+
const maxTf = Math.max(...tf.values(), 1);
|
|
3954
|
+
for (const [k, v] of tf) {
|
|
3955
|
+
tf.set(k, v / maxTf);
|
|
3956
|
+
}
|
|
3957
|
+
this.tfVectors.set(docKey, tf);
|
|
3958
|
+
allTokenSets.set(docKey, uniqueTokens);
|
|
3959
|
+
const length = tokens.length;
|
|
3960
|
+
this.docLengths.set(docKey, length);
|
|
3961
|
+
totalLength += length;
|
|
3962
|
+
}
|
|
3963
|
+
this.avgDocLength = totalLength / (tools.length || 1);
|
|
3964
|
+
const totalDocs = tools.length || 1;
|
|
3965
|
+
const dfCounts = /* @__PURE__ */ new Map();
|
|
3966
|
+
for (const tokenSet of allTokenSets.values()) {
|
|
3967
|
+
for (const tok of tokenSet) {
|
|
3968
|
+
dfCounts.set(tok, (dfCounts.get(tok) ?? 0) + 1);
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
for (const [tok, df] of dfCounts) {
|
|
3972
|
+
this.idf.set(tok, Math.log(totalDocs / df) + 1);
|
|
3973
|
+
}
|
|
3974
|
+
if (this.options.embedFn) {
|
|
3975
|
+
const names = [...this.searchTexts.keys()];
|
|
3976
|
+
const texts = names.map((n) => this.searchTexts.get(n));
|
|
3977
|
+
try {
|
|
3978
|
+
const vectors = await this.options.embedFn(texts);
|
|
3979
|
+
for (let i = 0; i < names.length; i++) {
|
|
3980
|
+
if (vectors[i]) {
|
|
3981
|
+
this.embeddings.set(names[i], vectors[i]);
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
} catch (err) {
|
|
3985
|
+
console.warn("[ToolIndex] Embedding generation failed, falling back to keyword-only search:", err);
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
// -----------------------------------------------------------------------
|
|
3990
|
+
// Search
|
|
3991
|
+
// -----------------------------------------------------------------------
|
|
3992
|
+
/**
|
|
3993
|
+
* Search the index and return the top-K most relevant tools.
|
|
3994
|
+
*
|
|
3995
|
+
* When an `embedFn` is configured the final score is a weighted blend of
|
|
3996
|
+
* keyword TF-IDF similarity and embedding cosine-similarity:
|
|
3997
|
+
*
|
|
3998
|
+
* `score = keywordWeight × keyword_score + (1 - keywordWeight) × cosine_score`
|
|
3999
|
+
*/
|
|
4000
|
+
async search(query, topK = 5) {
|
|
4001
|
+
if (this.tools.size === 0) return [];
|
|
4002
|
+
const queryLower = query.toLowerCase();
|
|
4003
|
+
const queryTokens = this.tokenize(queryLower);
|
|
4004
|
+
const keywordScores = /* @__PURE__ */ new Map();
|
|
4005
|
+
const k1 = 1.2;
|
|
4006
|
+
const b = 0.75;
|
|
4007
|
+
for (const [docKey, docTf] of this.tfVectors) {
|
|
4008
|
+
let score = 0;
|
|
4009
|
+
const docLen = this.docLengths.get(docKey) ?? 0;
|
|
4010
|
+
for (const tok of queryTokens) {
|
|
4011
|
+
const tfVal = docTf.get(tok) ?? 0;
|
|
4012
|
+
if (tfVal === 0) continue;
|
|
4013
|
+
const idf = this.idf.get(tok) ?? 0;
|
|
4014
|
+
const numerator = tfVal * (k1 + 1);
|
|
4015
|
+
const denominator = tfVal + k1 * (1 - b + b * (docLen / this.avgDocLength));
|
|
4016
|
+
score += idf * (numerator / denominator);
|
|
4017
|
+
}
|
|
4018
|
+
keywordScores.set(docKey, score);
|
|
4019
|
+
}
|
|
4020
|
+
let embeddingScores = null;
|
|
4021
|
+
if (this.options.embedFn && this.embeddings.size > 0) {
|
|
4022
|
+
try {
|
|
4023
|
+
const [queryEmbedding] = await this.options.embedFn([queryLower]);
|
|
4024
|
+
if (queryEmbedding) {
|
|
4025
|
+
embeddingScores = /* @__PURE__ */ new Map();
|
|
4026
|
+
for (const [docKey, vec] of this.embeddings) {
|
|
4027
|
+
embeddingScores.set(docKey, this.cosineSimilarity(queryEmbedding, vec));
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
} catch {
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
const kw = this.options.keywordWeight;
|
|
4034
|
+
const finalScores = [];
|
|
4035
|
+
for (const docKey of this.toolSummaries.keys()) {
|
|
4036
|
+
const kwScore = keywordScores.get(docKey) ?? 0;
|
|
4037
|
+
const embScore = embeddingScores?.get(docKey) ?? 0;
|
|
4038
|
+
const score = embeddingScores ? kw * kwScore + (1 - kw) * embScore : kwScore;
|
|
4039
|
+
if (score > 0) {
|
|
4040
|
+
finalScores.push({ docKey, score });
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
finalScores.sort((a, b2) => b2.score - a.score);
|
|
4044
|
+
return finalScores.slice(0, topK).map(({ docKey }) => {
|
|
4045
|
+
return this.toolSummaries.get(docKey);
|
|
4046
|
+
});
|
|
4047
|
+
}
|
|
4048
|
+
/**
|
|
4049
|
+
* Search tools using a regex pattern.
|
|
4050
|
+
* Matches against name, description, and parameter metadata.
|
|
4051
|
+
*/
|
|
4052
|
+
searchRegex(pattern, topK = 5) {
|
|
4053
|
+
if (this.tools.size === 0) return [];
|
|
4054
|
+
try {
|
|
4055
|
+
let flags = "";
|
|
4056
|
+
let cleanPattern = pattern;
|
|
4057
|
+
if (pattern.includes("(?i)")) {
|
|
4058
|
+
flags = "i";
|
|
4059
|
+
cleanPattern = pattern.replace(/\(\?i\)/g, "");
|
|
4060
|
+
}
|
|
4061
|
+
const regex = new RegExp(cleanPattern, flags || void 0);
|
|
4062
|
+
const matches = [];
|
|
4063
|
+
for (const [docKey, text] of this.searchTexts) {
|
|
4064
|
+
const tool = this.toolSummaries.get(docKey);
|
|
4065
|
+
if (!tool) continue;
|
|
4066
|
+
if (regex.test(text) || regex.test(tool.name)) {
|
|
4067
|
+
let score = 1;
|
|
4068
|
+
if (tool.name === cleanPattern) score = 10;
|
|
4069
|
+
else if (tool.name.startsWith(cleanPattern)) score = 5;
|
|
4070
|
+
else if (tool.name.toLowerCase().includes(cleanPattern.toLowerCase())) score = 2;
|
|
4071
|
+
matches.push({ docKey, score });
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
matches.sort((a, b) => b.score - a.score);
|
|
4075
|
+
return matches.slice(0, topK).map(({ docKey }) => {
|
|
4076
|
+
return this.toolSummaries.get(docKey);
|
|
4077
|
+
});
|
|
4078
|
+
} catch (err) {
|
|
4079
|
+
console.warn("[ToolIndex] Regex search failed:", err);
|
|
4080
|
+
return [];
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
// -----------------------------------------------------------------------
|
|
4084
|
+
// Accessors
|
|
4085
|
+
// -----------------------------------------------------------------------
|
|
4086
|
+
/**
|
|
4087
|
+
* Get tool definition(s) by name.
|
|
4088
|
+
* If namespace is provided, it tries to match sessionId or serverName.
|
|
4089
|
+
*/
|
|
4090
|
+
getTool(name, namespace) {
|
|
4091
|
+
const list = this.tools.get(name) ?? [];
|
|
4092
|
+
if (!namespace) return list;
|
|
4093
|
+
return list.filter((t) => t.sessionId === namespace || t.serverName === namespace);
|
|
4094
|
+
}
|
|
4095
|
+
/** All indexed tool names. */
|
|
4096
|
+
getToolNames() {
|
|
4097
|
+
return [...this.tools.keys()];
|
|
4098
|
+
}
|
|
4099
|
+
/** Number of indexed tools (including duplicates). */
|
|
4100
|
+
get size() {
|
|
4101
|
+
let count = 0;
|
|
4102
|
+
for (const list of this.tools.values()) {
|
|
4103
|
+
count += list.length;
|
|
4104
|
+
}
|
|
4105
|
+
return count;
|
|
4106
|
+
}
|
|
4107
|
+
/** Total estimated token cost of all indexed tool schemas. */
|
|
4108
|
+
getTotalTokenCost() {
|
|
4109
|
+
return this.totalTokenCost;
|
|
4110
|
+
}
|
|
4111
|
+
// -----------------------------------------------------------------------
|
|
4112
|
+
// Static Helpers
|
|
4113
|
+
// -----------------------------------------------------------------------
|
|
4114
|
+
/**
|
|
4115
|
+
* Estimate token count of a tool's full schema (name + description + inputSchema).
|
|
4116
|
+
*
|
|
4117
|
+
* Uses character-class weighted counting calibrated against cl100k_base.
|
|
4118
|
+
* Accuracy is typically within ±10% for JSON Schema payloads.
|
|
4119
|
+
*/
|
|
4120
|
+
static estimateTokens(tool) {
|
|
4121
|
+
const parts = [tool.name];
|
|
4122
|
+
if (tool.description) parts.push(tool.description);
|
|
4123
|
+
if (tool.inputSchema) parts.push(JSON.stringify(tool.inputSchema));
|
|
4124
|
+
const text = parts.join(" ");
|
|
4125
|
+
let weightedLen = 0;
|
|
4126
|
+
for (let i = 0; i < text.length; i++) {
|
|
4127
|
+
weightedLen += 1 / classifyChar(text[i]);
|
|
4128
|
+
}
|
|
4129
|
+
return Math.ceil(weightedLen / (1 / CALIBRATION_DIVISOR));
|
|
4130
|
+
}
|
|
4131
|
+
// -----------------------------------------------------------------------
|
|
4132
|
+
// Internals
|
|
4133
|
+
// -----------------------------------------------------------------------
|
|
4134
|
+
/** Build a single searchable string from tool metadata. */
|
|
4135
|
+
buildSearchableText(tool) {
|
|
4136
|
+
const parts = [tool.name];
|
|
4137
|
+
if (tool.description) parts.push(tool.description);
|
|
4138
|
+
if (tool.inputSchema && typeof tool.inputSchema === "object") {
|
|
4139
|
+
const schema = tool.inputSchema;
|
|
4140
|
+
const props = schema.properties;
|
|
4141
|
+
if (props) {
|
|
4142
|
+
for (const [key, val] of Object.entries(props)) {
|
|
4143
|
+
parts.push(key);
|
|
4144
|
+
if (val && typeof val === "object" && val.description) {
|
|
4145
|
+
parts.push(val.description);
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
return parts.join(" ");
|
|
4151
|
+
}
|
|
4152
|
+
getDocumentKey(tool) {
|
|
4153
|
+
return `${tool.sessionId}::${tool.serverName}::${tool.name}`;
|
|
4154
|
+
}
|
|
4155
|
+
/** Simple whitespace + camelCase + snake_case tokenizer. */
|
|
4156
|
+
tokenize(text) {
|
|
4157
|
+
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);
|
|
4158
|
+
}
|
|
4159
|
+
/** Cosine similarity between two vectors. */
|
|
4160
|
+
cosineSimilarity(a, b) {
|
|
4161
|
+
const len = Math.min(a.length, b.length);
|
|
4162
|
+
let dot = 0;
|
|
4163
|
+
let magA = 0;
|
|
4164
|
+
let magB = 0;
|
|
4165
|
+
for (let i = 0; i < len; i++) {
|
|
4166
|
+
dot += a[i] * b[i];
|
|
4167
|
+
magA += a[i] * a[i];
|
|
4168
|
+
magB += b[i] * b[i];
|
|
4169
|
+
}
|
|
4170
|
+
const denom = Math.sqrt(magA) * Math.sqrt(magB);
|
|
4171
|
+
return denom > 0 ? dot / denom : 0;
|
|
4172
|
+
}
|
|
4173
|
+
};
|
|
4174
|
+
|
|
4175
|
+
// src/shared/schema-compressor.ts
|
|
4176
|
+
init_cjs_shims();
|
|
4177
|
+
var SchemaCompressor = class _SchemaCompressor {
|
|
4178
|
+
/**
|
|
4179
|
+
* Convert a full MCP Tool definition to a compact summary.
|
|
4180
|
+
*
|
|
4181
|
+
* The compact form omits `inputSchema` entirely and optionally generates
|
|
4182
|
+
* a short `parameterHint` from the schema's top-level properties.
|
|
4183
|
+
*/
|
|
4184
|
+
static toCompact(tool) {
|
|
4185
|
+
const compact = {
|
|
4186
|
+
name: tool.name,
|
|
4187
|
+
description: tool.description
|
|
4188
|
+
};
|
|
4189
|
+
if (tool.inputSchema && typeof tool.inputSchema === "object") {
|
|
4190
|
+
const schema = tool.inputSchema;
|
|
4191
|
+
if (schema.properties) {
|
|
4192
|
+
const required = new Set(schema.required ?? []);
|
|
4193
|
+
const parts = [];
|
|
4194
|
+
for (const [key, val] of Object.entries(schema.properties)) {
|
|
4195
|
+
const type = val?.type ?? "any";
|
|
4196
|
+
const enumSuffix = val?.enum && Array.isArray(val.enum) ? `: ${val.enum.map((e) => `'${e}'`).join(" | ")}` : `: ${type}`;
|
|
4197
|
+
parts.push(required.has(key) ? `${key}${enumSuffix}` : `${key}?${enumSuffix}`);
|
|
4198
|
+
}
|
|
4199
|
+
if (parts.length > 0) {
|
|
4200
|
+
compact.parameterHint = `(${parts.join(", ")})`;
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
return compact;
|
|
4205
|
+
}
|
|
4206
|
+
/**
|
|
4207
|
+
* Convert an array of tools to compact form, optionally limiting the count.
|
|
4208
|
+
*/
|
|
4209
|
+
static compactAll(tools, options) {
|
|
4210
|
+
const limited = options?.maxTools ? tools.slice(0, options.maxTools) : tools;
|
|
4211
|
+
return limited.map((t) => _SchemaCompressor.toCompact(t));
|
|
4212
|
+
}
|
|
4213
|
+
/**
|
|
4214
|
+
* Estimate token savings from using compact vs full tool schemas.
|
|
4215
|
+
*/
|
|
4216
|
+
static estimateSavings(tools) {
|
|
4217
|
+
let fullTokens = 0;
|
|
4218
|
+
let compactTokens = 0;
|
|
4219
|
+
for (const tool of tools) {
|
|
4220
|
+
fullTokens += ToolIndex.estimateTokens(tool);
|
|
4221
|
+
const compact = _SchemaCompressor.toCompact(tool);
|
|
4222
|
+
const text = [compact.name, compact.description ?? "", compact.parameterHint ?? ""].join(" ");
|
|
4223
|
+
compactTokens += Math.ceil(text.length / 4);
|
|
4224
|
+
}
|
|
4225
|
+
const saved = fullTokens - compactTokens;
|
|
4226
|
+
const pct = fullTokens > 0 ? (saved / fullTokens * 100).toFixed(1) : "0.0";
|
|
4227
|
+
return {
|
|
4228
|
+
fullTokens,
|
|
4229
|
+
compactTokens,
|
|
4230
|
+
savedTokens: saved,
|
|
4231
|
+
savingsPercent: `${pct}%`
|
|
4232
|
+
};
|
|
4233
|
+
}
|
|
4234
|
+
};
|
|
4235
|
+
|
|
4236
|
+
// src/shared/meta-tools.ts
|
|
4237
|
+
init_cjs_shims();
|
|
4238
|
+
function createSearchToolDefinition() {
|
|
4239
|
+
return {
|
|
4240
|
+
name: "mcp_search_tool_bm25",
|
|
4241
|
+
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".',
|
|
4242
|
+
inputSchema: {
|
|
4243
|
+
type: "object",
|
|
4244
|
+
properties: {
|
|
4245
|
+
query: {
|
|
4246
|
+
type: "string",
|
|
4247
|
+
description: "Natural language description of the capability you need."
|
|
4248
|
+
},
|
|
4249
|
+
limit: {
|
|
4250
|
+
type: "number",
|
|
4251
|
+
description: "Maximum number of results to return (default: 5, max: 20)."
|
|
4252
|
+
}
|
|
4253
|
+
},
|
|
4254
|
+
required: ["query"]
|
|
4255
|
+
}
|
|
4256
|
+
};
|
|
4257
|
+
}
|
|
4258
|
+
function createRegexSearchToolDefinition() {
|
|
4259
|
+
return {
|
|
4260
|
+
name: "mcp_search_tool_regex",
|
|
4261
|
+
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".',
|
|
4262
|
+
inputSchema: {
|
|
4263
|
+
type: "object",
|
|
4264
|
+
properties: {
|
|
4265
|
+
query: {
|
|
4266
|
+
type: "string",
|
|
4267
|
+
description: 'Regex pattern to search for (e.g., "^get_.*_data", "database").'
|
|
4268
|
+
},
|
|
4269
|
+
limit: {
|
|
4270
|
+
type: "number",
|
|
4271
|
+
description: "Maximum number of results to return (default: 5, max: 20)."
|
|
4272
|
+
}
|
|
4273
|
+
},
|
|
4274
|
+
required: ["query"]
|
|
4275
|
+
}
|
|
4276
|
+
};
|
|
4277
|
+
}
|
|
4278
|
+
function createGetSchemaToolDefinition() {
|
|
4279
|
+
return {
|
|
4280
|
+
name: "mcp_get_tool_schema",
|
|
4281
|
+
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.",
|
|
4282
|
+
inputSchema: {
|
|
4283
|
+
type: "object",
|
|
4284
|
+
properties: {
|
|
4285
|
+
toolName: {
|
|
4286
|
+
type: "string",
|
|
4287
|
+
description: "The exact tool name returned by mcp_search_tool_bm25."
|
|
4288
|
+
},
|
|
4289
|
+
serverName: {
|
|
4290
|
+
type: "string",
|
|
4291
|
+
description: "Optional: The server name provided in mcp_search_tool_bm25. Required if multiple tools have the same name."
|
|
4292
|
+
}
|
|
4293
|
+
},
|
|
4294
|
+
required: ["toolName"]
|
|
4295
|
+
}
|
|
4296
|
+
};
|
|
4297
|
+
}
|
|
4298
|
+
function createExecuteToolDefinition() {
|
|
4299
|
+
return {
|
|
4300
|
+
name: "mcp_execute_tool",
|
|
4301
|
+
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.",
|
|
4302
|
+
inputSchema: {
|
|
4303
|
+
type: "object",
|
|
4304
|
+
properties: {
|
|
4305
|
+
toolName: {
|
|
4306
|
+
type: "string",
|
|
4307
|
+
description: "The exact tool name from mcp_search_tool_bm25 results."
|
|
4308
|
+
},
|
|
4309
|
+
serverName: {
|
|
4310
|
+
type: "string",
|
|
4311
|
+
description: "Optional: The server name provided in mcp_search_tool_bm25. Required if multiple tools have the same name."
|
|
4312
|
+
},
|
|
4313
|
+
args: {
|
|
4314
|
+
type: "object",
|
|
4315
|
+
description: "Arguments matching the tool's inputSchema. Omit or pass {} if the tool takes no parameters.",
|
|
4316
|
+
additionalProperties: true
|
|
4317
|
+
}
|
|
4318
|
+
},
|
|
4319
|
+
required: ["toolName"]
|
|
4320
|
+
}
|
|
4321
|
+
};
|
|
4322
|
+
}
|
|
4323
|
+
async function executeMetaTool(toolName, args, router, callToolFn) {
|
|
4324
|
+
const resolveToolSchema = (name, namespace) => {
|
|
4325
|
+
try {
|
|
4326
|
+
return { tool: router.getToolSchema(name, namespace) };
|
|
4327
|
+
} catch (err) {
|
|
4328
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4329
|
+
return {
|
|
4330
|
+
error: {
|
|
4331
|
+
content: [{ type: "text", text: errorMessage }],
|
|
4332
|
+
isError: true
|
|
4333
|
+
}
|
|
4334
|
+
};
|
|
4335
|
+
}
|
|
4336
|
+
};
|
|
4337
|
+
switch (toolName) {
|
|
4338
|
+
case "mcp_search_tool_bm25": {
|
|
4339
|
+
const query = String(args.query ?? "");
|
|
4340
|
+
const limit = Math.min(Number(args.limit) || 5, 20);
|
|
4341
|
+
const results = await router.searchTools(query, limit);
|
|
4342
|
+
const text = results.length === 0 ? "No tools found matching your query. Try different keywords." : results.map(
|
|
4343
|
+
(t, i) => `${i + 1}. **${t.name}** (server: ${t.serverName})
|
|
4344
|
+
${t.description}
|
|
4345
|
+
Estimated tokens: ${t.estimatedTokens}`
|
|
4346
|
+
).join("\n");
|
|
4347
|
+
return {
|
|
4348
|
+
content: [{ type: "text", text }],
|
|
4349
|
+
isError: false
|
|
4350
|
+
};
|
|
4351
|
+
}
|
|
4352
|
+
case "mcp_search_tool_regex": {
|
|
4353
|
+
const pattern = String(args.query ?? "");
|
|
4354
|
+
const limit = Math.min(Number(args.limit) || 5, 20);
|
|
4355
|
+
const results = await router.searchToolsRegex(pattern, limit);
|
|
4356
|
+
const text = results.length === 0 ? "No tools matched your regex pattern. Try a broader pattern." : results.map(
|
|
4357
|
+
(t, i) => `${i + 1}. **${t.name}** (server: ${t.serverName})
|
|
4358
|
+
${t.description}
|
|
4359
|
+
Estimated tokens: ${t.estimatedTokens}`
|
|
4360
|
+
).join("\n");
|
|
4361
|
+
return {
|
|
4362
|
+
content: [{ type: "text", text }],
|
|
4363
|
+
isError: false
|
|
4364
|
+
};
|
|
4365
|
+
}
|
|
4366
|
+
case "mcp_get_tool_schema": {
|
|
4367
|
+
const name = String(args.toolName ?? "");
|
|
4368
|
+
const namespace = String(args.serverName ?? "") || void 0;
|
|
4369
|
+
const { tool, error } = resolveToolSchema(name, namespace);
|
|
4370
|
+
if (error) {
|
|
4371
|
+
return error;
|
|
4372
|
+
}
|
|
4373
|
+
if (!tool) {
|
|
4374
|
+
return {
|
|
4375
|
+
content: [
|
|
4376
|
+
{
|
|
4377
|
+
type: "text",
|
|
4378
|
+
text: `Tool "${name}" not found. Use mcp_search_tool_bm25 to find available tools first.`
|
|
4379
|
+
}
|
|
4380
|
+
],
|
|
4381
|
+
isError: true
|
|
4382
|
+
};
|
|
4383
|
+
}
|
|
4384
|
+
const schema = {
|
|
4385
|
+
name: tool.name,
|
|
4386
|
+
description: tool.description,
|
|
4387
|
+
inputSchema: tool.inputSchema
|
|
4388
|
+
};
|
|
4389
|
+
return {
|
|
4390
|
+
content: [{ type: "text", text: JSON.stringify(schema, null, 2) }],
|
|
4391
|
+
isError: false
|
|
4392
|
+
};
|
|
4393
|
+
}
|
|
4394
|
+
case "mcp_execute_tool": {
|
|
4395
|
+
const targetToolName = String(args.toolName ?? "");
|
|
4396
|
+
const namespace = String(args.serverName ?? "") || void 0;
|
|
4397
|
+
const toolArgs = args.args ?? {};
|
|
4398
|
+
if (!targetToolName) {
|
|
4399
|
+
return {
|
|
4400
|
+
content: [{ type: "text", text: 'Missing required parameter "toolName". Specify which tool to execute.' }],
|
|
4401
|
+
isError: true
|
|
4402
|
+
};
|
|
4403
|
+
}
|
|
4404
|
+
const { tool, error } = resolveToolSchema(targetToolName, namespace);
|
|
4405
|
+
if (error) {
|
|
4406
|
+
return error;
|
|
4407
|
+
}
|
|
4408
|
+
if (!tool) {
|
|
4409
|
+
return {
|
|
4410
|
+
content: [
|
|
4411
|
+
{
|
|
4412
|
+
type: "text",
|
|
4413
|
+
text: `Tool "${targetToolName}" not found. Use mcp_search_tool_bm25 to discover available tools first.`
|
|
4414
|
+
}
|
|
4415
|
+
],
|
|
4416
|
+
isError: true
|
|
4417
|
+
};
|
|
4418
|
+
}
|
|
4419
|
+
if (!callToolFn) {
|
|
4420
|
+
return {
|
|
4421
|
+
content: [{ type: "text", text: "Tool execution is not available. No callToolFn was configured." }],
|
|
4422
|
+
isError: true
|
|
4423
|
+
};
|
|
4424
|
+
}
|
|
4425
|
+
try {
|
|
4426
|
+
const result = await callToolFn(targetToolName, toolArgs, namespace);
|
|
4427
|
+
if (result && typeof result === "object" && "content" in result) {
|
|
4428
|
+
return result;
|
|
4429
|
+
}
|
|
4430
|
+
const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
4431
|
+
return {
|
|
4432
|
+
content: [{ type: "text", text }],
|
|
4433
|
+
isError: false
|
|
4434
|
+
};
|
|
4435
|
+
} catch (err) {
|
|
4436
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4437
|
+
return {
|
|
4438
|
+
content: [{ type: "text", text: `Tool execution failed: ${errorMessage}` }],
|
|
4439
|
+
isError: true
|
|
4440
|
+
};
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
default:
|
|
4444
|
+
return null;
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
function isMetaTool(toolName) {
|
|
4448
|
+
return toolName === "mcp_search_tool_bm25" || toolName === "mcp_search_tool_regex" || toolName === "mcp_get_tool_schema" || toolName === "mcp_execute_tool";
|
|
4449
|
+
}
|
|
4450
|
+
function resolveMetaToolProxy(toolName, args) {
|
|
4451
|
+
if (toolName === "mcp_execute_tool") {
|
|
4452
|
+
const innerName = args?.toolName;
|
|
4453
|
+
const innerArgs = args?.args;
|
|
4454
|
+
return {
|
|
4455
|
+
toolName: typeof innerName === "string" && innerName ? innerName : toolName,
|
|
4456
|
+
args: innerArgs && typeof innerArgs === "object" && !Array.isArray(innerArgs) ? innerArgs : {}
|
|
4457
|
+
};
|
|
4458
|
+
}
|
|
4459
|
+
const match = toolName.match(/(?:tool_[^_]+_)?(.+)$/);
|
|
4460
|
+
const resolvedName = match?.[1] ?? toolName;
|
|
4461
|
+
return { toolName: resolvedName, args: args ?? {} };
|
|
4462
|
+
}
|
|
4463
|
+
|
|
4464
|
+
// src/shared/tool-router.ts
|
|
4465
|
+
var ToolRouter = class {
|
|
4466
|
+
constructor(client, options = {}) {
|
|
4467
|
+
this.client = client;
|
|
4468
|
+
this.options = options;
|
|
4469
|
+
__publicField(this, "index");
|
|
4470
|
+
__publicField(this, "allTools", []);
|
|
4471
|
+
__publicField(this, "groupsMap", /* @__PURE__ */ new Map());
|
|
4472
|
+
__publicField(this, "strategy");
|
|
4473
|
+
__publicField(this, "maxTools");
|
|
4474
|
+
__publicField(this, "compactSchemas");
|
|
4475
|
+
__publicField(this, "activeGroups");
|
|
4476
|
+
__publicField(this, "customGroups");
|
|
4477
|
+
__publicField(this, "initialized", false);
|
|
4478
|
+
this.strategy = options.strategy ?? "all";
|
|
4479
|
+
this.maxTools = options.maxTools ?? 40;
|
|
4480
|
+
this.compactSchemas = options.compactSchemas ?? false;
|
|
4481
|
+
this.activeGroups = new Set(options.activeGroups ?? []);
|
|
4482
|
+
this.customGroups = options.groups;
|
|
4483
|
+
this.index = new ToolIndex({
|
|
4484
|
+
embedFn: options.embedFn,
|
|
4485
|
+
keywordWeight: options.keywordWeight
|
|
4486
|
+
});
|
|
4487
|
+
}
|
|
4488
|
+
// -----------------------------------------------------------------------
|
|
4489
|
+
// Core Public API
|
|
4490
|
+
// -----------------------------------------------------------------------
|
|
4491
|
+
/**
|
|
4492
|
+
* Get tools filtered by the current strategy.
|
|
4493
|
+
* This is the main method adapters should call.
|
|
4494
|
+
*
|
|
4495
|
+
* - `all` → returns all tools (unchanged behavior)
|
|
4496
|
+
* - `search` → returns only meta-tools (mcp_search_tool_bm25, mcp_get_tool_schema, mcp_execute_tool)
|
|
4497
|
+
* - `groups` → returns tools from active groups only
|
|
4498
|
+
*/
|
|
4499
|
+
async getFilteredTools() {
|
|
4500
|
+
await this.ensureInitialized();
|
|
4501
|
+
switch (this.strategy) {
|
|
4502
|
+
case "search":
|
|
4503
|
+
return this.getMetaToolDefinitions();
|
|
4504
|
+
case "groups":
|
|
4505
|
+
return this.getGroupFilteredTools();
|
|
4506
|
+
case "all":
|
|
4507
|
+
default:
|
|
4508
|
+
if (this.compactSchemas) {
|
|
4509
|
+
return this.allTools.map((t) => {
|
|
4510
|
+
const compact = SchemaCompressor.toCompact(t);
|
|
4511
|
+
return {
|
|
4512
|
+
name: compact.name,
|
|
4513
|
+
description: (compact.description ?? "") + (compact.parameterHint ? ` Parameters: ${compact.parameterHint}` : ""),
|
|
4514
|
+
inputSchema: { type: "object", properties: {} }
|
|
4515
|
+
};
|
|
4516
|
+
});
|
|
4517
|
+
}
|
|
4518
|
+
return [...this.allTools];
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
/**
|
|
4522
|
+
* Search tools by natural-language query.
|
|
4523
|
+
* Works regardless of strategy.
|
|
4524
|
+
*/
|
|
4525
|
+
async searchTools(query, topK) {
|
|
4526
|
+
await this.ensureInitialized();
|
|
4527
|
+
return this.index.search(query, topK ?? this.maxTools);
|
|
4528
|
+
}
|
|
4529
|
+
/**
|
|
4530
|
+
* Search tools by regex pattern.
|
|
4531
|
+
* Matches against name, description, and parameter metadata.
|
|
4532
|
+
*/
|
|
4533
|
+
async searchToolsRegex(pattern, topK) {
|
|
4534
|
+
await this.ensureInitialized();
|
|
4535
|
+
return this.index.searchRegex(pattern, topK ?? this.maxTools);
|
|
4536
|
+
}
|
|
4537
|
+
/**
|
|
4538
|
+
* Get the full tool definition by name.
|
|
4539
|
+
* If tool name is ambiguous, use namespace to specify the server.
|
|
4540
|
+
*/
|
|
4541
|
+
getToolSchema(toolName, namespace) {
|
|
4542
|
+
const matches = this.index.getTool(toolName, namespace);
|
|
4543
|
+
if (matches.length === 0) return void 0;
|
|
4544
|
+
if (matches.length > 1) {
|
|
4545
|
+
const servers = matches.map((m) => m.serverName).join(", ");
|
|
4546
|
+
throw new Error(
|
|
4547
|
+
`Tool "${toolName}" is provided by multiple servers: [${servers}]. Please specify the desired "serverName" as a namespace.`
|
|
4548
|
+
);
|
|
4549
|
+
}
|
|
4550
|
+
return matches[0];
|
|
4551
|
+
}
|
|
4552
|
+
/**
|
|
4553
|
+
* Get compact (schema-less) summaries for all tools.
|
|
4554
|
+
*/
|
|
4555
|
+
getCompactTools() {
|
|
4556
|
+
return SchemaCompressor.compactAll(this.allTools);
|
|
4557
|
+
}
|
|
4558
|
+
// -----------------------------------------------------------------------
|
|
4559
|
+
// Group Management
|
|
4560
|
+
// -----------------------------------------------------------------------
|
|
4561
|
+
/** Get all available groups with their tool lists and active status. */
|
|
4562
|
+
getGroups() {
|
|
4563
|
+
return new Map(this.groupsMap);
|
|
4564
|
+
}
|
|
4565
|
+
/** Activate specific groups. Pass empty array to activate all. */
|
|
4566
|
+
setActiveGroups(groups) {
|
|
4567
|
+
this.activeGroups = new Set(groups);
|
|
4568
|
+
for (const [name, info] of this.groupsMap) {
|
|
4569
|
+
info.active = this.activeGroups.size === 0 || this.activeGroups.has(name);
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
/** Get the names of currently active groups. */
|
|
4573
|
+
getActiveGroups() {
|
|
4574
|
+
return [...this.activeGroups];
|
|
4575
|
+
}
|
|
4576
|
+
// -----------------------------------------------------------------------
|
|
4577
|
+
// Stats & Introspection
|
|
4578
|
+
// -----------------------------------------------------------------------
|
|
4579
|
+
/** Total token cost of all tools if loaded without filtering. */
|
|
4580
|
+
getTotalTokenCost() {
|
|
4581
|
+
return this.index.getTotalTokenCost();
|
|
4582
|
+
}
|
|
4583
|
+
/** Estimate token cost of the currently filtered tool set. */
|
|
4584
|
+
async getFilteredTokenCost() {
|
|
4585
|
+
const tools = await this.getFilteredTools();
|
|
4586
|
+
let total = 0;
|
|
4587
|
+
for (const tool of tools) {
|
|
4588
|
+
total += ToolIndex.estimateTokens(tool);
|
|
4589
|
+
}
|
|
4590
|
+
return total;
|
|
4591
|
+
}
|
|
4592
|
+
/** Get compression stats showing savings from current strategy. */
|
|
4593
|
+
getCompressionStats() {
|
|
4594
|
+
return SchemaCompressor.estimateSavings(this.allTools);
|
|
4595
|
+
}
|
|
4596
|
+
/** Number of total indexed tools. */
|
|
4597
|
+
get totalToolCount() {
|
|
4598
|
+
return this.allTools.length;
|
|
4599
|
+
}
|
|
4600
|
+
/** Change strategy at runtime. */
|
|
4601
|
+
setStrategy(strategy) {
|
|
4602
|
+
this.strategy = strategy;
|
|
4603
|
+
}
|
|
4604
|
+
/**
|
|
4605
|
+
* Force a re-index of tools from all connected clients.
|
|
4606
|
+
* Call this after adding/removing MCP server connections.
|
|
4607
|
+
*/
|
|
4608
|
+
async refresh() {
|
|
4609
|
+
this.initialized = false;
|
|
4610
|
+
await this.ensureInitialized();
|
|
4611
|
+
}
|
|
4612
|
+
/**
|
|
4613
|
+
* Execute a tool by routing to the correct MCP client.
|
|
4614
|
+
* Used by the `mcp_execute_tool` meta-tool to proxy tool calls.
|
|
4615
|
+
*/
|
|
4616
|
+
async callTool(toolName, args, namespace) {
|
|
4617
|
+
await this.ensureInitialized();
|
|
4618
|
+
const indexedTool = this.getToolSchema(toolName, namespace);
|
|
4619
|
+
if (!indexedTool) {
|
|
4620
|
+
throw new Error(
|
|
4621
|
+
`Tool "${toolName}" not found${namespace ? ` on server "${namespace}"` : ""}. Use mcp_search_tool_bm25 or mcp_search_tool_regex to discover available tools.`
|
|
4622
|
+
);
|
|
4623
|
+
}
|
|
4624
|
+
const clients = this.getClients();
|
|
4625
|
+
const targetClient = clients.find(
|
|
4626
|
+
(c) => typeof c.getSessionId === "function" && c.getSessionId() === indexedTool.sessionId
|
|
4627
|
+
) ?? clients.find((c) => c.isConnected());
|
|
4628
|
+
if (!targetClient) {
|
|
4629
|
+
throw new Error(`No connected client found for tool "${toolName}"`);
|
|
4630
|
+
}
|
|
4631
|
+
return await targetClient.callTool(toolName, args);
|
|
4632
|
+
}
|
|
4633
|
+
// -----------------------------------------------------------------------
|
|
4634
|
+
// Internals
|
|
4635
|
+
// -----------------------------------------------------------------------
|
|
4636
|
+
/** Lazy initialization — fetches tools from all connected clients. */
|
|
4637
|
+
async ensureInitialized() {
|
|
4638
|
+
if (this.initialized) return;
|
|
4639
|
+
this.allTools = await this.fetchAllTools();
|
|
4640
|
+
await this.index.buildIndex(this.allTools);
|
|
4641
|
+
this.buildGroups();
|
|
4642
|
+
this.initialized = true;
|
|
4643
|
+
}
|
|
4644
|
+
/** Fetch tools from all connected MCP clients. */
|
|
4645
|
+
async fetchAllTools() {
|
|
4646
|
+
const clients = this.getClients();
|
|
4647
|
+
const result = [];
|
|
4648
|
+
for (const client of clients) {
|
|
4649
|
+
if (!client.isConnected()) continue;
|
|
4650
|
+
try {
|
|
4651
|
+
const { tools } = await client.listTools();
|
|
4652
|
+
const serverId = typeof client.getServerId === "function" ? client.getServerId() ?? "unknown" : "unknown";
|
|
4653
|
+
const serverName = (typeof client.getServerName === "function" ? client.getServerName() : void 0) ?? serverId;
|
|
4654
|
+
const sessionId = typeof client.getSessionId === "function" ? client.getSessionId() ?? "unknown" : "unknown";
|
|
4655
|
+
for (const tool of tools) {
|
|
4656
|
+
result.push({
|
|
4657
|
+
...tool,
|
|
4658
|
+
serverName,
|
|
4659
|
+
sessionId
|
|
4660
|
+
});
|
|
4661
|
+
}
|
|
4662
|
+
} catch (err) {
|
|
4663
|
+
console.warn("[ToolRouter] Failed to fetch tools from client:", err);
|
|
4664
|
+
}
|
|
4665
|
+
}
|
|
4666
|
+
return result;
|
|
4667
|
+
}
|
|
4668
|
+
/** Resolve the client input to a flat array of ToolClient instances. */
|
|
4669
|
+
getClients() {
|
|
4670
|
+
if (Array.isArray(this.client)) {
|
|
4671
|
+
return this.client;
|
|
4672
|
+
}
|
|
4673
|
+
if (typeof this.client.getClients === "function") {
|
|
4674
|
+
return this.client.getClients();
|
|
4675
|
+
}
|
|
4676
|
+
return [this.client];
|
|
4677
|
+
}
|
|
4678
|
+
/** Build group map from custom config or auto-detect from server names. */
|
|
4679
|
+
buildGroups() {
|
|
4680
|
+
this.groupsMap.clear();
|
|
4681
|
+
if (this.customGroups) {
|
|
4682
|
+
for (const [name, tools] of Object.entries(this.customGroups)) {
|
|
4683
|
+
this.groupsMap.set(name, {
|
|
4684
|
+
tools,
|
|
4685
|
+
active: this.activeGroups.size === 0 || this.activeGroups.has(name)
|
|
4686
|
+
});
|
|
4687
|
+
}
|
|
4688
|
+
} else {
|
|
4689
|
+
const serverTools = /* @__PURE__ */ new Map();
|
|
4690
|
+
for (const tool of this.allTools) {
|
|
4691
|
+
const group = tool.serverName;
|
|
4692
|
+
if (!serverTools.has(group)) {
|
|
4693
|
+
serverTools.set(group, []);
|
|
4694
|
+
}
|
|
4695
|
+
serverTools.get(group).push(tool.name);
|
|
4696
|
+
}
|
|
4697
|
+
for (const [serverName, tools] of serverTools) {
|
|
4698
|
+
this.groupsMap.set(serverName, {
|
|
4699
|
+
tools,
|
|
4700
|
+
active: this.activeGroups.size === 0 || this.activeGroups.has(serverName)
|
|
4701
|
+
});
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
/** Return only tools belonging to currently active groups. */
|
|
4706
|
+
getGroupFilteredTools() {
|
|
4707
|
+
const activeToolNames = /* @__PURE__ */ new Set();
|
|
4708
|
+
for (const [, info] of this.groupsMap) {
|
|
4709
|
+
if (info.active) {
|
|
4710
|
+
for (const name of info.tools) {
|
|
4711
|
+
activeToolNames.add(name);
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
const filtered = this.allTools.filter((t) => activeToolNames.has(t.name));
|
|
4716
|
+
if (this.compactSchemas) {
|
|
4717
|
+
return filtered.slice(0, this.maxTools).map((t) => {
|
|
4718
|
+
const compact = SchemaCompressor.toCompact(t);
|
|
4719
|
+
return {
|
|
4720
|
+
name: compact.name,
|
|
4721
|
+
description: (compact.description ?? "") + (compact.parameterHint ? ` Parameters: ${compact.parameterHint}` : ""),
|
|
4722
|
+
inputSchema: { type: "object", properties: {} }
|
|
4723
|
+
};
|
|
4724
|
+
});
|
|
4725
|
+
}
|
|
4726
|
+
return filtered.slice(0, this.maxTools);
|
|
4727
|
+
}
|
|
4728
|
+
/** The 4 meta-tool definitions exposed in `search` strategy. */
|
|
4729
|
+
getMetaToolDefinitions() {
|
|
4730
|
+
return [
|
|
4731
|
+
createSearchToolDefinition(),
|
|
4732
|
+
createRegexSearchToolDefinition(),
|
|
4733
|
+
createGetSchemaToolDefinition(),
|
|
4734
|
+
createExecuteToolDefinition()
|
|
4735
|
+
];
|
|
4736
|
+
}
|
|
4737
|
+
};
|
|
4738
|
+
|
|
4739
|
+
exports.APP_HOST_DEFAULTS = APP_HOST_DEFAULTS;
|
|
3692
4740
|
exports.AppHost = AppHost;
|
|
3693
4741
|
exports.AuthenticationError = AuthenticationError;
|
|
3694
4742
|
exports.ConfigurationError = ConfigurationError;
|
|
@@ -3697,6 +4745,7 @@ exports.DEFAULT_CLIENT_NAME = DEFAULT_CLIENT_NAME;
|
|
|
3697
4745
|
exports.DEFAULT_CLIENT_URI = DEFAULT_CLIENT_URI;
|
|
3698
4746
|
exports.DEFAULT_HEARTBEAT_INTERVAL_MS = DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
3699
4747
|
exports.DEFAULT_LOGO_URI = DEFAULT_LOGO_URI;
|
|
4748
|
+
exports.DEFAULT_MCP_APP_CSP = DEFAULT_MCP_APP_CSP;
|
|
3700
4749
|
exports.DEFAULT_POLICY_URI = DEFAULT_POLICY_URI;
|
|
3701
4750
|
exports.DisposableStore = DisposableStore;
|
|
3702
4751
|
exports.Emitter = Emitter;
|
|
@@ -3709,20 +4758,30 @@ exports.MultiSessionClient = MultiSessionClient;
|
|
|
3709
4758
|
exports.NotConnectedError = NotConnectedError;
|
|
3710
4759
|
exports.REDIS_KEY_PREFIX = REDIS_KEY_PREFIX;
|
|
3711
4760
|
exports.RpcErrorCodes = RpcErrorCodes;
|
|
4761
|
+
exports.SANDBOX_PROXY_READY_METHOD = SANDBOX_PROXY_READY_METHOD;
|
|
4762
|
+
exports.SANDBOX_RESOURCE_READY_METHOD = SANDBOX_RESOURCE_READY_METHOD;
|
|
3712
4763
|
exports.SESSION_TTL_SECONDS = SESSION_TTL_SECONDS;
|
|
3713
4764
|
exports.SOFTWARE_ID = SOFTWARE_ID;
|
|
3714
4765
|
exports.SOFTWARE_VERSION = SOFTWARE_VERSION;
|
|
3715
4766
|
exports.SSEClient = SSEClient;
|
|
3716
4767
|
exports.SSEConnectionManager = SSEConnectionManager;
|
|
3717
4768
|
exports.STATE_EXPIRATION_MS = STATE_EXPIRATION_MS;
|
|
4769
|
+
exports.SchemaCompressor = SchemaCompressor;
|
|
3718
4770
|
exports.SessionNotFoundError = SessionNotFoundError;
|
|
3719
4771
|
exports.SessionValidationError = SessionValidationError;
|
|
3720
4772
|
exports.StorageOAuthClientProvider = StorageOAuthClientProvider;
|
|
3721
4773
|
exports.TOKEN_EXPIRY_BUFFER_MS = TOKEN_EXPIRY_BUFFER_MS;
|
|
3722
4774
|
exports.ToolExecutionError = ToolExecutionError;
|
|
4775
|
+
exports.ToolIndex = ToolIndex;
|
|
4776
|
+
exports.ToolRouter = ToolRouter;
|
|
3723
4777
|
exports.UnauthorizedError = UnauthorizedError;
|
|
4778
|
+
exports.createExecuteToolDefinition = createExecuteToolDefinition;
|
|
4779
|
+
exports.createGetSchemaToolDefinition = createGetSchemaToolDefinition;
|
|
3724
4780
|
exports.createNextMcpHandler = createNextMcpHandler;
|
|
4781
|
+
exports.createRegexSearchToolDefinition = createRegexSearchToolDefinition;
|
|
3725
4782
|
exports.createSSEHandler = createSSEHandler;
|
|
4783
|
+
exports.createSearchToolDefinition = createSearchToolDefinition;
|
|
4784
|
+
exports.executeMetaTool = executeMetaTool;
|
|
3726
4785
|
exports.findToolByName = findToolByName;
|
|
3727
4786
|
exports.getToolUiResourceUri = getToolUiResourceUri;
|
|
3728
4787
|
exports.isCallToolSuccess = isCallToolSuccess;
|
|
@@ -3730,6 +4789,8 @@ exports.isConnectAuthRequired = isConnectAuthRequired;
|
|
|
3730
4789
|
exports.isConnectError = isConnectError;
|
|
3731
4790
|
exports.isConnectSuccess = isConnectSuccess;
|
|
3732
4791
|
exports.isListToolsSuccess = isListToolsSuccess;
|
|
4792
|
+
exports.isMetaTool = isMetaTool;
|
|
4793
|
+
exports.resolveMetaToolProxy = resolveMetaToolProxy;
|
|
3733
4794
|
exports.sanitizeServerLabel = sanitizeServerLabel;
|
|
3734
4795
|
exports.storage = storage;
|
|
3735
4796
|
//# sourceMappingURL=index.js.map
|