@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.
Files changed (71) hide show
  1. package/README.md +20 -27
  2. package/dist/adapters/agui-adapter.d.mts +16 -0
  3. package/dist/adapters/agui-adapter.d.ts +16 -0
  4. package/dist/adapters/agui-adapter.js +185 -0
  5. package/dist/adapters/agui-adapter.js.map +1 -1
  6. package/dist/adapters/agui-adapter.mjs +185 -0
  7. package/dist/adapters/agui-adapter.mjs.map +1 -1
  8. package/dist/adapters/agui-middleware.d.mts +2 -0
  9. package/dist/adapters/agui-middleware.d.ts +2 -0
  10. package/dist/adapters/agui-middleware.js.map +1 -1
  11. package/dist/adapters/agui-middleware.mjs.map +1 -1
  12. package/dist/adapters/ai-adapter.d.mts +21 -0
  13. package/dist/adapters/ai-adapter.d.ts +21 -0
  14. package/dist/adapters/ai-adapter.js +175 -0
  15. package/dist/adapters/ai-adapter.js.map +1 -1
  16. package/dist/adapters/ai-adapter.mjs +175 -0
  17. package/dist/adapters/ai-adapter.mjs.map +1 -1
  18. package/dist/adapters/langchain-adapter.d.mts +16 -0
  19. package/dist/adapters/langchain-adapter.d.ts +16 -0
  20. package/dist/adapters/langchain-adapter.js +179 -0
  21. package/dist/adapters/langchain-adapter.js.map +1 -1
  22. package/dist/adapters/langchain-adapter.mjs +179 -0
  23. package/dist/adapters/langchain-adapter.mjs.map +1 -1
  24. package/dist/client/index.d.mts +2 -2
  25. package/dist/client/index.d.ts +2 -2
  26. package/dist/client/react.d.mts +14 -7
  27. package/dist/client/react.d.ts +14 -7
  28. package/dist/client/react.js +48 -23
  29. package/dist/client/react.js.map +1 -1
  30. package/dist/client/react.mjs +47 -24
  31. package/dist/client/react.mjs.map +1 -1
  32. package/dist/client/vue.d.mts +4 -4
  33. package/dist/client/vue.d.ts +4 -4
  34. package/dist/{index-CQr9q0bF.d.mts → index-DcYfpY3H.d.mts} +1 -1
  35. package/dist/{index-nE_7Io0I.d.ts → index-GfC_eNEv.d.ts} +1 -1
  36. package/dist/index.d.mts +4 -3
  37. package/dist/index.d.ts +4 -3
  38. package/dist/index.js +883 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/index.mjs +868 -2
  41. package/dist/index.mjs.map +1 -1
  42. package/dist/server/index.d.mts +2 -2
  43. package/dist/server/index.d.ts +2 -2
  44. package/dist/server/index.js +3 -1
  45. package/dist/server/index.js.map +1 -1
  46. package/dist/server/index.mjs +3 -1
  47. package/dist/server/index.mjs.map +1 -1
  48. package/dist/shared/index.d.mts +86 -4
  49. package/dist/shared/index.d.ts +86 -4
  50. package/dist/shared/index.js +874 -0
  51. package/dist/shared/index.js.map +1 -1
  52. package/dist/shared/index.mjs +865 -1
  53. package/dist/shared/index.mjs.map +1 -1
  54. package/dist/tool-router-Bo8qZbsD.d.ts +325 -0
  55. package/dist/tool-router-XnWVxPzv.d.mts +325 -0
  56. package/dist/{types-CW6lghof.d.mts → types-CfCoIsWI.d.mts} +27 -1
  57. package/dist/{types-CW6lghof.d.ts → types-CfCoIsWI.d.ts} +27 -1
  58. package/package.json +3 -2
  59. package/src/adapters/agui-adapter.ts +79 -0
  60. package/src/adapters/ai-adapter.ts +75 -0
  61. package/src/adapters/langchain-adapter.ts +74 -0
  62. package/src/client/react/index.ts +2 -0
  63. package/src/client/react/use-mcp-apps.tsx +50 -32
  64. package/src/server/index.ts +2 -0
  65. package/src/server/mcp/oauth-client.ts +3 -1
  66. package/src/shared/index.ts +36 -0
  67. package/src/shared/meta-tools.ts +387 -0
  68. package/src/shared/schema-compressor.ts +124 -0
  69. package/src/shared/tool-index.ts +499 -0
  70. package/src/shared/tool-router.ts +469 -0
  71. package/src/shared/types.ts +30 -0
@@ -0,0 +1,387 @@
1
+ /**
2
+ * Meta-tools — Injectable tool definitions that let the LLM discover and
3
+ * load MCP tools on-demand, following Anthropic's Tool Search pattern.
4
+ *
5
+ * Instead of injecting 50+ full tool schemas into the context window, you
6
+ * inject just these 4 meta-tools. The LLM calls them to find and load
7
+ * only the tools it actually needs.
8
+ *
9
+ * Meta-tools:
10
+ * • `mcp_search_tool_bm25` — BM25 natural language search
11
+ * • `mcp_search_tool_regex` — Regex pattern search
12
+ * • `mcp_get_tool_schema` — Get full inputSchema for a discovered tool
13
+ * • `mcp_execute_tool` — Execute a discovered tool
14
+ *
15
+ * @packageDocumentation
16
+ */
17
+
18
+ import type { Tool, CallToolResult } from '@modelcontextprotocol/sdk/types.js';
19
+ import type { ToolRouter } from './tool-router.js';
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Tool Definitions
23
+ // ---------------------------------------------------------------------------
24
+
25
+ /**
26
+ * Creates the `mcp_search_tool_bm25` tool definition.
27
+ *
28
+ * This tool lets the LLM search the full catalog of available MCP tools
29
+ * using a BM25 natural-language query. Returns tool names and descriptions
30
+ * without the full inputSchema to save context space.
31
+ */
32
+ export function createSearchToolDefinition(): Tool {
33
+ return {
34
+ name: 'mcp_search_tool_bm25',
35
+ description:
36
+ 'Search the catalog of available tools using BM25 natural language ranking. ' +
37
+ 'Returns tool names, descriptions, and server info. ' +
38
+ 'Use this FIRST to find relevant tools before calling them. ' +
39
+ 'Example queries: "database query", "send email", "github pull request".',
40
+ inputSchema: {
41
+ type: 'object' as const,
42
+ properties: {
43
+ query: {
44
+ type: 'string',
45
+ description: 'Natural language description of the capability you need.',
46
+ },
47
+ limit: {
48
+ type: 'number',
49
+ description: 'Maximum number of results to return (default: 5, max: 20).',
50
+ },
51
+ },
52
+ required: ['query'],
53
+ },
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Creates the `mcp_search_tool_regex` tool definition.
59
+ *
60
+ * Matches Anthropic's tool_search_tool_regex exactly (takes a 'query' regex pattern).
61
+ */
62
+ export function createRegexSearchToolDefinition(): Tool {
63
+ return {
64
+ name: 'mcp_search_tool_regex',
65
+ description:
66
+ 'Search the catalog of available tools using a Python-style regex pattern. ' +
67
+ 'Matches against tool names, descriptions, and parameter descriptions. ' +
68
+ 'Example patterns: "^github_", "weather", "(?i)slack".',
69
+ inputSchema: {
70
+ type: 'object' as const,
71
+ properties: {
72
+ query: {
73
+ type: 'string',
74
+ description: 'Regex pattern to search for (e.g., "^get_.*_data", "database").',
75
+ },
76
+ limit: {
77
+ type: 'number',
78
+ description: 'Maximum number of results to return (default: 5, max: 20).',
79
+ },
80
+ },
81
+ required: ['query'],
82
+ },
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Creates the `mcp_get_tool_schema` tool definition.
88
+ *
89
+ * After discovering tools via `mcp_search_tool_bm25` or
90
+ * `mcp_search_tool_regex`, the LLM calls this to load the full
91
+ * inputSchema for a specific tool so it can construct the correct
92
+ * arguments.
93
+ */
94
+ export function createGetSchemaToolDefinition(): Tool {
95
+ return {
96
+ name: 'mcp_get_tool_schema',
97
+ description:
98
+ 'Get the full input schema (parameters) for a specific tool. ' +
99
+ 'Call this after mcp_search_tool_bm25 to get the parameter details ' +
100
+ 'needed to call a tool correctly.',
101
+ inputSchema: {
102
+ type: 'object' as const,
103
+ properties: {
104
+ toolName: {
105
+ type: 'string',
106
+ description: 'The exact tool name returned by mcp_search_tool_bm25.',
107
+ },
108
+ serverName: {
109
+ type: 'string',
110
+ description:
111
+ 'Optional: The server name provided in mcp_search_tool_bm25. Required if multiple tools have the same name.',
112
+ },
113
+ },
114
+ required: ['toolName'],
115
+ },
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Creates the `mcp_execute_tool` tool definition.
121
+ *
122
+ * This is the execution meta-tool — the LLM calls this to execute any
123
+ * tool discovered via `mcp_search_tool_bm25` or `mcp_search_tool_regex`.
124
+ * The LLM should first call `mcp_get_tool_schema` to know the correct
125
+ * arguments.
126
+ *
127
+ * Instead of registering every real tool with the framework, we proxy
128
+ * all execution through a single meta-tool.
129
+ */
130
+ export function createExecuteToolDefinition(): Tool {
131
+ return {
132
+ name: 'mcp_execute_tool',
133
+ description:
134
+ 'Execute a tool that was discovered via mcp_search_tool_bm25. ' +
135
+ 'You MUST call mcp_get_tool_schema first to know the correct parameters. ' +
136
+ 'Pass the exact tool name and its arguments.',
137
+ inputSchema: {
138
+ type: 'object' as const,
139
+ properties: {
140
+ toolName: {
141
+ type: 'string',
142
+ description: 'The exact tool name from mcp_search_tool_bm25 results.',
143
+ },
144
+ serverName: {
145
+ type: 'string',
146
+ description:
147
+ 'Optional: The server name provided in mcp_search_tool_bm25. Required if multiple tools have the same name.',
148
+ },
149
+ args: {
150
+ type: 'object',
151
+ description:
152
+ "Arguments matching the tool's inputSchema. Omit or pass {} if the tool takes no parameters.",
153
+ additionalProperties: true,
154
+ },
155
+ },
156
+ required: ['toolName'],
157
+ },
158
+ };
159
+ }
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // Meta-tool Executors
163
+ // ---------------------------------------------------------------------------
164
+
165
+ /**
166
+ * Callback for executing a real MCP tool via the correct client.
167
+ * Provided by adapters that wire up client routing.
168
+ */
169
+ export type CallToolFn = (
170
+ toolName: string,
171
+ args: Record<string, unknown>,
172
+ namespace?: string
173
+ ) => Promise<any>;
174
+
175
+ /**
176
+ * Execute a meta-tool call and return the result in MCP CallToolResult format.
177
+ *
178
+ * @param toolName - One of the meta-tool names (mcp_search_tool_bm25, mcp_search_tool_regex, etc.)
179
+ * @param args - The arguments from the LLM's tool call
180
+ * @param router - The ToolRouter to query
181
+ * @param callToolFn - Optional callback for executing real tools (required for mcp_execute_tool)
182
+ * @returns MCP-compatible CallToolResult, or null if this isn't a meta-tool
183
+ */
184
+ export async function executeMetaTool(
185
+ toolName: string,
186
+ args: Record<string, unknown>,
187
+ router: ToolRouter,
188
+ callToolFn?: CallToolFn
189
+ ): Promise<CallToolResult | null> {
190
+ const resolveToolSchema = (name: string, namespace?: string): { tool?: Tool; error?: CallToolResult } => {
191
+ try {
192
+ return { tool: router.getToolSchema(name, namespace) };
193
+ } catch (err) {
194
+ const errorMessage = err instanceof Error ? err.message : String(err);
195
+ return {
196
+ error: {
197
+ content: [{ type: 'text', text: errorMessage }],
198
+ isError: true,
199
+ },
200
+ };
201
+ }
202
+ };
203
+
204
+ switch (toolName) {
205
+ case 'mcp_search_tool_bm25': {
206
+ const query = String(args.query ?? '');
207
+ const limit = Math.min(Number(args.limit) || 5, 20);
208
+
209
+ const results = await router.searchTools(query, limit);
210
+
211
+ const text = results.length === 0
212
+ ? 'No tools found matching your query. Try different keywords.'
213
+ : results
214
+ .map(
215
+ (t, i) =>
216
+ `${i + 1}. **${t.name}** (server: ${t.serverName})\n` +
217
+ ` ${t.description}\n` +
218
+ ` Estimated tokens: ${t.estimatedTokens}`
219
+ )
220
+ .join('\n');
221
+
222
+ return {
223
+ content: [{ type: 'text', text }],
224
+ isError: false,
225
+ };
226
+ }
227
+
228
+ case 'mcp_search_tool_regex': {
229
+ const pattern = String(args.query ?? '');
230
+ const limit = Math.min(Number(args.limit) || 5, 20);
231
+
232
+ const results = await router.searchToolsRegex(pattern, limit);
233
+
234
+ const text = results.length === 0
235
+ ? 'No tools matched your regex pattern. Try a broader pattern.'
236
+ : results
237
+ .map(
238
+ (t, i) =>
239
+ `${i + 1}. **${t.name}** (server: ${t.serverName})\n` +
240
+ ` ${t.description}\n` +
241
+ ` Estimated tokens: ${t.estimatedTokens}`
242
+ )
243
+ .join('\n');
244
+
245
+ return {
246
+ content: [{ type: 'text', text }],
247
+ isError: false,
248
+ };
249
+ }
250
+
251
+ case 'mcp_get_tool_schema': {
252
+ const name = String(args.toolName ?? '');
253
+ const namespace = String(args.serverName ?? '') || undefined;
254
+ const { tool, error } = resolveToolSchema(name, namespace);
255
+
256
+ if (error) {
257
+ return error;
258
+ }
259
+
260
+ if (!tool) {
261
+ return {
262
+ content: [
263
+ {
264
+ type: 'text',
265
+ text: `Tool "${name}" not found. Use mcp_search_tool_bm25 to find available tools first.`,
266
+ },
267
+ ],
268
+ isError: true,
269
+ };
270
+ }
271
+
272
+ const schema = {
273
+ name: tool.name,
274
+ description: tool.description,
275
+ inputSchema: tool.inputSchema,
276
+ };
277
+
278
+ return {
279
+ content: [{ type: 'text', text: JSON.stringify(schema, null, 2) }],
280
+ isError: false,
281
+ };
282
+ }
283
+
284
+ case 'mcp_execute_tool': {
285
+ const targetToolName = String(args.toolName ?? '');
286
+ const namespace = String(args.serverName ?? '') || undefined;
287
+ const toolArgs = (args.args as Record<string, unknown>) ?? {};
288
+
289
+ if (!targetToolName) {
290
+ return {
291
+ content: [{ type: 'text', text: 'Missing required parameter "toolName". Specify which tool to execute.' }],
292
+ isError: true,
293
+ };
294
+ }
295
+
296
+ // Verify the tool exists in our index
297
+ const { tool, error } = resolveToolSchema(targetToolName, namespace);
298
+ if (error) {
299
+ return error;
300
+ }
301
+
302
+ if (!tool) {
303
+ return {
304
+ content: [
305
+ {
306
+ type: 'text',
307
+ text: `Tool "${targetToolName}" not found. Use mcp_search_tool_bm25 to discover available tools first.`,
308
+ },
309
+ ],
310
+ isError: true,
311
+ };
312
+ }
313
+
314
+ if (!callToolFn) {
315
+ return {
316
+ content: [{ type: 'text', text: 'Tool execution is not available. No callToolFn was configured.' }],
317
+ isError: true,
318
+ };
319
+ }
320
+
321
+ try {
322
+ const result = await callToolFn(targetToolName, toolArgs, namespace);
323
+
324
+ // Normalize result to text
325
+ if (result && typeof result === 'object' && 'content' in result) {
326
+ // Already MCP CallToolResult format
327
+ return result as CallToolResult;
328
+ }
329
+
330
+ const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
331
+ return {
332
+ content: [{ type: 'text', text }],
333
+ isError: false,
334
+ };
335
+ } catch (err) {
336
+ const errorMessage = err instanceof Error ? err.message : String(err);
337
+ return {
338
+ content: [{ type: 'text', text: `Tool execution failed: ${errorMessage}` }],
339
+ isError: true,
340
+ };
341
+ }
342
+ }
343
+
344
+ default:
345
+ return null;
346
+ }
347
+ }
348
+
349
+ /** Check if a tool name is one of the meta-tools. */
350
+ export function isMetaTool(toolName: string): boolean {
351
+ return (
352
+ toolName === 'mcp_search_tool_bm25' ||
353
+ toolName === 'mcp_search_tool_regex' ||
354
+ toolName === 'mcp_get_tool_schema' ||
355
+ toolName === 'mcp_execute_tool'
356
+ );
357
+ }
358
+
359
+ /**
360
+ * Unwraps a meta-tool proxy call (like mcp_execute_tool) to find the real target tool name and arguments.
361
+ * Also automatically strips routing prefixes like tool_{serverId}_.
362
+ *
363
+ * Useful for frontend components that need to determine the actual tool being executed by an AI agent.
364
+ */
365
+ export function resolveMetaToolProxy(
366
+ toolName: string,
367
+ args: Record<string, unknown> | null | undefined
368
+ ): { toolName: string; args: Record<string, unknown> } {
369
+ // Unwrap mcp_execute_tool proxy arguments
370
+ if (toolName === 'mcp_execute_tool') {
371
+ const innerName = args?.toolName;
372
+ const innerArgs = args?.args;
373
+ return {
374
+ toolName: typeof innerName === 'string' && innerName ? innerName : toolName,
375
+ args: innerArgs && typeof innerArgs === 'object' && !Array.isArray(innerArgs)
376
+ ? (innerArgs as Record<string, unknown>)
377
+ : {},
378
+ };
379
+ }
380
+
381
+ // Strip tool_<serverId>_ prefix used by AIAdapter
382
+ const match = toolName.match(/(?:tool_[^_]+_)?(.+)$/);
383
+ const resolvedName = match?.[1] ?? toolName;
384
+
385
+ return { toolName: resolvedName, args: args ?? {} };
386
+ }
387
+
@@ -0,0 +1,124 @@
1
+ /**
2
+ * SchemaCompressor — Utilities for reducing tool schema token overhead.
3
+ *
4
+ * Provides compact representations of tools (name + description only,
5
+ * no inputSchema) and token savings estimation.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import type { Tool } from '@modelcontextprotocol/sdk/types.js';
11
+ import { ToolIndex } from './tool-index.js';
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Types
15
+ // ---------------------------------------------------------------------------
16
+
17
+ /**
18
+ * A minimal tool representation containing only what an LLM needs to
19
+ * *decide whether* to use a tool. The full `inputSchema` is deferred.
20
+ */
21
+ export interface CompactTool {
22
+ name: string;
23
+ description?: string;
24
+ /**
25
+ * Human-readable hint about the expected parameters.
26
+ * e.g. "(location: string, unit?: 'celsius' | 'fahrenheit')"
27
+ */
28
+ parameterHint?: string;
29
+ }
30
+
31
+ export interface CompressionStats {
32
+ /** Estimated tokens for the *full* tool list. */
33
+ fullTokens: number;
34
+ /** Estimated tokens for the *compact* tool list. */
35
+ compactTokens: number;
36
+ /** Absolute token savings. */
37
+ savedTokens: number;
38
+ /** Percentage savings as a human-readable string, e.g. "82.3%". */
39
+ savingsPercent: string;
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // SchemaCompressor
44
+ // ---------------------------------------------------------------------------
45
+
46
+ export class SchemaCompressor {
47
+ /**
48
+ * Convert a full MCP Tool definition to a compact summary.
49
+ *
50
+ * The compact form omits `inputSchema` entirely and optionally generates
51
+ * a short `parameterHint` from the schema's top-level properties.
52
+ */
53
+ static toCompact(tool: Tool): CompactTool {
54
+ const compact: CompactTool = {
55
+ name: tool.name,
56
+ description: tool.description,
57
+ };
58
+
59
+ // Build parameter hint from schema
60
+ if (tool.inputSchema && typeof tool.inputSchema === 'object') {
61
+ const schema = tool.inputSchema as {
62
+ properties?: Record<string, { type?: string; enum?: unknown[] }>;
63
+ required?: string[];
64
+ };
65
+
66
+ if (schema.properties) {
67
+ const required = new Set(schema.required ?? []);
68
+ const parts: string[] = [];
69
+
70
+ for (const [key, val] of Object.entries(schema.properties)) {
71
+ const type = val?.type ?? 'any';
72
+ const enumSuffix =
73
+ val?.enum && Array.isArray(val.enum)
74
+ ? `: ${val.enum.map((e) => `'${e}'`).join(' | ')}`
75
+ : `: ${type}`;
76
+
77
+ parts.push(required.has(key) ? `${key}${enumSuffix}` : `${key}?${enumSuffix}`);
78
+ }
79
+
80
+ if (parts.length > 0) {
81
+ compact.parameterHint = `(${parts.join(', ')})`;
82
+ }
83
+ }
84
+ }
85
+
86
+ return compact;
87
+ }
88
+
89
+ /**
90
+ * Convert an array of tools to compact form, optionally limiting the count.
91
+ */
92
+ static compactAll(tools: Tool[], options?: { maxTools?: number }): CompactTool[] {
93
+ const limited = options?.maxTools ? tools.slice(0, options.maxTools) : tools;
94
+ return limited.map((t) => SchemaCompressor.toCompact(t));
95
+ }
96
+
97
+ /**
98
+ * Estimate token savings from using compact vs full tool schemas.
99
+ */
100
+ static estimateSavings(tools: Tool[]): CompressionStats {
101
+ let fullTokens = 0;
102
+ let compactTokens = 0;
103
+
104
+ for (const tool of tools) {
105
+ fullTokens += ToolIndex.estimateTokens(tool);
106
+
107
+ // Compact form: name + description + parameterHint
108
+ const compact = SchemaCompressor.toCompact(tool);
109
+ const text = [compact.name, compact.description ?? '', compact.parameterHint ?? ''].join(' ');
110
+ // Simple estimation for compact: ~4 chars per token for plain text
111
+ compactTokens += Math.ceil(text.length / 4);
112
+ }
113
+
114
+ const saved = fullTokens - compactTokens;
115
+ const pct = fullTokens > 0 ? ((saved / fullTokens) * 100).toFixed(1) : '0.0';
116
+
117
+ return {
118
+ fullTokens,
119
+ compactTokens,
120
+ savedTokens: saved,
121
+ savingsPercent: `${pct}%`,
122
+ };
123
+ }
124
+ }