@mastra/mcp 0.10.5-alpha.0 → 0.10.5-alpha.2
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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +23 -0
- package/dist/_tsup-dts-rollup.d.cts +167 -126
- package/dist/_tsup-dts-rollup.d.ts +167 -126
- package/dist/index.cjs +624 -391
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +620 -387
- package/integration-tests/node_modules/.bin/vitest +2 -2
- package/integration-tests/package.json +2 -2
- package/package.json +7 -7
- package/src/__fixtures__/tools.ts +0 -9
- package/src/client/client.test.ts +310 -1
- package/src/client/client.ts +66 -3
- package/src/client/configuration.ts +22 -2
- package/src/client/elicitationActions.ts +26 -0
- package/src/client/index.ts +1 -1
- package/src/server/server.test.ts +433 -0
- package/src/server/server.ts +431 -429
- package/src/server/types.ts +25 -1
|
@@ -10,9 +10,9 @@ case `uname` in
|
|
|
10
10
|
esac
|
|
11
11
|
|
|
12
12
|
if [ -z "$NODE_PATH" ]; then
|
|
13
|
-
export NODE_PATH="/home/runner/work/mastra/mastra/node_modules/.pnpm/vitest@3.2.
|
|
13
|
+
export NODE_PATH="/home/runner/work/mastra/mastra/node_modules/.pnpm/vitest@3.2.4_@types+debug@4.1.12_@types+node@20.19.1_@vitest+ui@3.2.3_jiti@2.4.2_jsdom@_9ed2428c73777463a3d92e2c0ddae976/node_modules/vitest/node_modules:/home/runner/work/mastra/mastra/node_modules/.pnpm/vitest@3.2.4_@types+debug@4.1.12_@types+node@20.19.1_@vitest+ui@3.2.3_jiti@2.4.2_jsdom@_9ed2428c73777463a3d92e2c0ddae976/node_modules:/home/runner/work/mastra/mastra/node_modules/.pnpm/node_modules"
|
|
14
14
|
else
|
|
15
|
-
export NODE_PATH="/home/runner/work/mastra/mastra/node_modules/.pnpm/vitest@3.2.
|
|
15
|
+
export NODE_PATH="/home/runner/work/mastra/mastra/node_modules/.pnpm/vitest@3.2.4_@types+debug@4.1.12_@types+node@20.19.1_@vitest+ui@3.2.3_jiti@2.4.2_jsdom@_9ed2428c73777463a3d92e2c0ddae976/node_modules/vitest/node_modules:/home/runner/work/mastra/mastra/node_modules/.pnpm/vitest@3.2.4_@types+debug@4.1.12_@types+node@20.19.1_@vitest+ui@3.2.3_jiti@2.4.2_jsdom@_9ed2428c73777463a3d92e2c0ddae976/node_modules:/home/runner/work/mastra/mastra/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
16
|
fi
|
|
17
17
|
if [ -x "$basedir/node" ]; then
|
|
18
18
|
exec "$basedir/node" "$basedir/../vitest/vitest.mjs" "$@"
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
"@mastra/client-js": "workspace:*",
|
|
13
13
|
"@mastra/mcp": "workspace:*",
|
|
14
14
|
"dotenv": "^16.5.0",
|
|
15
|
-
"zod": "^3.25.
|
|
15
|
+
"zod": "^3.25.67"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@mastra/core": "workspace:*",
|
|
19
|
-
"@testing-library/react": "^16.
|
|
19
|
+
"@testing-library/react": "^16.3.0",
|
|
20
20
|
"@types/node": "^20.17.57",
|
|
21
21
|
"get-port": "^7.1.0",
|
|
22
22
|
"mastra": "workspace:*",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/mcp",
|
|
3
|
-
"version": "0.10.5-alpha.
|
|
3
|
+
"version": "0.10.5-alpha.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,11 +22,10 @@
|
|
|
22
22
|
"author": "",
|
|
23
23
|
"license": "Elastic-2.0",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.13.0",
|
|
26
26
|
"date-fns": "^4.1.0",
|
|
27
27
|
"exit-hook": "^4.0.0",
|
|
28
28
|
"fast-deep-equal": "^3.1.3",
|
|
29
|
-
"hono": "^4.7.11",
|
|
30
29
|
"uuid": "^11.1.0",
|
|
31
30
|
"zod-from-json-schema": "^0.0.5"
|
|
32
31
|
},
|
|
@@ -42,16 +41,17 @@
|
|
|
42
41
|
"@microsoft/api-extractor": "^7.52.8",
|
|
43
42
|
"@types/node": "^20.19.0",
|
|
44
43
|
"ai": "4.3.16",
|
|
45
|
-
"eslint": "^9.
|
|
46
|
-
"hono-mcp-server-sse-transport": "0.0.
|
|
44
|
+
"eslint": "^9.29.0",
|
|
45
|
+
"hono-mcp-server-sse-transport": "0.0.7",
|
|
46
|
+
"hono": "^4.7.11",
|
|
47
47
|
"tsup": "^8.5.0",
|
|
48
48
|
"tsx": "^4.19.4",
|
|
49
49
|
"typescript": "^5.8.3",
|
|
50
50
|
"vitest": "^3.2.3",
|
|
51
|
-
"zod": "^3.25.
|
|
51
|
+
"zod": "^3.25.67",
|
|
52
52
|
"zod-to-json-schema": "^3.24.5",
|
|
53
53
|
"@internal/lint": "0.0.13",
|
|
54
|
-
"@mastra/core": "0.10.7-alpha.
|
|
54
|
+
"@mastra/core": "0.10.7-alpha.4"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting",
|
|
@@ -26,15 +26,6 @@ export const weatherTool = createTool({
|
|
|
26
26
|
inputSchema: z.object({
|
|
27
27
|
location: z.string().describe('City name'),
|
|
28
28
|
}),
|
|
29
|
-
outputSchema: z.object({
|
|
30
|
-
temperature: z.number(),
|
|
31
|
-
feelsLike: z.number(),
|
|
32
|
-
humidity: z.number(),
|
|
33
|
-
windSpeed: z.number(),
|
|
34
|
-
windGust: z.number(),
|
|
35
|
-
conditions: z.string(),
|
|
36
|
-
location: z.string(),
|
|
37
|
-
}),
|
|
38
29
|
execute: async ({ context }) => {
|
|
39
30
|
console.log('weather tool', context);
|
|
40
31
|
return await getWeather(context.location);
|
|
@@ -5,7 +5,7 @@ import type { AddressInfo } from 'node:net';
|
|
|
5
5
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
6
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
7
7
|
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
|
|
11
11
|
import { InternalMastraMCPClient } from './client.js';
|
|
@@ -208,3 +208,312 @@ describe('MastraMCPClient with Streamable HTTP', () => {
|
|
|
208
208
|
});
|
|
209
209
|
});
|
|
210
210
|
});
|
|
211
|
+
|
|
212
|
+
describe('MastraMCPClient - Elicitation Tests', () => {
|
|
213
|
+
let testServer: {
|
|
214
|
+
httpServer: HttpServer;
|
|
215
|
+
mcpServer: McpServer;
|
|
216
|
+
serverTransport: StreamableHTTPServerTransport;
|
|
217
|
+
baseUrl: URL;
|
|
218
|
+
};
|
|
219
|
+
let client: InternalMastraMCPClient;
|
|
220
|
+
|
|
221
|
+
beforeEach(async () => {
|
|
222
|
+
testServer = await setupTestServer(false);
|
|
223
|
+
|
|
224
|
+
// Add elicitation-enabled tools to the test server
|
|
225
|
+
testServer.mcpServer.tool(
|
|
226
|
+
'collectUserInfo',
|
|
227
|
+
'Collects user information through elicitation',
|
|
228
|
+
{
|
|
229
|
+
message: z.string().describe('Message to show to user').default('Please provide your information'),
|
|
230
|
+
},
|
|
231
|
+
async ({ message }): Promise<CallToolResult> => {
|
|
232
|
+
const result = await testServer.mcpServer.server.elicitInput({
|
|
233
|
+
message: message,
|
|
234
|
+
requestedSchema: {
|
|
235
|
+
type: 'object',
|
|
236
|
+
properties: {
|
|
237
|
+
name: { type: 'string', title: 'Name' },
|
|
238
|
+
email: { type: 'string', title: 'Email', format: 'email' },
|
|
239
|
+
},
|
|
240
|
+
required: ['name'],
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
246
|
+
};
|
|
247
|
+
},
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
testServer.mcpServer.tool(
|
|
251
|
+
'collectSensitiveInfo',
|
|
252
|
+
'Collects sensitive information that might be rejected',
|
|
253
|
+
{
|
|
254
|
+
message: z.string().describe('Message to show to user').default('Please provide sensitive information'),
|
|
255
|
+
},
|
|
256
|
+
async ({ message }): Promise<CallToolResult> => {
|
|
257
|
+
const result = await testServer.mcpServer.server.elicitInput({
|
|
258
|
+
message: message,
|
|
259
|
+
requestedSchema: {
|
|
260
|
+
type: 'object',
|
|
261
|
+
properties: {
|
|
262
|
+
ssn: { type: 'string', title: 'Social Security Number' },
|
|
263
|
+
creditCard: { type: 'string', title: 'Credit Card Number' },
|
|
264
|
+
},
|
|
265
|
+
required: ['ssn'],
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
271
|
+
};
|
|
272
|
+
},
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
testServer.mcpServer.tool(
|
|
276
|
+
'collectOptionalInfo',
|
|
277
|
+
'Collects optional information that might be cancelled',
|
|
278
|
+
{
|
|
279
|
+
message: z.string().describe('Message to show to user').default('Optional information request'),
|
|
280
|
+
},
|
|
281
|
+
async ({ message }): Promise<CallToolResult> => {
|
|
282
|
+
const result = await testServer.mcpServer.server.elicitInput({
|
|
283
|
+
message: message,
|
|
284
|
+
requestedSchema: {
|
|
285
|
+
type: 'object',
|
|
286
|
+
properties: {
|
|
287
|
+
feedback: { type: 'string', title: 'Feedback' },
|
|
288
|
+
rating: { type: 'number', title: 'Rating', minimum: 1, maximum: 5 },
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
afterEach(async () => {
|
|
301
|
+
await client?.disconnect().catch(() => {});
|
|
302
|
+
await testServer?.mcpServer.close().catch(() => {});
|
|
303
|
+
await testServer?.serverTransport.close().catch(() => {});
|
|
304
|
+
testServer?.httpServer.close();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should handle elicitation request with accept response', async () => {
|
|
308
|
+
const mockHandler = vi.fn(async (request) => {
|
|
309
|
+
expect(request.message).toBe('Please provide your information');
|
|
310
|
+
expect(request.requestedSchema).toBeDefined();
|
|
311
|
+
expect(request.requestedSchema.properties.name).toBeDefined();
|
|
312
|
+
expect(request.requestedSchema.properties.email).toBeDefined();
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
action: 'accept' as const,
|
|
316
|
+
content: {
|
|
317
|
+
name: 'John Doe',
|
|
318
|
+
email: 'john@example.com',
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
client = new InternalMastraMCPClient({
|
|
324
|
+
name: 'elicitation-accept-client',
|
|
325
|
+
server: {
|
|
326
|
+
url: testServer.baseUrl,
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
client.elicitation.onRequest(mockHandler);
|
|
330
|
+
await client.connect();
|
|
331
|
+
|
|
332
|
+
// Get the tools and call the elicitation tool
|
|
333
|
+
const tools = await client.tools();
|
|
334
|
+
const collectUserInfoTool = tools['collectUserInfo'];
|
|
335
|
+
expect(collectUserInfoTool).toBeDefined();
|
|
336
|
+
|
|
337
|
+
// Call the tool which will trigger elicitation
|
|
338
|
+
const result = await collectUserInfoTool.execute({
|
|
339
|
+
context: { message: 'Please provide your information' }
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
console.log('result', result);
|
|
343
|
+
|
|
344
|
+
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
345
|
+
expect(result.content).toBeDefined();
|
|
346
|
+
expect(result.content[0].type).toBe('text');
|
|
347
|
+
|
|
348
|
+
const elicitationResult = JSON.parse(result.content[0].text);
|
|
349
|
+
expect(elicitationResult.action).toBe('accept');
|
|
350
|
+
expect(elicitationResult.content).toEqual({
|
|
351
|
+
name: 'John Doe',
|
|
352
|
+
email: 'john@example.com',
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('should handle elicitation request with reject response', async () => {
|
|
357
|
+
const mockHandler = vi.fn(async (request) => {
|
|
358
|
+
expect(request.message).toBe('Please provide sensitive information');
|
|
359
|
+
return { action: 'reject' as const };
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
client = new InternalMastraMCPClient({
|
|
363
|
+
name: 'elicitation-reject-client',
|
|
364
|
+
server: {
|
|
365
|
+
url: testServer.baseUrl,
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
client.elicitation.onRequest(mockHandler);
|
|
369
|
+
await client.connect();
|
|
370
|
+
|
|
371
|
+
// Get the tools and call the sensitive info tool
|
|
372
|
+
const tools = await client.tools();
|
|
373
|
+
const collectSensitiveInfoTool = tools['collectSensitiveInfo'];
|
|
374
|
+
expect(collectSensitiveInfoTool).toBeDefined();
|
|
375
|
+
|
|
376
|
+
// Call the tool which will trigger elicitation
|
|
377
|
+
const result = await collectSensitiveInfoTool.execute({
|
|
378
|
+
context: { message: 'Please provide sensitive information' }
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
382
|
+
expect(result.content).toBeDefined();
|
|
383
|
+
expect(result.content[0].type).toBe('text');
|
|
384
|
+
|
|
385
|
+
const elicitationResult = JSON.parse(result.content[0].text);
|
|
386
|
+
expect(elicitationResult.action).toBe('reject');
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should handle elicitation request with cancel response', async () => {
|
|
390
|
+
const mockHandler = vi.fn(async (_request) => {
|
|
391
|
+
return { action: 'cancel' as const };
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
client = new InternalMastraMCPClient({
|
|
395
|
+
name: 'elicitation-cancel-client',
|
|
396
|
+
server: {
|
|
397
|
+
url: testServer.baseUrl,
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
client.elicitation.onRequest(mockHandler);
|
|
401
|
+
await client.connect();
|
|
402
|
+
|
|
403
|
+
// Get the tools and call the optional info tool
|
|
404
|
+
const tools = await client.tools();
|
|
405
|
+
const collectOptionalInfoTool = tools['collectOptionalInfo'];
|
|
406
|
+
expect(collectOptionalInfoTool).toBeDefined();
|
|
407
|
+
|
|
408
|
+
// Call the tool which will trigger elicitation
|
|
409
|
+
const result = await collectOptionalInfoTool.execute({
|
|
410
|
+
context: { message: 'Optional information request' }
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
414
|
+
expect(result.content).toBeDefined();
|
|
415
|
+
expect(result.content[0].type).toBe('text');
|
|
416
|
+
|
|
417
|
+
const elicitationResult = JSON.parse(result.content[0].text);
|
|
418
|
+
expect(elicitationResult.action).toBe('cancel');
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('should return an error when elicitation handler throws error', async () => {
|
|
422
|
+
const mockHandler = vi.fn(async (_request) => {
|
|
423
|
+
throw new Error('Handler failed');
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
client = new InternalMastraMCPClient({
|
|
427
|
+
name: 'elicitation-error-client',
|
|
428
|
+
server: {
|
|
429
|
+
url: testServer.baseUrl,
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
client.elicitation.onRequest(mockHandler);
|
|
433
|
+
await client.connect();
|
|
434
|
+
|
|
435
|
+
// Get the tools and call a tool that will trigger elicitation
|
|
436
|
+
const tools = await client.tools();
|
|
437
|
+
const collectUserInfoTool = tools['collectUserInfo'];
|
|
438
|
+
expect(collectUserInfoTool).toBeDefined();
|
|
439
|
+
|
|
440
|
+
// Call the tool which will trigger elicitation, handler will throw error
|
|
441
|
+
const result = await collectUserInfoTool.execute({
|
|
442
|
+
context: { message: 'This will cause handler to throw' }
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
446
|
+
expect(result.content).toBeDefined();
|
|
447
|
+
|
|
448
|
+
expect(result.isError).toBe(true);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('should return an error when client has no elicitation handler', async () => {
|
|
452
|
+
client = new InternalMastraMCPClient({
|
|
453
|
+
name: 'no-elicitation-client',
|
|
454
|
+
server: {
|
|
455
|
+
url: testServer.baseUrl,
|
|
456
|
+
// No elicitationHandler provided
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
await client.connect();
|
|
460
|
+
|
|
461
|
+
// Get the tools and call a tool that will trigger elicitation
|
|
462
|
+
const tools = await client.tools();
|
|
463
|
+
const collectUserInfoTool = tools['collectUserInfo'];
|
|
464
|
+
expect(collectUserInfoTool).toBeDefined();
|
|
465
|
+
|
|
466
|
+
// Call the tool which will trigger elicitation, should fail gracefully
|
|
467
|
+
const result = await collectUserInfoTool.execute({
|
|
468
|
+
context: { message: 'This should fail gracefully' }
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
expect(result.content).toBeDefined();
|
|
472
|
+
expect(result.isError).toBe(true);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('should validate elicitation request schema structure', async () => {
|
|
476
|
+
const mockHandler = vi.fn(async (request) => {
|
|
477
|
+
// Verify the request has the expected structure
|
|
478
|
+
expect(request).toHaveProperty('message');
|
|
479
|
+
expect(request).toHaveProperty('requestedSchema');
|
|
480
|
+
expect(typeof request.message).toBe('string');
|
|
481
|
+
expect(typeof request.requestedSchema).toBe('object');
|
|
482
|
+
expect(request.requestedSchema).toHaveProperty('type', 'object');
|
|
483
|
+
expect(request.requestedSchema).toHaveProperty('properties');
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
action: 'accept' as const,
|
|
487
|
+
content: { validated: true },
|
|
488
|
+
};
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
client = new InternalMastraMCPClient({
|
|
492
|
+
name: 'schema-validation-client',
|
|
493
|
+
server: {
|
|
494
|
+
url: testServer.baseUrl,
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
client.elicitation.onRequest(mockHandler);
|
|
498
|
+
await client.connect();
|
|
499
|
+
|
|
500
|
+
// Get the tools and call a tool that will trigger elicitation
|
|
501
|
+
const tools = await client.tools();
|
|
502
|
+
const collectUserInfoTool = tools['collectUserInfo'];
|
|
503
|
+
expect(collectUserInfoTool).toBeDefined();
|
|
504
|
+
|
|
505
|
+
// Call the tool which will trigger elicitation with schema validation
|
|
506
|
+
const result = await collectUserInfoTool.execute({
|
|
507
|
+
context: { message: 'Schema validation test' }
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
console.log('result', result);
|
|
511
|
+
|
|
512
|
+
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
513
|
+
expect(result.content).toBeDefined();
|
|
514
|
+
expect(result.content[0].type).toBe('text');
|
|
515
|
+
|
|
516
|
+
const elicitationResultText = result.content[0].text;
|
|
517
|
+
expect(elicitationResultText).toContain('Elicitation response content does not match requested schema');
|
|
518
|
+
});
|
|
519
|
+
});
|
package/src/client/client.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MastraBase } from '@mastra/core/base';
|
|
2
2
|
|
|
3
3
|
import type { RuntimeContext } from '@mastra/core/di';
|
|
4
|
+
import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
|
|
4
5
|
import { createTool } from '@mastra/core/tools';
|
|
5
6
|
import { isZodType } from '@mastra/core/utils';
|
|
6
7
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
@@ -13,6 +14,8 @@ import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/p
|
|
|
13
14
|
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
14
15
|
import type {
|
|
15
16
|
ClientCapabilities,
|
|
17
|
+
ElicitRequest,
|
|
18
|
+
ElicitResult,
|
|
16
19
|
GetPromptResult,
|
|
17
20
|
ListPromptsResult,
|
|
18
21
|
LoggingLevel,
|
|
@@ -27,12 +30,14 @@ import {
|
|
|
27
30
|
ListPromptsResultSchema,
|
|
28
31
|
GetPromptResultSchema,
|
|
29
32
|
PromptListChangedNotificationSchema,
|
|
33
|
+
ElicitRequestSchema,
|
|
30
34
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
31
35
|
|
|
32
36
|
import { asyncExitHook, gracefulExit } from 'exit-hook';
|
|
33
37
|
import { z } from 'zod';
|
|
34
38
|
import { convertJsonSchemaToZod } from 'zod-from-json-schema';
|
|
35
39
|
import type { JSONSchema } from 'zod-from-json-schema';
|
|
40
|
+
import { ElicitationClientActions } from './elicitationActions';
|
|
36
41
|
import { PromptClientActions } from './promptActions';
|
|
37
42
|
import { ResourceClientActions } from './resourceActions';
|
|
38
43
|
|
|
@@ -50,6 +55,9 @@ export interface LogMessage {
|
|
|
50
55
|
|
|
51
56
|
export type LogHandler = (logMessage: LogMessage) => void;
|
|
52
57
|
|
|
58
|
+
// Elicitation handler type
|
|
59
|
+
export type ElicitationHandler = (request: ElicitRequest['params']) => Promise<ElicitResult>;
|
|
60
|
+
|
|
53
61
|
// Base options common to all server definitions
|
|
54
62
|
type BaseServerOptions = {
|
|
55
63
|
logger?: LogHandler;
|
|
@@ -129,7 +137,7 @@ export class InternalMastraMCPClient extends MastraBase {
|
|
|
129
137
|
private currentOperationContext: RuntimeContext | null = null;
|
|
130
138
|
public readonly resources: ResourceClientActions;
|
|
131
139
|
public readonly prompts: PromptClientActions;
|
|
132
|
-
|
|
140
|
+
public readonly elicitation: ElicitationClientActions;
|
|
133
141
|
constructor({
|
|
134
142
|
name,
|
|
135
143
|
version = '1.0.0',
|
|
@@ -144,21 +152,25 @@ export class InternalMastraMCPClient extends MastraBase {
|
|
|
144
152
|
this.enableServerLogs = server.enableServerLogs ?? true;
|
|
145
153
|
this.serverConfig = server;
|
|
146
154
|
|
|
155
|
+
const clientCapabilities = { ...capabilities, elicitation: {} };
|
|
156
|
+
|
|
147
157
|
this.client = new Client(
|
|
148
158
|
{
|
|
149
159
|
name,
|
|
150
160
|
version,
|
|
151
161
|
},
|
|
152
162
|
{
|
|
153
|
-
capabilities,
|
|
163
|
+
capabilities: clientCapabilities,
|
|
154
164
|
},
|
|
155
165
|
);
|
|
156
166
|
|
|
157
167
|
// Set up log message capturing
|
|
158
168
|
this.setupLogging();
|
|
159
169
|
|
|
170
|
+
|
|
160
171
|
this.resources = new ResourceClientActions({ client: this, logger: this.logger });
|
|
161
172
|
this.prompts = new PromptClientActions({ client: this, logger: this.logger });
|
|
173
|
+
this.elicitation = new ElicitationClientActions({ client: this, logger: this.logger });
|
|
162
174
|
}
|
|
163
175
|
|
|
164
176
|
/**
|
|
@@ -449,6 +461,14 @@ export class InternalMastraMCPClient extends MastraBase {
|
|
|
449
461
|
});
|
|
450
462
|
}
|
|
451
463
|
|
|
464
|
+
setElicitationRequestHandler(handler: ElicitationHandler): void {
|
|
465
|
+
this.log('debug', 'Setting elicitation request handler');
|
|
466
|
+
this.client.setRequestHandler(ElicitRequestSchema, async (request) => {
|
|
467
|
+
this.log('debug', `Received elicitation request: ${request.params.message}`);
|
|
468
|
+
return handler(request.params);
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
452
472
|
private convertInputSchema(
|
|
453
473
|
inputSchema: Awaited<ReturnType<Client['listTools']>>['tools'][0]['inputSchema'] | JSONSchema,
|
|
454
474
|
): z.ZodType {
|
|
@@ -475,7 +495,48 @@ export class InternalMastraMCPClient extends MastraBase {
|
|
|
475
495
|
originalJsonSchema: inputSchema,
|
|
476
496
|
});
|
|
477
497
|
|
|
478
|
-
throw new
|
|
498
|
+
throw new MastraError({
|
|
499
|
+
id: 'MCP_TOOL_INPUT_SCHEMA_CONVERSION_FAILED',
|
|
500
|
+
domain: ErrorDomain.MCP,
|
|
501
|
+
category: ErrorCategory.USER,
|
|
502
|
+
details: { error: errorDetails ?? 'Unknown error' },
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
private convertOutputSchema(
|
|
508
|
+
outputSchema: Awaited<ReturnType<Client['listTools']>>['tools'][0]['outputSchema'] | JSONSchema,
|
|
509
|
+
): z.ZodType | undefined {
|
|
510
|
+
if (!outputSchema) return
|
|
511
|
+
if (isZodType(outputSchema)) {
|
|
512
|
+
return outputSchema;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
try {
|
|
516
|
+
return convertJsonSchemaToZod(outputSchema as JSONSchema);
|
|
517
|
+
} catch (error: unknown) {
|
|
518
|
+
let errorDetails: string | undefined;
|
|
519
|
+
if (error instanceof Error) {
|
|
520
|
+
errorDetails = error.stack;
|
|
521
|
+
} else {
|
|
522
|
+
// Attempt to stringify, fallback to String()
|
|
523
|
+
try {
|
|
524
|
+
errorDetails = JSON.stringify(error);
|
|
525
|
+
} catch {
|
|
526
|
+
errorDetails = String(error);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
this.log('error', 'Failed to convert JSON schema to Zod schema using zodFromJsonSchema', {
|
|
530
|
+
error: errorDetails,
|
|
531
|
+
originalJsonSchema: outputSchema,
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
throw new MastraError({
|
|
535
|
+
id: 'MCP_TOOL_OUTPUT_SCHEMA_CONVERSION_FAILED',
|
|
536
|
+
domain: ErrorDomain.MCP,
|
|
537
|
+
category: ErrorCategory.USER,
|
|
538
|
+
details: { error: errorDetails ?? 'Unknown error' },
|
|
539
|
+
});
|
|
479
540
|
}
|
|
480
541
|
}
|
|
481
542
|
|
|
@@ -490,6 +551,7 @@ export class InternalMastraMCPClient extends MastraBase {
|
|
|
490
551
|
id: `${this.name}_${tool.name}`,
|
|
491
552
|
description: tool.description || '',
|
|
492
553
|
inputSchema: this.convertInputSchema(tool.inputSchema),
|
|
554
|
+
outputSchema: this.convertOutputSchema(tool.outputSchema),
|
|
493
555
|
execute: async ({ context, runtimeContext }: { context: any; runtimeContext?: RuntimeContext | null }) => {
|
|
494
556
|
const previousContext = this.currentOperationContext;
|
|
495
557
|
this.currentOperationContext = runtimeContext || null; // Set current context
|
|
@@ -505,6 +567,7 @@ export class InternalMastraMCPClient extends MastraBase {
|
|
|
505
567
|
timeout: this.timeout,
|
|
506
568
|
},
|
|
507
569
|
);
|
|
570
|
+
|
|
508
571
|
this.log('debug', `Tool executed successfully: ${tool.name}`);
|
|
509
572
|
return res;
|
|
510
573
|
} catch (e) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MastraBase } from '@mastra/core/base';
|
|
2
2
|
import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
|
|
3
3
|
import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
4
|
-
import type { Prompt, Resource, ResourceTemplate } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import type { ElicitRequest, ElicitResult, Prompt, Resource, ResourceTemplate } from '@modelcontextprotocol/sdk/types.js';
|
|
5
5
|
import equal from 'fast-deep-equal';
|
|
6
6
|
import { v5 as uuidv5 } from 'uuid';
|
|
7
7
|
import { InternalMastraMCPClient } from './client';
|
|
@@ -64,6 +64,26 @@ To fix this you have three different options:
|
|
|
64
64
|
this.addToInstanceCache();
|
|
65
65
|
return this;
|
|
66
66
|
}
|
|
67
|
+
public get elicitation() {
|
|
68
|
+
this.addToInstanceCache();
|
|
69
|
+
return {
|
|
70
|
+
onRequest: async (serverName: string, handler: (request: ElicitRequest['params']) => Promise<ElicitResult>) => {
|
|
71
|
+
try {
|
|
72
|
+
const internalClient = await this.getConnectedClientForServer(serverName);
|
|
73
|
+
return internalClient.elicitation.onRequest(handler);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
throw new MastraError({
|
|
76
|
+
id: 'MCP_CLIENT_ON_REQUEST_ELICITATION_FAILED',
|
|
77
|
+
domain: ErrorDomain.MCP,
|
|
78
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
79
|
+
details: {
|
|
80
|
+
serverName,
|
|
81
|
+
}
|
|
82
|
+
}, err);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
67
87
|
|
|
68
88
|
public get resources() {
|
|
69
89
|
this.addToInstanceCache();
|
|
@@ -356,7 +376,7 @@ To fix this you have three different options:
|
|
|
356
376
|
const existingClient = this.mcpClientsById.get(name);
|
|
357
377
|
|
|
358
378
|
this.logger.debug(`getConnectedClient ${name} exists: ${exists}`);
|
|
359
|
-
|
|
379
|
+
|
|
360
380
|
if (exists) {
|
|
361
381
|
// This is just to satisfy Typescript since technically you could have this.mcpClientsById.set('someKey', undefined);
|
|
362
382
|
// Should never reach this point basically we always create a new MastraMCPClient instance when we add to the Map.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { IMastraLogger } from "@mastra/core/logger";
|
|
2
|
+
import type { ElicitRequest, ElicitResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import type { InternalMastraMCPClient } from "./client";
|
|
4
|
+
|
|
5
|
+
interface ElicitationClientActionsConfig {
|
|
6
|
+
client: InternalMastraMCPClient;
|
|
7
|
+
logger: IMastraLogger;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class ElicitationClientActions {
|
|
11
|
+
private readonly client: InternalMastraMCPClient;
|
|
12
|
+
private readonly logger: IMastraLogger;
|
|
13
|
+
|
|
14
|
+
constructor({ client, logger }: ElicitationClientActionsConfig) {
|
|
15
|
+
this.client = client;
|
|
16
|
+
this.logger = logger;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Set a handler for elicitation requests.
|
|
21
|
+
* @param handler The callback function to handle the elicitation request.
|
|
22
|
+
*/
|
|
23
|
+
public onRequest(handler: (request: ElicitRequest['params']) => Promise<ElicitResult>): void {
|
|
24
|
+
this.client.setElicitationRequestHandler(handler);
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/client/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type { LoggingLevel, LogMessage, LogHandler, MastraMCPServerDefinition } from './client';
|
|
1
|
+
export type { LoggingLevel, LogMessage, LogHandler, MastraMCPServerDefinition, ElicitationHandler } from './client';
|
|
2
2
|
export { MastraMCPClient } from './client';
|
|
3
3
|
export * from './configuration';
|