@mcp-ts/sdk 1.4.0 → 1.5.1
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 +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/react.d.mts +94 -8
- package/dist/client/react.d.ts +94 -8
- package/dist/client/react.js +364 -26
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +358 -27
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +4 -4
- package/dist/client/vue.d.ts +4 -4
- package/dist/client/vue.js +11 -2
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +11 -2
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{index-CQr9q0bF.d.mts → index-DcYfpY3H.d.mts} +1 -1
- package/dist/{index-nE_7Io0I.d.ts → index-GfC_eNEv.d.ts} +1 -1
- package/dist/index.d.mts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +938 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +923 -13
- 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 +58 -12
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +58 -12
- 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 +3 -2
- package/src/adapters/agui-adapter.ts +79 -0
- package/src/adapters/ai-adapter.ts +75 -0
- package/src/adapters/langchain-adapter.ts +74 -0
- package/src/client/react/index.ts +16 -0
- package/src/client/react/oauth-popup.tsx +446 -0
- package/src/client/react/use-mcp-apps.tsx +50 -32
- package/src/client/react/use-mcp.ts +36 -3
- package/src/client/vue/use-mcp.ts +38 -3
- package/src/server/handlers/sse-handler.ts +39 -0
- package/src/server/index.ts +2 -0
- package/src/server/mcp/oauth-client.ts +35 -15
- 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/supabase/migrations/20260421010000_add_session_cleanup_cron.sql +32 -0
package/dist/index.js
CHANGED
|
@@ -1785,13 +1785,8 @@ var MCPClient = class _MCPClient {
|
|
|
1785
1785
|
this.transportType = transportType;
|
|
1786
1786
|
this.emitStateChange("CONNECTED");
|
|
1787
1787
|
this.emitProgress("Connected successfully");
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
const needsTtlPromotion = !existingSession || existingSession.active !== true;
|
|
1791
|
-
if (needsTransportUpdate || needsTtlPromotion) {
|
|
1792
|
-
console.log(`[MCPClient] Saving session ${this.sessionId} with 12hr TTL (connect success)`);
|
|
1793
|
-
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
1794
|
-
}
|
|
1788
|
+
console.log(`[MCPClient] Saving session ${this.sessionId} with 12hr TTL (connect success)`);
|
|
1789
|
+
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
1795
1790
|
} catch (error) {
|
|
1796
1791
|
if (error instanceof auth_js.UnauthorizedError || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
|
|
1797
1792
|
let authUrl = "";
|
|
@@ -1829,6 +1824,13 @@ var MCPClient = class _MCPClient {
|
|
|
1829
1824
|
const errorMessage = error instanceof Error ? error.message : "Connection failed";
|
|
1830
1825
|
this.emitError(errorMessage, "connection");
|
|
1831
1826
|
this.emitStateChange("FAILED");
|
|
1827
|
+
try {
|
|
1828
|
+
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
1829
|
+
if (!existingSession || existingSession.active !== true) {
|
|
1830
|
+
await storage.removeSession(this.identity, this.sessionId);
|
|
1831
|
+
}
|
|
1832
|
+
} catch {
|
|
1833
|
+
}
|
|
1832
1834
|
throw error;
|
|
1833
1835
|
}
|
|
1834
1836
|
}
|
|
@@ -1852,6 +1854,7 @@ var MCPClient = class _MCPClient {
|
|
|
1852
1854
|
const transportsToTry = this.transportType ? [this.transportType] : ["streamable_http", "sse"];
|
|
1853
1855
|
let lastError;
|
|
1854
1856
|
let tokensExchanged = false;
|
|
1857
|
+
let authenticatedStateEmitted = false;
|
|
1855
1858
|
for (const currentType of transportsToTry) {
|
|
1856
1859
|
const isLastAttempt = currentType === transportsToTry[transportsToTry.length - 1];
|
|
1857
1860
|
try {
|
|
@@ -1863,8 +1866,10 @@ var MCPClient = class _MCPClient {
|
|
|
1863
1866
|
} else {
|
|
1864
1867
|
this.emitProgress(`Tokens already exchanged, skipping auth step for ${currentType}...`);
|
|
1865
1868
|
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1869
|
+
if (!authenticatedStateEmitted) {
|
|
1870
|
+
this.emitStateChange("AUTHENTICATED");
|
|
1871
|
+
authenticatedStateEmitted = true;
|
|
1872
|
+
}
|
|
1868
1873
|
this.emitProgress("Creating authenticated client...");
|
|
1869
1874
|
this.client = new index_js.Client(
|
|
1870
1875
|
{
|
|
@@ -1883,6 +1888,7 @@ var MCPClient = class _MCPClient {
|
|
|
1883
1888
|
);
|
|
1884
1889
|
this.emitStateChange("CONNECTING");
|
|
1885
1890
|
await this.client.connect(this.transport);
|
|
1891
|
+
this.transportType = currentType;
|
|
1886
1892
|
this.emitStateChange("CONNECTED");
|
|
1887
1893
|
console.log(`[MCPClient] Updating session ${this.sessionId} to 12hr TTL (OAuth complete)`);
|
|
1888
1894
|
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
@@ -2264,7 +2270,9 @@ var MCPClient = class _MCPClient {
|
|
|
2264
2270
|
* @returns Server name or undefined
|
|
2265
2271
|
*/
|
|
2266
2272
|
getServerName() {
|
|
2267
|
-
|
|
2273
|
+
const info = this.client?.getServerVersion();
|
|
2274
|
+
console.log("server info ->", info);
|
|
2275
|
+
return info?.title ?? info?.name ?? this.serverName;
|
|
2268
2276
|
}
|
|
2269
2277
|
/**
|
|
2270
2278
|
* Gets the server ID
|
|
@@ -2752,9 +2760,23 @@ var SSEConnectionManager = class {
|
|
|
2752
2760
|
if (existing) {
|
|
2753
2761
|
return existing;
|
|
2754
2762
|
}
|
|
2763
|
+
const session = await storage.getSession(this.identity, sessionId);
|
|
2764
|
+
if (!session) {
|
|
2765
|
+
throw new Error("Session not found");
|
|
2766
|
+
}
|
|
2755
2767
|
const client = new MCPClient({
|
|
2756
2768
|
identity: this.identity,
|
|
2757
|
-
sessionId
|
|
2769
|
+
sessionId,
|
|
2770
|
+
// These fields are optional in MCPClient, but when rehydrating a known
|
|
2771
|
+
// stored session on the server we pass them explicitly to preserve the
|
|
2772
|
+
// original transport/connection metadata instead of relying on lazy
|
|
2773
|
+
// reloading during initialize().
|
|
2774
|
+
serverId: session.serverId,
|
|
2775
|
+
serverName: session.serverName,
|
|
2776
|
+
serverUrl: session.serverUrl,
|
|
2777
|
+
callbackUrl: session.callbackUrl,
|
|
2778
|
+
transportType: session.transportType,
|
|
2779
|
+
headers: session.headers
|
|
2758
2780
|
});
|
|
2759
2781
|
client.onConnectionEvent((event) => this.emitConnectionEvent(event));
|
|
2760
2782
|
client.onObservabilityEvent((event) => this.sendEvent(event));
|
|
@@ -2811,6 +2833,16 @@ var SSEConnectionManager = class {
|
|
|
2811
2833
|
const client = new MCPClient({
|
|
2812
2834
|
identity: this.identity,
|
|
2813
2835
|
sessionId,
|
|
2836
|
+
// These fields are optional in MCPClient, but when rehydrating a known
|
|
2837
|
+
// stored session on the server we pass them explicitly to preserve the
|
|
2838
|
+
// original transport/connection metadata instead of relying on lazy
|
|
2839
|
+
// reloading during initialize().
|
|
2840
|
+
serverId: session.serverId,
|
|
2841
|
+
serverName: session.serverName,
|
|
2842
|
+
serverUrl: session.serverUrl,
|
|
2843
|
+
callbackUrl: session.callbackUrl,
|
|
2844
|
+
transportType: session.transportType,
|
|
2845
|
+
headers: session.headers,
|
|
2814
2846
|
...clientMetadata
|
|
2815
2847
|
});
|
|
2816
2848
|
client.onConnectionEvent((event) => this.emitConnectionEvent(event));
|
|
@@ -2843,7 +2875,21 @@ var SSEConnectionManager = class {
|
|
|
2843
2875
|
try {
|
|
2844
2876
|
const client = new MCPClient({
|
|
2845
2877
|
identity: this.identity,
|
|
2846
|
-
sessionId
|
|
2878
|
+
sessionId,
|
|
2879
|
+
// These fields are optional in MCPClient, but when rehydrating a known
|
|
2880
|
+
// stored session on the server we pass them explicitly to preserve the
|
|
2881
|
+
// original connection metadata instead of relying on lazy
|
|
2882
|
+
// reloading during initialize().
|
|
2883
|
+
serverId: session.serverId,
|
|
2884
|
+
serverName: session.serverName,
|
|
2885
|
+
serverUrl: session.serverUrl,
|
|
2886
|
+
callbackUrl: session.callbackUrl,
|
|
2887
|
+
// NOTE: transportType is intentionally omitted here.
|
|
2888
|
+
// The session's stored transportType is a placeholder ('streamable_http')
|
|
2889
|
+
// set before transport negotiation. Omitting it lets MCPClient auto-negotiate
|
|
2890
|
+
// (try streamable_http → SSE fallback), which is critical for servers like
|
|
2891
|
+
// Neon that only support SSE transport.
|
|
2892
|
+
headers: session.headers
|
|
2847
2893
|
});
|
|
2848
2894
|
client.onConnectionEvent((event) => this.emitConnectionEvent(event));
|
|
2849
2895
|
await client.finishAuth(code);
|
|
@@ -3864,6 +3910,876 @@ function findToolByName(connections, toolName) {
|
|
|
3864
3910
|
return void 0;
|
|
3865
3911
|
}
|
|
3866
3912
|
|
|
3913
|
+
// src/shared/tool-router.ts
|
|
3914
|
+
init_cjs_shims();
|
|
3915
|
+
|
|
3916
|
+
// src/shared/tool-index.ts
|
|
3917
|
+
init_cjs_shims();
|
|
3918
|
+
var CALIBRATION_DIVISOR = 3.6;
|
|
3919
|
+
function classifyChar(ch) {
|
|
3920
|
+
const code = ch.charCodeAt(0);
|
|
3921
|
+
if (code <= 32 || ch === "{" || ch === "}" || ch === "[" || ch === "]" || ch === ":" || ch === ",") return 1;
|
|
3922
|
+
if (code >= 33 && code <= 47) return 1.5;
|
|
3923
|
+
if (code >= 48 && code <= 57) return 2;
|
|
3924
|
+
if (code >= 65 && code <= 90) return 3.5;
|
|
3925
|
+
if (code >= 97 && code <= 122) return 4;
|
|
3926
|
+
return 2.5;
|
|
3927
|
+
}
|
|
3928
|
+
var ToolIndex = class _ToolIndex {
|
|
3929
|
+
constructor(options = {}) {
|
|
3930
|
+
/** All indexed tools keyed by name (supports duplicates). */
|
|
3931
|
+
__publicField(this, "tools", /* @__PURE__ */ new Map());
|
|
3932
|
+
/** Precomputed lightweight summaries keyed by document. */
|
|
3933
|
+
__publicField(this, "toolSummaries", /* @__PURE__ */ new Map());
|
|
3934
|
+
/** Pre-computed search text for keyword matching (lowercase), keyed by document. */
|
|
3935
|
+
__publicField(this, "searchTexts", /* @__PURE__ */ new Map());
|
|
3936
|
+
/** Pre-computed IDF values per token (computed once on build). */
|
|
3937
|
+
__publicField(this, "idf", /* @__PURE__ */ new Map());
|
|
3938
|
+
/** Per-tool TF vectors (Map<token, tf>). */
|
|
3939
|
+
__publicField(this, "tfVectors", /* @__PURE__ */ new Map());
|
|
3940
|
+
/** Optional: pre-computed embedding vectors per tool. */
|
|
3941
|
+
__publicField(this, "embeddings", /* @__PURE__ */ new Map());
|
|
3942
|
+
/** BM25: document lengths in tokens for each tool. */
|
|
3943
|
+
__publicField(this, "docLengths", /* @__PURE__ */ new Map());
|
|
3944
|
+
/** BM25: average document length across the entire index. */
|
|
3945
|
+
__publicField(this, "avgDocLength", 0);
|
|
3946
|
+
/** Cached total estimated token cost across all indexed tools. */
|
|
3947
|
+
__publicField(this, "totalTokenCost", 0);
|
|
3948
|
+
__publicField(this, "options");
|
|
3949
|
+
this.options = {
|
|
3950
|
+
embedFn: options.embedFn ?? void 0,
|
|
3951
|
+
keywordWeight: options.keywordWeight ?? 0.4
|
|
3952
|
+
};
|
|
3953
|
+
}
|
|
3954
|
+
// -----------------------------------------------------------------------
|
|
3955
|
+
// Indexing
|
|
3956
|
+
// -----------------------------------------------------------------------
|
|
3957
|
+
/**
|
|
3958
|
+
* Build (or rebuild) the index from the given tool set.
|
|
3959
|
+
* Call this after connecting / reconnecting to MCP servers.
|
|
3960
|
+
*/
|
|
3961
|
+
async buildIndex(tools) {
|
|
3962
|
+
this.tools.clear();
|
|
3963
|
+
this.toolSummaries.clear();
|
|
3964
|
+
this.searchTexts.clear();
|
|
3965
|
+
this.idf.clear();
|
|
3966
|
+
this.tfVectors.clear();
|
|
3967
|
+
this.embeddings.clear();
|
|
3968
|
+
this.docLengths.clear();
|
|
3969
|
+
this.avgDocLength = 0;
|
|
3970
|
+
this.totalTokenCost = 0;
|
|
3971
|
+
const allTokenSets = /* @__PURE__ */ new Map();
|
|
3972
|
+
let totalLength = 0;
|
|
3973
|
+
for (const tool of tools) {
|
|
3974
|
+
const docKey = this.getDocumentKey(tool);
|
|
3975
|
+
if (!this.tools.has(tool.name)) {
|
|
3976
|
+
this.tools.set(tool.name, []);
|
|
3977
|
+
}
|
|
3978
|
+
this.tools.get(tool.name).push(tool);
|
|
3979
|
+
const estimatedTokens = _ToolIndex.estimateTokens(tool);
|
|
3980
|
+
this.toolSummaries.set(docKey, {
|
|
3981
|
+
name: tool.name,
|
|
3982
|
+
description: tool.description ?? "",
|
|
3983
|
+
serverName: tool.serverName,
|
|
3984
|
+
sessionId: tool.sessionId,
|
|
3985
|
+
estimatedTokens
|
|
3986
|
+
});
|
|
3987
|
+
this.totalTokenCost += estimatedTokens;
|
|
3988
|
+
const text = this.buildSearchableText(tool).toLowerCase();
|
|
3989
|
+
this.searchTexts.set(docKey, text);
|
|
3990
|
+
const tokens = this.tokenize(text);
|
|
3991
|
+
const tf = /* @__PURE__ */ new Map();
|
|
3992
|
+
const uniqueTokens = /* @__PURE__ */ new Set();
|
|
3993
|
+
for (const tok of tokens) {
|
|
3994
|
+
tf.set(tok, (tf.get(tok) ?? 0) + 1);
|
|
3995
|
+
uniqueTokens.add(tok);
|
|
3996
|
+
}
|
|
3997
|
+
const maxTf = Math.max(...tf.values(), 1);
|
|
3998
|
+
for (const [k, v] of tf) {
|
|
3999
|
+
tf.set(k, v / maxTf);
|
|
4000
|
+
}
|
|
4001
|
+
this.tfVectors.set(docKey, tf);
|
|
4002
|
+
allTokenSets.set(docKey, uniqueTokens);
|
|
4003
|
+
const length = tokens.length;
|
|
4004
|
+
this.docLengths.set(docKey, length);
|
|
4005
|
+
totalLength += length;
|
|
4006
|
+
}
|
|
4007
|
+
this.avgDocLength = totalLength / (tools.length || 1);
|
|
4008
|
+
const totalDocs = tools.length || 1;
|
|
4009
|
+
const dfCounts = /* @__PURE__ */ new Map();
|
|
4010
|
+
for (const tokenSet of allTokenSets.values()) {
|
|
4011
|
+
for (const tok of tokenSet) {
|
|
4012
|
+
dfCounts.set(tok, (dfCounts.get(tok) ?? 0) + 1);
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
for (const [tok, df] of dfCounts) {
|
|
4016
|
+
this.idf.set(tok, Math.log(totalDocs / df) + 1);
|
|
4017
|
+
}
|
|
4018
|
+
if (this.options.embedFn) {
|
|
4019
|
+
const names = [...this.searchTexts.keys()];
|
|
4020
|
+
const texts = names.map((n) => this.searchTexts.get(n));
|
|
4021
|
+
try {
|
|
4022
|
+
const vectors = await this.options.embedFn(texts);
|
|
4023
|
+
for (let i = 0; i < names.length; i++) {
|
|
4024
|
+
if (vectors[i]) {
|
|
4025
|
+
this.embeddings.set(names[i], vectors[i]);
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
} catch (err) {
|
|
4029
|
+
console.warn("[ToolIndex] Embedding generation failed, falling back to keyword-only search:", err);
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
// -----------------------------------------------------------------------
|
|
4034
|
+
// Search
|
|
4035
|
+
// -----------------------------------------------------------------------
|
|
4036
|
+
/**
|
|
4037
|
+
* Search the index and return the top-K most relevant tools.
|
|
4038
|
+
*
|
|
4039
|
+
* When an `embedFn` is configured the final score is a weighted blend of
|
|
4040
|
+
* keyword TF-IDF similarity and embedding cosine-similarity:
|
|
4041
|
+
*
|
|
4042
|
+
* `score = keywordWeight × keyword_score + (1 - keywordWeight) × cosine_score`
|
|
4043
|
+
*/
|
|
4044
|
+
async search(query, topK = 5) {
|
|
4045
|
+
if (this.tools.size === 0) return [];
|
|
4046
|
+
const queryLower = query.toLowerCase();
|
|
4047
|
+
const queryTokens = this.tokenize(queryLower);
|
|
4048
|
+
const keywordScores = /* @__PURE__ */ new Map();
|
|
4049
|
+
const k1 = 1.2;
|
|
4050
|
+
const b = 0.75;
|
|
4051
|
+
for (const [docKey, docTf] of this.tfVectors) {
|
|
4052
|
+
let score = 0;
|
|
4053
|
+
const docLen = this.docLengths.get(docKey) ?? 0;
|
|
4054
|
+
for (const tok of queryTokens) {
|
|
4055
|
+
const tfVal = docTf.get(tok) ?? 0;
|
|
4056
|
+
if (tfVal === 0) continue;
|
|
4057
|
+
const idf = this.idf.get(tok) ?? 0;
|
|
4058
|
+
const numerator = tfVal * (k1 + 1);
|
|
4059
|
+
const denominator = tfVal + k1 * (1 - b + b * (docLen / this.avgDocLength));
|
|
4060
|
+
score += idf * (numerator / denominator);
|
|
4061
|
+
}
|
|
4062
|
+
keywordScores.set(docKey, score);
|
|
4063
|
+
}
|
|
4064
|
+
let embeddingScores = null;
|
|
4065
|
+
if (this.options.embedFn && this.embeddings.size > 0) {
|
|
4066
|
+
try {
|
|
4067
|
+
const [queryEmbedding] = await this.options.embedFn([queryLower]);
|
|
4068
|
+
if (queryEmbedding) {
|
|
4069
|
+
embeddingScores = /* @__PURE__ */ new Map();
|
|
4070
|
+
for (const [docKey, vec] of this.embeddings) {
|
|
4071
|
+
embeddingScores.set(docKey, this.cosineSimilarity(queryEmbedding, vec));
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
} catch {
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
const kw = this.options.keywordWeight;
|
|
4078
|
+
const finalScores = [];
|
|
4079
|
+
for (const docKey of this.toolSummaries.keys()) {
|
|
4080
|
+
const kwScore = keywordScores.get(docKey) ?? 0;
|
|
4081
|
+
const embScore = embeddingScores?.get(docKey) ?? 0;
|
|
4082
|
+
const score = embeddingScores ? kw * kwScore + (1 - kw) * embScore : kwScore;
|
|
4083
|
+
if (score > 0) {
|
|
4084
|
+
finalScores.push({ docKey, score });
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
finalScores.sort((a, b2) => b2.score - a.score);
|
|
4088
|
+
return finalScores.slice(0, topK).map(({ docKey }) => {
|
|
4089
|
+
return this.toolSummaries.get(docKey);
|
|
4090
|
+
});
|
|
4091
|
+
}
|
|
4092
|
+
/**
|
|
4093
|
+
* Search tools using a regex pattern.
|
|
4094
|
+
* Matches against name, description, and parameter metadata.
|
|
4095
|
+
*/
|
|
4096
|
+
searchRegex(pattern, topK = 5) {
|
|
4097
|
+
if (this.tools.size === 0) return [];
|
|
4098
|
+
try {
|
|
4099
|
+
let flags = "";
|
|
4100
|
+
let cleanPattern = pattern;
|
|
4101
|
+
if (pattern.includes("(?i)")) {
|
|
4102
|
+
flags = "i";
|
|
4103
|
+
cleanPattern = pattern.replace(/\(\?i\)/g, "");
|
|
4104
|
+
}
|
|
4105
|
+
const regex = new RegExp(cleanPattern, flags || void 0);
|
|
4106
|
+
const matches = [];
|
|
4107
|
+
for (const [docKey, text] of this.searchTexts) {
|
|
4108
|
+
const tool = this.toolSummaries.get(docKey);
|
|
4109
|
+
if (!tool) continue;
|
|
4110
|
+
if (regex.test(text) || regex.test(tool.name)) {
|
|
4111
|
+
let score = 1;
|
|
4112
|
+
if (tool.name === cleanPattern) score = 10;
|
|
4113
|
+
else if (tool.name.startsWith(cleanPattern)) score = 5;
|
|
4114
|
+
else if (tool.name.toLowerCase().includes(cleanPattern.toLowerCase())) score = 2;
|
|
4115
|
+
matches.push({ docKey, score });
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
matches.sort((a, b) => b.score - a.score);
|
|
4119
|
+
return matches.slice(0, topK).map(({ docKey }) => {
|
|
4120
|
+
return this.toolSummaries.get(docKey);
|
|
4121
|
+
});
|
|
4122
|
+
} catch (err) {
|
|
4123
|
+
console.warn("[ToolIndex] Regex search failed:", err);
|
|
4124
|
+
return [];
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
// -----------------------------------------------------------------------
|
|
4128
|
+
// Accessors
|
|
4129
|
+
// -----------------------------------------------------------------------
|
|
4130
|
+
/**
|
|
4131
|
+
* Get tool definition(s) by name.
|
|
4132
|
+
* If namespace is provided, it tries to match sessionId or serverName.
|
|
4133
|
+
*/
|
|
4134
|
+
getTool(name, namespace) {
|
|
4135
|
+
const list = this.tools.get(name) ?? [];
|
|
4136
|
+
if (!namespace) return list;
|
|
4137
|
+
return list.filter((t) => t.sessionId === namespace || t.serverName === namespace);
|
|
4138
|
+
}
|
|
4139
|
+
/** All indexed tool names. */
|
|
4140
|
+
getToolNames() {
|
|
4141
|
+
return [...this.tools.keys()];
|
|
4142
|
+
}
|
|
4143
|
+
/** Number of indexed tools (including duplicates). */
|
|
4144
|
+
get size() {
|
|
4145
|
+
let count = 0;
|
|
4146
|
+
for (const list of this.tools.values()) {
|
|
4147
|
+
count += list.length;
|
|
4148
|
+
}
|
|
4149
|
+
return count;
|
|
4150
|
+
}
|
|
4151
|
+
/** Total estimated token cost of all indexed tool schemas. */
|
|
4152
|
+
getTotalTokenCost() {
|
|
4153
|
+
return this.totalTokenCost;
|
|
4154
|
+
}
|
|
4155
|
+
// -----------------------------------------------------------------------
|
|
4156
|
+
// Static Helpers
|
|
4157
|
+
// -----------------------------------------------------------------------
|
|
4158
|
+
/**
|
|
4159
|
+
* Estimate token count of a tool's full schema (name + description + inputSchema).
|
|
4160
|
+
*
|
|
4161
|
+
* Uses character-class weighted counting calibrated against cl100k_base.
|
|
4162
|
+
* Accuracy is typically within ±10% for JSON Schema payloads.
|
|
4163
|
+
*/
|
|
4164
|
+
static estimateTokens(tool) {
|
|
4165
|
+
const parts = [tool.name];
|
|
4166
|
+
if (tool.description) parts.push(tool.description);
|
|
4167
|
+
if (tool.inputSchema) parts.push(JSON.stringify(tool.inputSchema));
|
|
4168
|
+
const text = parts.join(" ");
|
|
4169
|
+
let weightedLen = 0;
|
|
4170
|
+
for (let i = 0; i < text.length; i++) {
|
|
4171
|
+
weightedLen += 1 / classifyChar(text[i]);
|
|
4172
|
+
}
|
|
4173
|
+
return Math.ceil(weightedLen / (1 / CALIBRATION_DIVISOR));
|
|
4174
|
+
}
|
|
4175
|
+
// -----------------------------------------------------------------------
|
|
4176
|
+
// Internals
|
|
4177
|
+
// -----------------------------------------------------------------------
|
|
4178
|
+
/** Build a single searchable string from tool metadata. */
|
|
4179
|
+
buildSearchableText(tool) {
|
|
4180
|
+
const parts = [tool.name];
|
|
4181
|
+
if (tool.description) parts.push(tool.description);
|
|
4182
|
+
if (tool.inputSchema && typeof tool.inputSchema === "object") {
|
|
4183
|
+
const schema = tool.inputSchema;
|
|
4184
|
+
const props = schema.properties;
|
|
4185
|
+
if (props) {
|
|
4186
|
+
for (const [key, val] of Object.entries(props)) {
|
|
4187
|
+
parts.push(key);
|
|
4188
|
+
if (val && typeof val === "object" && val.description) {
|
|
4189
|
+
parts.push(val.description);
|
|
4190
|
+
}
|
|
4191
|
+
}
|
|
4192
|
+
}
|
|
4193
|
+
}
|
|
4194
|
+
return parts.join(" ");
|
|
4195
|
+
}
|
|
4196
|
+
getDocumentKey(tool) {
|
|
4197
|
+
return `${tool.sessionId}::${tool.serverName}::${tool.name}`;
|
|
4198
|
+
}
|
|
4199
|
+
/** Simple whitespace + camelCase + snake_case tokenizer. */
|
|
4200
|
+
tokenize(text) {
|
|
4201
|
+
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);
|
|
4202
|
+
}
|
|
4203
|
+
/** Cosine similarity between two vectors. */
|
|
4204
|
+
cosineSimilarity(a, b) {
|
|
4205
|
+
const len = Math.min(a.length, b.length);
|
|
4206
|
+
let dot = 0;
|
|
4207
|
+
let magA = 0;
|
|
4208
|
+
let magB = 0;
|
|
4209
|
+
for (let i = 0; i < len; i++) {
|
|
4210
|
+
dot += a[i] * b[i];
|
|
4211
|
+
magA += a[i] * a[i];
|
|
4212
|
+
magB += b[i] * b[i];
|
|
4213
|
+
}
|
|
4214
|
+
const denom = Math.sqrt(magA) * Math.sqrt(magB);
|
|
4215
|
+
return denom > 0 ? dot / denom : 0;
|
|
4216
|
+
}
|
|
4217
|
+
};
|
|
4218
|
+
|
|
4219
|
+
// src/shared/schema-compressor.ts
|
|
4220
|
+
init_cjs_shims();
|
|
4221
|
+
var SchemaCompressor = class _SchemaCompressor {
|
|
4222
|
+
/**
|
|
4223
|
+
* Convert a full MCP Tool definition to a compact summary.
|
|
4224
|
+
*
|
|
4225
|
+
* The compact form omits `inputSchema` entirely and optionally generates
|
|
4226
|
+
* a short `parameterHint` from the schema's top-level properties.
|
|
4227
|
+
*/
|
|
4228
|
+
static toCompact(tool) {
|
|
4229
|
+
const compact = {
|
|
4230
|
+
name: tool.name,
|
|
4231
|
+
description: tool.description
|
|
4232
|
+
};
|
|
4233
|
+
if (tool.inputSchema && typeof tool.inputSchema === "object") {
|
|
4234
|
+
const schema = tool.inputSchema;
|
|
4235
|
+
if (schema.properties) {
|
|
4236
|
+
const required = new Set(schema.required ?? []);
|
|
4237
|
+
const parts = [];
|
|
4238
|
+
for (const [key, val] of Object.entries(schema.properties)) {
|
|
4239
|
+
const type = val?.type ?? "any";
|
|
4240
|
+
const enumSuffix = val?.enum && Array.isArray(val.enum) ? `: ${val.enum.map((e) => `'${e}'`).join(" | ")}` : `: ${type}`;
|
|
4241
|
+
parts.push(required.has(key) ? `${key}${enumSuffix}` : `${key}?${enumSuffix}`);
|
|
4242
|
+
}
|
|
4243
|
+
if (parts.length > 0) {
|
|
4244
|
+
compact.parameterHint = `(${parts.join(", ")})`;
|
|
4245
|
+
}
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4248
|
+
return compact;
|
|
4249
|
+
}
|
|
4250
|
+
/**
|
|
4251
|
+
* Convert an array of tools to compact form, optionally limiting the count.
|
|
4252
|
+
*/
|
|
4253
|
+
static compactAll(tools, options) {
|
|
4254
|
+
const limited = options?.maxTools ? tools.slice(0, options.maxTools) : tools;
|
|
4255
|
+
return limited.map((t) => _SchemaCompressor.toCompact(t));
|
|
4256
|
+
}
|
|
4257
|
+
/**
|
|
4258
|
+
* Estimate token savings from using compact vs full tool schemas.
|
|
4259
|
+
*/
|
|
4260
|
+
static estimateSavings(tools) {
|
|
4261
|
+
let fullTokens = 0;
|
|
4262
|
+
let compactTokens = 0;
|
|
4263
|
+
for (const tool of tools) {
|
|
4264
|
+
fullTokens += ToolIndex.estimateTokens(tool);
|
|
4265
|
+
const compact = _SchemaCompressor.toCompact(tool);
|
|
4266
|
+
const text = [compact.name, compact.description ?? "", compact.parameterHint ?? ""].join(" ");
|
|
4267
|
+
compactTokens += Math.ceil(text.length / 4);
|
|
4268
|
+
}
|
|
4269
|
+
const saved = fullTokens - compactTokens;
|
|
4270
|
+
const pct = fullTokens > 0 ? (saved / fullTokens * 100).toFixed(1) : "0.0";
|
|
4271
|
+
return {
|
|
4272
|
+
fullTokens,
|
|
4273
|
+
compactTokens,
|
|
4274
|
+
savedTokens: saved,
|
|
4275
|
+
savingsPercent: `${pct}%`
|
|
4276
|
+
};
|
|
4277
|
+
}
|
|
4278
|
+
};
|
|
4279
|
+
|
|
4280
|
+
// src/shared/meta-tools.ts
|
|
4281
|
+
init_cjs_shims();
|
|
4282
|
+
function createSearchToolDefinition() {
|
|
4283
|
+
return {
|
|
4284
|
+
name: "mcp_search_tool_bm25",
|
|
4285
|
+
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".',
|
|
4286
|
+
inputSchema: {
|
|
4287
|
+
type: "object",
|
|
4288
|
+
properties: {
|
|
4289
|
+
query: {
|
|
4290
|
+
type: "string",
|
|
4291
|
+
description: "Natural language description of the capability you need."
|
|
4292
|
+
},
|
|
4293
|
+
limit: {
|
|
4294
|
+
type: "number",
|
|
4295
|
+
description: "Maximum number of results to return (default: 5, max: 20)."
|
|
4296
|
+
}
|
|
4297
|
+
},
|
|
4298
|
+
required: ["query"]
|
|
4299
|
+
}
|
|
4300
|
+
};
|
|
4301
|
+
}
|
|
4302
|
+
function createRegexSearchToolDefinition() {
|
|
4303
|
+
return {
|
|
4304
|
+
name: "mcp_search_tool_regex",
|
|
4305
|
+
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".',
|
|
4306
|
+
inputSchema: {
|
|
4307
|
+
type: "object",
|
|
4308
|
+
properties: {
|
|
4309
|
+
query: {
|
|
4310
|
+
type: "string",
|
|
4311
|
+
description: 'Regex pattern to search for (e.g., "^get_.*_data", "database").'
|
|
4312
|
+
},
|
|
4313
|
+
limit: {
|
|
4314
|
+
type: "number",
|
|
4315
|
+
description: "Maximum number of results to return (default: 5, max: 20)."
|
|
4316
|
+
}
|
|
4317
|
+
},
|
|
4318
|
+
required: ["query"]
|
|
4319
|
+
}
|
|
4320
|
+
};
|
|
4321
|
+
}
|
|
4322
|
+
function createGetSchemaToolDefinition() {
|
|
4323
|
+
return {
|
|
4324
|
+
name: "mcp_get_tool_schema",
|
|
4325
|
+
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.",
|
|
4326
|
+
inputSchema: {
|
|
4327
|
+
type: "object",
|
|
4328
|
+
properties: {
|
|
4329
|
+
toolName: {
|
|
4330
|
+
type: "string",
|
|
4331
|
+
description: "The exact tool name returned by mcp_search_tool_bm25."
|
|
4332
|
+
},
|
|
4333
|
+
serverName: {
|
|
4334
|
+
type: "string",
|
|
4335
|
+
description: "Optional: The server name provided in mcp_search_tool_bm25. Required if multiple tools have the same name."
|
|
4336
|
+
}
|
|
4337
|
+
},
|
|
4338
|
+
required: ["toolName"]
|
|
4339
|
+
}
|
|
4340
|
+
};
|
|
4341
|
+
}
|
|
4342
|
+
function createExecuteToolDefinition() {
|
|
4343
|
+
return {
|
|
4344
|
+
name: "mcp_execute_tool",
|
|
4345
|
+
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.",
|
|
4346
|
+
inputSchema: {
|
|
4347
|
+
type: "object",
|
|
4348
|
+
properties: {
|
|
4349
|
+
toolName: {
|
|
4350
|
+
type: "string",
|
|
4351
|
+
description: "The exact tool name from mcp_search_tool_bm25 results."
|
|
4352
|
+
},
|
|
4353
|
+
serverName: {
|
|
4354
|
+
type: "string",
|
|
4355
|
+
description: "Optional: The server name provided in mcp_search_tool_bm25. Required if multiple tools have the same name."
|
|
4356
|
+
},
|
|
4357
|
+
args: {
|
|
4358
|
+
type: "object",
|
|
4359
|
+
description: "Arguments matching the tool's inputSchema. Omit or pass {} if the tool takes no parameters.",
|
|
4360
|
+
additionalProperties: true
|
|
4361
|
+
}
|
|
4362
|
+
},
|
|
4363
|
+
required: ["toolName"]
|
|
4364
|
+
}
|
|
4365
|
+
};
|
|
4366
|
+
}
|
|
4367
|
+
async function executeMetaTool(toolName, args, router, callToolFn) {
|
|
4368
|
+
const resolveToolSchema = (name, namespace) => {
|
|
4369
|
+
try {
|
|
4370
|
+
return { tool: router.getToolSchema(name, namespace) };
|
|
4371
|
+
} catch (err) {
|
|
4372
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4373
|
+
return {
|
|
4374
|
+
error: {
|
|
4375
|
+
content: [{ type: "text", text: errorMessage }],
|
|
4376
|
+
isError: true
|
|
4377
|
+
}
|
|
4378
|
+
};
|
|
4379
|
+
}
|
|
4380
|
+
};
|
|
4381
|
+
switch (toolName) {
|
|
4382
|
+
case "mcp_search_tool_bm25": {
|
|
4383
|
+
const query = String(args.query ?? "");
|
|
4384
|
+
const limit = Math.min(Number(args.limit) || 5, 20);
|
|
4385
|
+
const results = await router.searchTools(query, limit);
|
|
4386
|
+
const text = results.length === 0 ? "No tools found matching your query. Try different keywords." : results.map(
|
|
4387
|
+
(t, i) => `${i + 1}. **${t.name}** (server: ${t.serverName})
|
|
4388
|
+
${t.description}
|
|
4389
|
+
Estimated tokens: ${t.estimatedTokens}`
|
|
4390
|
+
).join("\n");
|
|
4391
|
+
return {
|
|
4392
|
+
content: [{ type: "text", text }],
|
|
4393
|
+
isError: false
|
|
4394
|
+
};
|
|
4395
|
+
}
|
|
4396
|
+
case "mcp_search_tool_regex": {
|
|
4397
|
+
const pattern = String(args.query ?? "");
|
|
4398
|
+
const limit = Math.min(Number(args.limit) || 5, 20);
|
|
4399
|
+
const results = await router.searchToolsRegex(pattern, limit);
|
|
4400
|
+
const text = results.length === 0 ? "No tools matched your regex pattern. Try a broader pattern." : results.map(
|
|
4401
|
+
(t, i) => `${i + 1}. **${t.name}** (server: ${t.serverName})
|
|
4402
|
+
${t.description}
|
|
4403
|
+
Estimated tokens: ${t.estimatedTokens}`
|
|
4404
|
+
).join("\n");
|
|
4405
|
+
return {
|
|
4406
|
+
content: [{ type: "text", text }],
|
|
4407
|
+
isError: false
|
|
4408
|
+
};
|
|
4409
|
+
}
|
|
4410
|
+
case "mcp_get_tool_schema": {
|
|
4411
|
+
const name = String(args.toolName ?? "");
|
|
4412
|
+
const namespace = String(args.serverName ?? "") || void 0;
|
|
4413
|
+
const { tool, error } = resolveToolSchema(name, namespace);
|
|
4414
|
+
if (error) {
|
|
4415
|
+
return error;
|
|
4416
|
+
}
|
|
4417
|
+
if (!tool) {
|
|
4418
|
+
return {
|
|
4419
|
+
content: [
|
|
4420
|
+
{
|
|
4421
|
+
type: "text",
|
|
4422
|
+
text: `Tool "${name}" not found. Use mcp_search_tool_bm25 to find available tools first.`
|
|
4423
|
+
}
|
|
4424
|
+
],
|
|
4425
|
+
isError: true
|
|
4426
|
+
};
|
|
4427
|
+
}
|
|
4428
|
+
const schema = {
|
|
4429
|
+
name: tool.name,
|
|
4430
|
+
description: tool.description,
|
|
4431
|
+
inputSchema: tool.inputSchema
|
|
4432
|
+
};
|
|
4433
|
+
return {
|
|
4434
|
+
content: [{ type: "text", text: JSON.stringify(schema, null, 2) }],
|
|
4435
|
+
isError: false
|
|
4436
|
+
};
|
|
4437
|
+
}
|
|
4438
|
+
case "mcp_execute_tool": {
|
|
4439
|
+
const targetToolName = String(args.toolName ?? "");
|
|
4440
|
+
const namespace = String(args.serverName ?? "") || void 0;
|
|
4441
|
+
const toolArgs = args.args ?? {};
|
|
4442
|
+
if (!targetToolName) {
|
|
4443
|
+
return {
|
|
4444
|
+
content: [{ type: "text", text: 'Missing required parameter "toolName". Specify which tool to execute.' }],
|
|
4445
|
+
isError: true
|
|
4446
|
+
};
|
|
4447
|
+
}
|
|
4448
|
+
const { tool, error } = resolveToolSchema(targetToolName, namespace);
|
|
4449
|
+
if (error) {
|
|
4450
|
+
return error;
|
|
4451
|
+
}
|
|
4452
|
+
if (!tool) {
|
|
4453
|
+
return {
|
|
4454
|
+
content: [
|
|
4455
|
+
{
|
|
4456
|
+
type: "text",
|
|
4457
|
+
text: `Tool "${targetToolName}" not found. Use mcp_search_tool_bm25 to discover available tools first.`
|
|
4458
|
+
}
|
|
4459
|
+
],
|
|
4460
|
+
isError: true
|
|
4461
|
+
};
|
|
4462
|
+
}
|
|
4463
|
+
if (!callToolFn) {
|
|
4464
|
+
return {
|
|
4465
|
+
content: [{ type: "text", text: "Tool execution is not available. No callToolFn was configured." }],
|
|
4466
|
+
isError: true
|
|
4467
|
+
};
|
|
4468
|
+
}
|
|
4469
|
+
try {
|
|
4470
|
+
const result = await callToolFn(targetToolName, toolArgs, namespace);
|
|
4471
|
+
if (result && typeof result === "object" && "content" in result) {
|
|
4472
|
+
return result;
|
|
4473
|
+
}
|
|
4474
|
+
const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
4475
|
+
return {
|
|
4476
|
+
content: [{ type: "text", text }],
|
|
4477
|
+
isError: false
|
|
4478
|
+
};
|
|
4479
|
+
} catch (err) {
|
|
4480
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4481
|
+
return {
|
|
4482
|
+
content: [{ type: "text", text: `Tool execution failed: ${errorMessage}` }],
|
|
4483
|
+
isError: true
|
|
4484
|
+
};
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
default:
|
|
4488
|
+
return null;
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
function isMetaTool(toolName) {
|
|
4492
|
+
return toolName === "mcp_search_tool_bm25" || toolName === "mcp_search_tool_regex" || toolName === "mcp_get_tool_schema" || toolName === "mcp_execute_tool";
|
|
4493
|
+
}
|
|
4494
|
+
function resolveMetaToolProxy(toolName, args) {
|
|
4495
|
+
if (toolName === "mcp_execute_tool") {
|
|
4496
|
+
const innerName = args?.toolName;
|
|
4497
|
+
const innerArgs = args?.args;
|
|
4498
|
+
return {
|
|
4499
|
+
toolName: typeof innerName === "string" && innerName ? innerName : toolName,
|
|
4500
|
+
args: innerArgs && typeof innerArgs === "object" && !Array.isArray(innerArgs) ? innerArgs : {}
|
|
4501
|
+
};
|
|
4502
|
+
}
|
|
4503
|
+
const match = toolName.match(/(?:tool_[^_]+_)?(.+)$/);
|
|
4504
|
+
const resolvedName = match?.[1] ?? toolName;
|
|
4505
|
+
return { toolName: resolvedName, args: args ?? {} };
|
|
4506
|
+
}
|
|
4507
|
+
|
|
4508
|
+
// src/shared/tool-router.ts
|
|
4509
|
+
var ToolRouter = class {
|
|
4510
|
+
constructor(client, options = {}) {
|
|
4511
|
+
this.client = client;
|
|
4512
|
+
this.options = options;
|
|
4513
|
+
__publicField(this, "index");
|
|
4514
|
+
__publicField(this, "allTools", []);
|
|
4515
|
+
__publicField(this, "groupsMap", /* @__PURE__ */ new Map());
|
|
4516
|
+
__publicField(this, "strategy");
|
|
4517
|
+
__publicField(this, "maxTools");
|
|
4518
|
+
__publicField(this, "compactSchemas");
|
|
4519
|
+
__publicField(this, "activeGroups");
|
|
4520
|
+
__publicField(this, "customGroups");
|
|
4521
|
+
__publicField(this, "initialized", false);
|
|
4522
|
+
this.strategy = options.strategy ?? "all";
|
|
4523
|
+
this.maxTools = options.maxTools ?? 40;
|
|
4524
|
+
this.compactSchemas = options.compactSchemas ?? false;
|
|
4525
|
+
this.activeGroups = new Set(options.activeGroups ?? []);
|
|
4526
|
+
this.customGroups = options.groups;
|
|
4527
|
+
this.index = new ToolIndex({
|
|
4528
|
+
embedFn: options.embedFn,
|
|
4529
|
+
keywordWeight: options.keywordWeight
|
|
4530
|
+
});
|
|
4531
|
+
}
|
|
4532
|
+
// -----------------------------------------------------------------------
|
|
4533
|
+
// Core Public API
|
|
4534
|
+
// -----------------------------------------------------------------------
|
|
4535
|
+
/**
|
|
4536
|
+
* Get tools filtered by the current strategy.
|
|
4537
|
+
* This is the main method adapters should call.
|
|
4538
|
+
*
|
|
4539
|
+
* - `all` → returns all tools (unchanged behavior)
|
|
4540
|
+
* - `search` → returns only meta-tools (mcp_search_tool_bm25, mcp_get_tool_schema, mcp_execute_tool)
|
|
4541
|
+
* - `groups` → returns tools from active groups only
|
|
4542
|
+
*/
|
|
4543
|
+
async getFilteredTools() {
|
|
4544
|
+
await this.ensureInitialized();
|
|
4545
|
+
switch (this.strategy) {
|
|
4546
|
+
case "search":
|
|
4547
|
+
return this.getMetaToolDefinitions();
|
|
4548
|
+
case "groups":
|
|
4549
|
+
return this.getGroupFilteredTools();
|
|
4550
|
+
case "all":
|
|
4551
|
+
default:
|
|
4552
|
+
if (this.compactSchemas) {
|
|
4553
|
+
return this.allTools.map((t) => {
|
|
4554
|
+
const compact = SchemaCompressor.toCompact(t);
|
|
4555
|
+
return {
|
|
4556
|
+
name: compact.name,
|
|
4557
|
+
description: (compact.description ?? "") + (compact.parameterHint ? ` Parameters: ${compact.parameterHint}` : ""),
|
|
4558
|
+
inputSchema: { type: "object", properties: {} }
|
|
4559
|
+
};
|
|
4560
|
+
});
|
|
4561
|
+
}
|
|
4562
|
+
return [...this.allTools];
|
|
4563
|
+
}
|
|
4564
|
+
}
|
|
4565
|
+
/**
|
|
4566
|
+
* Search tools by natural-language query.
|
|
4567
|
+
* Works regardless of strategy.
|
|
4568
|
+
*/
|
|
4569
|
+
async searchTools(query, topK) {
|
|
4570
|
+
await this.ensureInitialized();
|
|
4571
|
+
return this.index.search(query, topK ?? this.maxTools);
|
|
4572
|
+
}
|
|
4573
|
+
/**
|
|
4574
|
+
* Search tools by regex pattern.
|
|
4575
|
+
* Matches against name, description, and parameter metadata.
|
|
4576
|
+
*/
|
|
4577
|
+
async searchToolsRegex(pattern, topK) {
|
|
4578
|
+
await this.ensureInitialized();
|
|
4579
|
+
return this.index.searchRegex(pattern, topK ?? this.maxTools);
|
|
4580
|
+
}
|
|
4581
|
+
/**
|
|
4582
|
+
* Get the full tool definition by name.
|
|
4583
|
+
* If tool name is ambiguous, use namespace to specify the server.
|
|
4584
|
+
*/
|
|
4585
|
+
getToolSchema(toolName, namespace) {
|
|
4586
|
+
const matches = this.index.getTool(toolName, namespace);
|
|
4587
|
+
if (matches.length === 0) return void 0;
|
|
4588
|
+
if (matches.length > 1) {
|
|
4589
|
+
const servers = matches.map((m) => m.serverName).join(", ");
|
|
4590
|
+
throw new Error(
|
|
4591
|
+
`Tool "${toolName}" is provided by multiple servers: [${servers}]. Please specify the desired "serverName" as a namespace.`
|
|
4592
|
+
);
|
|
4593
|
+
}
|
|
4594
|
+
return matches[0];
|
|
4595
|
+
}
|
|
4596
|
+
/**
|
|
4597
|
+
* Get compact (schema-less) summaries for all tools.
|
|
4598
|
+
*/
|
|
4599
|
+
getCompactTools() {
|
|
4600
|
+
return SchemaCompressor.compactAll(this.allTools);
|
|
4601
|
+
}
|
|
4602
|
+
// -----------------------------------------------------------------------
|
|
4603
|
+
// Group Management
|
|
4604
|
+
// -----------------------------------------------------------------------
|
|
4605
|
+
/** Get all available groups with their tool lists and active status. */
|
|
4606
|
+
getGroups() {
|
|
4607
|
+
return new Map(this.groupsMap);
|
|
4608
|
+
}
|
|
4609
|
+
/** Activate specific groups. Pass empty array to activate all. */
|
|
4610
|
+
setActiveGroups(groups) {
|
|
4611
|
+
this.activeGroups = new Set(groups);
|
|
4612
|
+
for (const [name, info] of this.groupsMap) {
|
|
4613
|
+
info.active = this.activeGroups.size === 0 || this.activeGroups.has(name);
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4616
|
+
/** Get the names of currently active groups. */
|
|
4617
|
+
getActiveGroups() {
|
|
4618
|
+
return [...this.activeGroups];
|
|
4619
|
+
}
|
|
4620
|
+
// -----------------------------------------------------------------------
|
|
4621
|
+
// Stats & Introspection
|
|
4622
|
+
// -----------------------------------------------------------------------
|
|
4623
|
+
/** Total token cost of all tools if loaded without filtering. */
|
|
4624
|
+
getTotalTokenCost() {
|
|
4625
|
+
return this.index.getTotalTokenCost();
|
|
4626
|
+
}
|
|
4627
|
+
/** Estimate token cost of the currently filtered tool set. */
|
|
4628
|
+
async getFilteredTokenCost() {
|
|
4629
|
+
const tools = await this.getFilteredTools();
|
|
4630
|
+
let total = 0;
|
|
4631
|
+
for (const tool of tools) {
|
|
4632
|
+
total += ToolIndex.estimateTokens(tool);
|
|
4633
|
+
}
|
|
4634
|
+
return total;
|
|
4635
|
+
}
|
|
4636
|
+
/** Get compression stats showing savings from current strategy. */
|
|
4637
|
+
getCompressionStats() {
|
|
4638
|
+
return SchemaCompressor.estimateSavings(this.allTools);
|
|
4639
|
+
}
|
|
4640
|
+
/** Number of total indexed tools. */
|
|
4641
|
+
get totalToolCount() {
|
|
4642
|
+
return this.allTools.length;
|
|
4643
|
+
}
|
|
4644
|
+
/** Change strategy at runtime. */
|
|
4645
|
+
setStrategy(strategy) {
|
|
4646
|
+
this.strategy = strategy;
|
|
4647
|
+
}
|
|
4648
|
+
/**
|
|
4649
|
+
* Force a re-index of tools from all connected clients.
|
|
4650
|
+
* Call this after adding/removing MCP server connections.
|
|
4651
|
+
*/
|
|
4652
|
+
async refresh() {
|
|
4653
|
+
this.initialized = false;
|
|
4654
|
+
await this.ensureInitialized();
|
|
4655
|
+
}
|
|
4656
|
+
/**
|
|
4657
|
+
* Execute a tool by routing to the correct MCP client.
|
|
4658
|
+
* Used by the `mcp_execute_tool` meta-tool to proxy tool calls.
|
|
4659
|
+
*/
|
|
4660
|
+
async callTool(toolName, args, namespace) {
|
|
4661
|
+
await this.ensureInitialized();
|
|
4662
|
+
const indexedTool = this.getToolSchema(toolName, namespace);
|
|
4663
|
+
if (!indexedTool) {
|
|
4664
|
+
throw new Error(
|
|
4665
|
+
`Tool "${toolName}" not found${namespace ? ` on server "${namespace}"` : ""}. Use mcp_search_tool_bm25 or mcp_search_tool_regex to discover available tools.`
|
|
4666
|
+
);
|
|
4667
|
+
}
|
|
4668
|
+
const clients = this.getClients();
|
|
4669
|
+
const targetClient = clients.find(
|
|
4670
|
+
(c) => typeof c.getSessionId === "function" && c.getSessionId() === indexedTool.sessionId
|
|
4671
|
+
) ?? clients.find((c) => c.isConnected());
|
|
4672
|
+
if (!targetClient) {
|
|
4673
|
+
throw new Error(`No connected client found for tool "${toolName}"`);
|
|
4674
|
+
}
|
|
4675
|
+
return await targetClient.callTool(toolName, args);
|
|
4676
|
+
}
|
|
4677
|
+
// -----------------------------------------------------------------------
|
|
4678
|
+
// Internals
|
|
4679
|
+
// -----------------------------------------------------------------------
|
|
4680
|
+
/** Lazy initialization — fetches tools from all connected clients. */
|
|
4681
|
+
async ensureInitialized() {
|
|
4682
|
+
if (this.initialized) return;
|
|
4683
|
+
this.allTools = await this.fetchAllTools();
|
|
4684
|
+
await this.index.buildIndex(this.allTools);
|
|
4685
|
+
this.buildGroups();
|
|
4686
|
+
this.initialized = true;
|
|
4687
|
+
}
|
|
4688
|
+
/** Fetch tools from all connected MCP clients. */
|
|
4689
|
+
async fetchAllTools() {
|
|
4690
|
+
const clients = this.getClients();
|
|
4691
|
+
const result = [];
|
|
4692
|
+
for (const client of clients) {
|
|
4693
|
+
if (!client.isConnected()) continue;
|
|
4694
|
+
try {
|
|
4695
|
+
const { tools } = await client.listTools();
|
|
4696
|
+
const serverId = typeof client.getServerId === "function" ? client.getServerId() ?? "unknown" : "unknown";
|
|
4697
|
+
const serverName = (typeof client.getServerName === "function" ? client.getServerName() : void 0) ?? serverId;
|
|
4698
|
+
const sessionId = typeof client.getSessionId === "function" ? client.getSessionId() ?? "unknown" : "unknown";
|
|
4699
|
+
for (const tool of tools) {
|
|
4700
|
+
result.push({
|
|
4701
|
+
...tool,
|
|
4702
|
+
serverName,
|
|
4703
|
+
sessionId
|
|
4704
|
+
});
|
|
4705
|
+
}
|
|
4706
|
+
} catch (err) {
|
|
4707
|
+
console.warn("[ToolRouter] Failed to fetch tools from client:", err);
|
|
4708
|
+
}
|
|
4709
|
+
}
|
|
4710
|
+
return result;
|
|
4711
|
+
}
|
|
4712
|
+
/** Resolve the client input to a flat array of ToolClient instances. */
|
|
4713
|
+
getClients() {
|
|
4714
|
+
if (Array.isArray(this.client)) {
|
|
4715
|
+
return this.client;
|
|
4716
|
+
}
|
|
4717
|
+
if (typeof this.client.getClients === "function") {
|
|
4718
|
+
return this.client.getClients();
|
|
4719
|
+
}
|
|
4720
|
+
return [this.client];
|
|
4721
|
+
}
|
|
4722
|
+
/** Build group map from custom config or auto-detect from server names. */
|
|
4723
|
+
buildGroups() {
|
|
4724
|
+
this.groupsMap.clear();
|
|
4725
|
+
if (this.customGroups) {
|
|
4726
|
+
for (const [name, tools] of Object.entries(this.customGroups)) {
|
|
4727
|
+
this.groupsMap.set(name, {
|
|
4728
|
+
tools,
|
|
4729
|
+
active: this.activeGroups.size === 0 || this.activeGroups.has(name)
|
|
4730
|
+
});
|
|
4731
|
+
}
|
|
4732
|
+
} else {
|
|
4733
|
+
const serverTools = /* @__PURE__ */ new Map();
|
|
4734
|
+
for (const tool of this.allTools) {
|
|
4735
|
+
const group = tool.serverName;
|
|
4736
|
+
if (!serverTools.has(group)) {
|
|
4737
|
+
serverTools.set(group, []);
|
|
4738
|
+
}
|
|
4739
|
+
serverTools.get(group).push(tool.name);
|
|
4740
|
+
}
|
|
4741
|
+
for (const [serverName, tools] of serverTools) {
|
|
4742
|
+
this.groupsMap.set(serverName, {
|
|
4743
|
+
tools,
|
|
4744
|
+
active: this.activeGroups.size === 0 || this.activeGroups.has(serverName)
|
|
4745
|
+
});
|
|
4746
|
+
}
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4749
|
+
/** Return only tools belonging to currently active groups. */
|
|
4750
|
+
getGroupFilteredTools() {
|
|
4751
|
+
const activeToolNames = /* @__PURE__ */ new Set();
|
|
4752
|
+
for (const [, info] of this.groupsMap) {
|
|
4753
|
+
if (info.active) {
|
|
4754
|
+
for (const name of info.tools) {
|
|
4755
|
+
activeToolNames.add(name);
|
|
4756
|
+
}
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
4759
|
+
const filtered = this.allTools.filter((t) => activeToolNames.has(t.name));
|
|
4760
|
+
if (this.compactSchemas) {
|
|
4761
|
+
return filtered.slice(0, this.maxTools).map((t) => {
|
|
4762
|
+
const compact = SchemaCompressor.toCompact(t);
|
|
4763
|
+
return {
|
|
4764
|
+
name: compact.name,
|
|
4765
|
+
description: (compact.description ?? "") + (compact.parameterHint ? ` Parameters: ${compact.parameterHint}` : ""),
|
|
4766
|
+
inputSchema: { type: "object", properties: {} }
|
|
4767
|
+
};
|
|
4768
|
+
});
|
|
4769
|
+
}
|
|
4770
|
+
return filtered.slice(0, this.maxTools);
|
|
4771
|
+
}
|
|
4772
|
+
/** The 4 meta-tool definitions exposed in `search` strategy. */
|
|
4773
|
+
getMetaToolDefinitions() {
|
|
4774
|
+
return [
|
|
4775
|
+
createSearchToolDefinition(),
|
|
4776
|
+
createRegexSearchToolDefinition(),
|
|
4777
|
+
createGetSchemaToolDefinition(),
|
|
4778
|
+
createExecuteToolDefinition()
|
|
4779
|
+
];
|
|
4780
|
+
}
|
|
4781
|
+
};
|
|
4782
|
+
|
|
3867
4783
|
exports.APP_HOST_DEFAULTS = APP_HOST_DEFAULTS;
|
|
3868
4784
|
exports.AppHost = AppHost;
|
|
3869
4785
|
exports.AuthenticationError = AuthenticationError;
|
|
@@ -3894,14 +4810,22 @@ exports.SOFTWARE_VERSION = SOFTWARE_VERSION;
|
|
|
3894
4810
|
exports.SSEClient = SSEClient;
|
|
3895
4811
|
exports.SSEConnectionManager = SSEConnectionManager;
|
|
3896
4812
|
exports.STATE_EXPIRATION_MS = STATE_EXPIRATION_MS;
|
|
4813
|
+
exports.SchemaCompressor = SchemaCompressor;
|
|
3897
4814
|
exports.SessionNotFoundError = SessionNotFoundError;
|
|
3898
4815
|
exports.SessionValidationError = SessionValidationError;
|
|
3899
4816
|
exports.StorageOAuthClientProvider = StorageOAuthClientProvider;
|
|
3900
4817
|
exports.TOKEN_EXPIRY_BUFFER_MS = TOKEN_EXPIRY_BUFFER_MS;
|
|
3901
4818
|
exports.ToolExecutionError = ToolExecutionError;
|
|
4819
|
+
exports.ToolIndex = ToolIndex;
|
|
4820
|
+
exports.ToolRouter = ToolRouter;
|
|
3902
4821
|
exports.UnauthorizedError = UnauthorizedError;
|
|
4822
|
+
exports.createExecuteToolDefinition = createExecuteToolDefinition;
|
|
4823
|
+
exports.createGetSchemaToolDefinition = createGetSchemaToolDefinition;
|
|
3903
4824
|
exports.createNextMcpHandler = createNextMcpHandler;
|
|
4825
|
+
exports.createRegexSearchToolDefinition = createRegexSearchToolDefinition;
|
|
3904
4826
|
exports.createSSEHandler = createSSEHandler;
|
|
4827
|
+
exports.createSearchToolDefinition = createSearchToolDefinition;
|
|
4828
|
+
exports.executeMetaTool = executeMetaTool;
|
|
3905
4829
|
exports.findToolByName = findToolByName;
|
|
3906
4830
|
exports.getToolUiResourceUri = getToolUiResourceUri;
|
|
3907
4831
|
exports.isCallToolSuccess = isCallToolSuccess;
|
|
@@ -3909,6 +4833,8 @@ exports.isConnectAuthRequired = isConnectAuthRequired;
|
|
|
3909
4833
|
exports.isConnectError = isConnectError;
|
|
3910
4834
|
exports.isConnectSuccess = isConnectSuccess;
|
|
3911
4835
|
exports.isListToolsSuccess = isListToolsSuccess;
|
|
4836
|
+
exports.isMetaTool = isMetaTool;
|
|
4837
|
+
exports.resolveMetaToolProxy = resolveMetaToolProxy;
|
|
3912
4838
|
exports.sanitizeServerLabel = sanitizeServerLabel;
|
|
3913
4839
|
exports.storage = storage;
|
|
3914
4840
|
//# sourceMappingURL=index.js.map
|