@mcp-ts/sdk 1.4.0 → 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 +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/react.d.mts +14 -7
- package/dist/client/react.d.ts +14 -7
- package/dist/client/react.js +48 -23
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +47 -24
- 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/{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 +883 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +868 -2
- 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 +3 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +3 -1
- 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 +2 -0
- package/src/client/react/use-mcp-apps.tsx +50 -32
- package/src/server/index.ts +2 -0
- package/src/server/mcp/oauth-client.ts +3 -1
- 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
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolRouter — Middleware layer for intelligent MCP tool selection.
|
|
3
|
+
*
|
|
4
|
+
* Sits between your AI framework adapter and MultiSessionClient to reduce
|
|
5
|
+
* context window usage. Supports three strategies:
|
|
6
|
+
*
|
|
7
|
+
* • `all` — Pass through every tool (backward-compatible default)
|
|
8
|
+
* • `search` — Expose only meta-tools; LLM discovers tools on-demand
|
|
9
|
+
* • `groups` — Expose tools from active groups only
|
|
10
|
+
*
|
|
11
|
+
* Inspired by Anthropic's `defer_loading` + `tool_search_tool` pattern.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { ToolRouter } from '@mcp-ts/sdk/shared';
|
|
16
|
+
* import { AIAdapter } from '@mcp-ts/sdk/adapters/ai';
|
|
17
|
+
*
|
|
18
|
+
* const router = new ToolRouter(multiSessionClient, {
|
|
19
|
+
* strategy: 'search',
|
|
20
|
+
* maxTools: 5,
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* const tools = await AIAdapter.getTools(multiSessionClient, { toolRouter: router });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @packageDocumentation
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
30
|
+
import type { ToolClient, ToolClientProvider } from './types.js';
|
|
31
|
+
import { ToolIndex, type IndexedTool, type ToolSummary, type EmbedFn } from './tool-index.js';
|
|
32
|
+
import { SchemaCompressor, type CompactTool } from './schema-compressor.js';
|
|
33
|
+
import {
|
|
34
|
+
createSearchToolDefinition,
|
|
35
|
+
createRegexSearchToolDefinition,
|
|
36
|
+
createGetSchemaToolDefinition,
|
|
37
|
+
createExecuteToolDefinition,
|
|
38
|
+
} from './meta-tools.js';
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Types
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
export type ToolRouterStrategy = 'all' | 'search' | 'groups';
|
|
45
|
+
|
|
46
|
+
export interface ToolRouterOptions {
|
|
47
|
+
/**
|
|
48
|
+
* Strategy for tool selection.
|
|
49
|
+
*
|
|
50
|
+
* • `all` — Expose all tools (default, backward-compatible)
|
|
51
|
+
* • `search` — Expose only meta-tools; LLM discovers real tools via search
|
|
52
|
+
* • `groups` — Expose only tools from active groups
|
|
53
|
+
*
|
|
54
|
+
* @default 'all'
|
|
55
|
+
*/
|
|
56
|
+
strategy?: ToolRouterStrategy;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Maximum tools to expose to the LLM at once.
|
|
60
|
+
* Only applies to `groups` strategy and search results.
|
|
61
|
+
* @default 40
|
|
62
|
+
*/
|
|
63
|
+
maxTools?: number;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Tool groups configuration — map of group name to tool names.
|
|
67
|
+
* When not provided, groups are auto-generated from server names.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* groups: {
|
|
72
|
+
* database: ['query_db', 'list_tables', 'describe_table'],
|
|
73
|
+
* github: ['create_pr', 'list_issues', 'search_code'],
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
groups?: Record<string, string[]>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Active groups (when `strategy='groups'`).
|
|
81
|
+
* Only tools in these groups are exposed. Empty = all groups active.
|
|
82
|
+
*/
|
|
83
|
+
activeGroups?: string[];
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Whether to use compact schemas (name + description + parameterHint only, no inputSchema).
|
|
87
|
+
* Reduces token usage but requires 2-turn flow: LLM picks tool → get schema → call.
|
|
88
|
+
* @default false
|
|
89
|
+
*/
|
|
90
|
+
compactSchemas?: boolean;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Optional embedding function for semantic search.
|
|
94
|
+
* When not provided, keyword TF-IDF matching is used.
|
|
95
|
+
*/
|
|
96
|
+
embedFn?: EmbedFn;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Weight of keyword score vs embedding score (0–1).
|
|
100
|
+
* Only relevant when `embedFn` is provided.
|
|
101
|
+
* @default 0.4
|
|
102
|
+
*/
|
|
103
|
+
keywordWeight?: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Information about a tool group. */
|
|
107
|
+
export interface ToolGroupInfo {
|
|
108
|
+
tools: string[];
|
|
109
|
+
active: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Client Input Types
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Accepted client input for ToolRouter.
|
|
118
|
+
* Pass a `ToolClientProvider` (e.g. MultiSessionClient), or an array of `ToolClient` instances.
|
|
119
|
+
*/
|
|
120
|
+
export type ToolRouterClientInput = ToolClientProvider | ToolClient[];
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// ToolRouter
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
export class ToolRouter {
|
|
127
|
+
private index: ToolIndex;
|
|
128
|
+
private allTools: IndexedTool[] = [];
|
|
129
|
+
private groupsMap = new Map<string, ToolGroupInfo>();
|
|
130
|
+
private strategy: ToolRouterStrategy;
|
|
131
|
+
private maxTools: number;
|
|
132
|
+
private compactSchemas: boolean;
|
|
133
|
+
private activeGroups: Set<string>;
|
|
134
|
+
private customGroups?: Record<string, string[]>;
|
|
135
|
+
private initialized = false;
|
|
136
|
+
|
|
137
|
+
constructor(
|
|
138
|
+
private client: ToolRouterClientInput,
|
|
139
|
+
private options: ToolRouterOptions = {}
|
|
140
|
+
) {
|
|
141
|
+
this.strategy = options.strategy ?? 'all';
|
|
142
|
+
this.maxTools = options.maxTools ?? 40;
|
|
143
|
+
this.compactSchemas = options.compactSchemas ?? false;
|
|
144
|
+
this.activeGroups = new Set(options.activeGroups ?? []);
|
|
145
|
+
this.customGroups = options.groups;
|
|
146
|
+
|
|
147
|
+
this.index = new ToolIndex({
|
|
148
|
+
embedFn: options.embedFn,
|
|
149
|
+
keywordWeight: options.keywordWeight,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// -----------------------------------------------------------------------
|
|
154
|
+
// Core Public API
|
|
155
|
+
// -----------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get tools filtered by the current strategy.
|
|
159
|
+
* This is the main method adapters should call.
|
|
160
|
+
*
|
|
161
|
+
* - `all` → returns all tools (unchanged behavior)
|
|
162
|
+
* - `search` → returns only meta-tools (mcp_search_tool_bm25, mcp_get_tool_schema, mcp_execute_tool)
|
|
163
|
+
* - `groups` → returns tools from active groups only
|
|
164
|
+
*/
|
|
165
|
+
async getFilteredTools(): Promise<Tool[]> {
|
|
166
|
+
await this.ensureInitialized();
|
|
167
|
+
|
|
168
|
+
switch (this.strategy) {
|
|
169
|
+
case 'search':
|
|
170
|
+
return this.getMetaToolDefinitions();
|
|
171
|
+
|
|
172
|
+
case 'groups':
|
|
173
|
+
return this.getGroupFilteredTools();
|
|
174
|
+
|
|
175
|
+
case 'all':
|
|
176
|
+
default:
|
|
177
|
+
if (this.compactSchemas) {
|
|
178
|
+
// Return tools with inputSchema stripped
|
|
179
|
+
return this.allTools.map((t) => {
|
|
180
|
+
const compact = SchemaCompressor.toCompact(t);
|
|
181
|
+
return {
|
|
182
|
+
name: compact.name,
|
|
183
|
+
description:
|
|
184
|
+
(compact.description ?? '') +
|
|
185
|
+
(compact.parameterHint ? ` Parameters: ${compact.parameterHint}` : ''),
|
|
186
|
+
inputSchema: { type: 'object' as const, properties: {} },
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return [...this.allTools];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Search tools by natural-language query.
|
|
196
|
+
* Works regardless of strategy.
|
|
197
|
+
*/
|
|
198
|
+
async searchTools(query: string, topK?: number): Promise<ToolSummary[]> {
|
|
199
|
+
await this.ensureInitialized();
|
|
200
|
+
return this.index.search(query, topK ?? this.maxTools);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Search tools by regex pattern.
|
|
205
|
+
* Matches against name, description, and parameter metadata.
|
|
206
|
+
*/
|
|
207
|
+
async searchToolsRegex(pattern: string, topK?: number): Promise<ToolSummary[]> {
|
|
208
|
+
await this.ensureInitialized();
|
|
209
|
+
return this.index.searchRegex(pattern, topK ?? this.maxTools);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get the full tool definition by name.
|
|
214
|
+
* If tool name is ambiguous, use namespace to specify the server.
|
|
215
|
+
*/
|
|
216
|
+
getToolSchema(toolName: string, namespace?: string): IndexedTool | undefined {
|
|
217
|
+
const matches = this.index.getTool(toolName, namespace);
|
|
218
|
+
|
|
219
|
+
if (matches.length === 0) return undefined;
|
|
220
|
+
|
|
221
|
+
if (matches.length > 1) {
|
|
222
|
+
const servers = matches.map((m) => m.serverName).join(', ');
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Tool "${toolName}" is provided by multiple servers: [${servers}]. ` +
|
|
225
|
+
`Please specify the desired "serverName" as a namespace.`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return matches[0];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get compact (schema-less) summaries for all tools.
|
|
234
|
+
*/
|
|
235
|
+
getCompactTools(): CompactTool[] {
|
|
236
|
+
return SchemaCompressor.compactAll(this.allTools);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// -----------------------------------------------------------------------
|
|
240
|
+
// Group Management
|
|
241
|
+
// -----------------------------------------------------------------------
|
|
242
|
+
|
|
243
|
+
/** Get all available groups with their tool lists and active status. */
|
|
244
|
+
getGroups(): Map<string, ToolGroupInfo> {
|
|
245
|
+
return new Map(this.groupsMap);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Activate specific groups. Pass empty array to activate all. */
|
|
249
|
+
setActiveGroups(groups: string[]): void {
|
|
250
|
+
this.activeGroups = new Set(groups);
|
|
251
|
+
// Update groupsMap active flags
|
|
252
|
+
for (const [name, info] of this.groupsMap) {
|
|
253
|
+
info.active = this.activeGroups.size === 0 || this.activeGroups.has(name);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Get the names of currently active groups. */
|
|
258
|
+
getActiveGroups(): string[] {
|
|
259
|
+
return [...this.activeGroups];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// -----------------------------------------------------------------------
|
|
263
|
+
// Stats & Introspection
|
|
264
|
+
// -----------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
/** Total token cost of all tools if loaded without filtering. */
|
|
267
|
+
getTotalTokenCost(): number {
|
|
268
|
+
return this.index.getTotalTokenCost();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Estimate token cost of the currently filtered tool set. */
|
|
272
|
+
async getFilteredTokenCost(): Promise<number> {
|
|
273
|
+
const tools = await this.getFilteredTools();
|
|
274
|
+
let total = 0;
|
|
275
|
+
for (const tool of tools) {
|
|
276
|
+
total += ToolIndex.estimateTokens(tool);
|
|
277
|
+
}
|
|
278
|
+
return total;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Get compression stats showing savings from current strategy. */
|
|
282
|
+
getCompressionStats() {
|
|
283
|
+
return SchemaCompressor.estimateSavings(this.allTools);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Number of total indexed tools. */
|
|
287
|
+
get totalToolCount(): number {
|
|
288
|
+
return this.allTools.length;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Change strategy at runtime. */
|
|
292
|
+
setStrategy(strategy: ToolRouterStrategy): void {
|
|
293
|
+
this.strategy = strategy;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Force a re-index of tools from all connected clients.
|
|
298
|
+
* Call this after adding/removing MCP server connections.
|
|
299
|
+
*/
|
|
300
|
+
async refresh(): Promise<void> {
|
|
301
|
+
this.initialized = false;
|
|
302
|
+
await this.ensureInitialized();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Execute a tool by routing to the correct MCP client.
|
|
307
|
+
* Used by the `mcp_execute_tool` meta-tool to proxy tool calls.
|
|
308
|
+
*/
|
|
309
|
+
async callTool(
|
|
310
|
+
toolName: string,
|
|
311
|
+
args: Record<string, unknown>,
|
|
312
|
+
namespace?: string
|
|
313
|
+
): Promise<any> {
|
|
314
|
+
await this.ensureInitialized();
|
|
315
|
+
|
|
316
|
+
const indexedTool = this.getToolSchema(toolName, namespace);
|
|
317
|
+
if (!indexedTool) {
|
|
318
|
+
throw new Error(
|
|
319
|
+
`Tool "${toolName}" not found${
|
|
320
|
+
namespace ? ` on server "${namespace}"` : ''
|
|
321
|
+
}. Use mcp_search_tool_bm25 or mcp_search_tool_regex to discover available tools.`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const clients = this.getClients();
|
|
326
|
+
const targetClient =
|
|
327
|
+
clients.find(
|
|
328
|
+
(c) =>
|
|
329
|
+
typeof c.getSessionId === 'function' &&
|
|
330
|
+
c.getSessionId() === indexedTool.sessionId
|
|
331
|
+
) ?? clients.find((c) => c.isConnected());
|
|
332
|
+
|
|
333
|
+
if (!targetClient) {
|
|
334
|
+
throw new Error(`No connected client found for tool "${toolName}"`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return await targetClient.callTool(toolName, args);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// -----------------------------------------------------------------------
|
|
341
|
+
// Internals
|
|
342
|
+
// -----------------------------------------------------------------------
|
|
343
|
+
|
|
344
|
+
/** Lazy initialization — fetches tools from all connected clients. */
|
|
345
|
+
private async ensureInitialized(): Promise<void> {
|
|
346
|
+
if (this.initialized) return;
|
|
347
|
+
|
|
348
|
+
this.allTools = await this.fetchAllTools();
|
|
349
|
+
await this.index.buildIndex(this.allTools);
|
|
350
|
+
this.buildGroups();
|
|
351
|
+
this.initialized = true;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** Fetch tools from all connected MCP clients. */
|
|
355
|
+
private async fetchAllTools(): Promise<IndexedTool[]> {
|
|
356
|
+
const clients = this.getClients();
|
|
357
|
+
const result: IndexedTool[] = [];
|
|
358
|
+
|
|
359
|
+
for (const client of clients) {
|
|
360
|
+
if (!client.isConnected()) continue;
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const { tools } = await client.listTools();
|
|
364
|
+
const serverId =
|
|
365
|
+
typeof client.getServerId === 'function' ? client.getServerId() ?? 'unknown' : 'unknown';
|
|
366
|
+
const serverName =
|
|
367
|
+
(typeof client.getServerName === 'function' ? client.getServerName() : undefined) ??
|
|
368
|
+
serverId;
|
|
369
|
+
const sessionId =
|
|
370
|
+
typeof client.getSessionId === 'function' ? client.getSessionId() ?? 'unknown' : 'unknown';
|
|
371
|
+
|
|
372
|
+
for (const tool of tools) {
|
|
373
|
+
result.push({
|
|
374
|
+
...tool,
|
|
375
|
+
serverName: serverName,
|
|
376
|
+
sessionId,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
} catch (err) {
|
|
380
|
+
console.warn('[ToolRouter] Failed to fetch tools from client:', err);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Resolve the client input to a flat array of ToolClient instances. */
|
|
388
|
+
private getClients(): ToolClient[] {
|
|
389
|
+
if (Array.isArray(this.client)) {
|
|
390
|
+
return this.client;
|
|
391
|
+
}
|
|
392
|
+
if (typeof (this.client as ToolClientProvider).getClients === 'function') {
|
|
393
|
+
return (this.client as ToolClientProvider).getClients();
|
|
394
|
+
}
|
|
395
|
+
// Single client
|
|
396
|
+
return [this.client as unknown as ToolClient];
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** Build group map from custom config or auto-detect from server names. */
|
|
400
|
+
private buildGroups(): void {
|
|
401
|
+
this.groupsMap.clear();
|
|
402
|
+
|
|
403
|
+
if (this.customGroups) {
|
|
404
|
+
// Explicit groups
|
|
405
|
+
for (const [name, tools] of Object.entries(this.customGroups)) {
|
|
406
|
+
this.groupsMap.set(name, {
|
|
407
|
+
tools,
|
|
408
|
+
active: this.activeGroups.size === 0 || this.activeGroups.has(name),
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
} else {
|
|
412
|
+
// Auto-group by server name
|
|
413
|
+
const serverTools = new Map<string, string[]>();
|
|
414
|
+
for (const tool of this.allTools) {
|
|
415
|
+
const group = tool.serverName;
|
|
416
|
+
if (!serverTools.has(group)) {
|
|
417
|
+
serverTools.set(group, []);
|
|
418
|
+
}
|
|
419
|
+
serverTools.get(group)!.push(tool.name);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
for (const [serverName, tools] of serverTools) {
|
|
423
|
+
this.groupsMap.set(serverName, {
|
|
424
|
+
tools,
|
|
425
|
+
active: this.activeGroups.size === 0 || this.activeGroups.has(serverName),
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** Return only tools belonging to currently active groups. */
|
|
432
|
+
private getGroupFilteredTools(): Tool[] {
|
|
433
|
+
const activeToolNames = new Set<string>();
|
|
434
|
+
for (const [, info] of this.groupsMap) {
|
|
435
|
+
if (info.active) {
|
|
436
|
+
for (const name of info.tools) {
|
|
437
|
+
activeToolNames.add(name);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const filtered = this.allTools.filter((t) => activeToolNames.has(t.name));
|
|
443
|
+
|
|
444
|
+
if (this.compactSchemas) {
|
|
445
|
+
return filtered.slice(0, this.maxTools).map((t) => {
|
|
446
|
+
const compact = SchemaCompressor.toCompact(t);
|
|
447
|
+
return {
|
|
448
|
+
name: compact.name,
|
|
449
|
+
description:
|
|
450
|
+
(compact.description ?? '') +
|
|
451
|
+
(compact.parameterHint ? ` Parameters: ${compact.parameterHint}` : ''),
|
|
452
|
+
inputSchema: { type: 'object' as const, properties: {} },
|
|
453
|
+
};
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return filtered.slice(0, this.maxTools);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** The 4 meta-tool definitions exposed in `search` strategy. */
|
|
461
|
+
private getMetaToolDefinitions(): Tool[] {
|
|
462
|
+
return [
|
|
463
|
+
createSearchToolDefinition(),
|
|
464
|
+
createRegexSearchToolDefinition(),
|
|
465
|
+
createGetSchemaToolDefinition(),
|
|
466
|
+
createExecuteToolDefinition(),
|
|
467
|
+
];
|
|
468
|
+
}
|
|
469
|
+
}
|
package/src/shared/types.ts
CHANGED
|
@@ -4,6 +4,36 @@
|
|
|
4
4
|
|
|
5
5
|
import { Tool, CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
6
6
|
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Core Capability Interfaces
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A client that can list and execute MCP tools.
|
|
13
|
+
*
|
|
14
|
+
* This is the structural interface that `ToolRouter`, adapters, and other
|
|
15
|
+
* consumers use to interact with any MCP client implementation.
|
|
16
|
+
* Both `MCPClient` and `createMcpClient()` satisfy this interface.
|
|
17
|
+
*/
|
|
18
|
+
export interface ToolClient {
|
|
19
|
+
isConnected(): boolean;
|
|
20
|
+
listTools(): Promise<{ tools: Tool[] }>;
|
|
21
|
+
callTool(name: string, args: Record<string, unknown>): Promise<any>;
|
|
22
|
+
getServerId?(): string | undefined;
|
|
23
|
+
getServerName?(): string | undefined;
|
|
24
|
+
getSessionId?(): string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A provider that manages multiple `ToolClient` instances.
|
|
29
|
+
*
|
|
30
|
+
* `MultiSessionClient` satisfies this interface. Pass it directly
|
|
31
|
+
* to `ToolRouter` or adapters to aggregate tools from all connected servers.
|
|
32
|
+
*/
|
|
33
|
+
export interface ToolClientProvider {
|
|
34
|
+
getClients(): ToolClient[];
|
|
35
|
+
}
|
|
36
|
+
|
|
7
37
|
// Connect API types
|
|
8
38
|
export interface ConnectRequest {
|
|
9
39
|
serverUrl: string;
|