@mastra/mcp 0.10.5-alpha.1 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/mcp",
3
- "version": "0.10.5-alpha.1",
3
+ "version": "0.10.5-alpha.2",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -51,7 +51,7 @@
51
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.2"
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",
@@ -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
+ });
@@ -14,6 +14,8 @@ import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/p
14
14
  import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
15
15
  import type {
16
16
  ClientCapabilities,
17
+ ElicitRequest,
18
+ ElicitResult,
17
19
  GetPromptResult,
18
20
  ListPromptsResult,
19
21
  LoggingLevel,
@@ -28,12 +30,14 @@ import {
28
30
  ListPromptsResultSchema,
29
31
  GetPromptResultSchema,
30
32
  PromptListChangedNotificationSchema,
33
+ ElicitRequestSchema,
31
34
  } from '@modelcontextprotocol/sdk/types.js';
32
35
 
33
36
  import { asyncExitHook, gracefulExit } from 'exit-hook';
34
37
  import { z } from 'zod';
35
38
  import { convertJsonSchemaToZod } from 'zod-from-json-schema';
36
39
  import type { JSONSchema } from 'zod-from-json-schema';
40
+ import { ElicitationClientActions } from './elicitationActions';
37
41
  import { PromptClientActions } from './promptActions';
38
42
  import { ResourceClientActions } from './resourceActions';
39
43
 
@@ -51,6 +55,9 @@ export interface LogMessage {
51
55
 
52
56
  export type LogHandler = (logMessage: LogMessage) => void;
53
57
 
58
+ // Elicitation handler type
59
+ export type ElicitationHandler = (request: ElicitRequest['params']) => Promise<ElicitResult>;
60
+
54
61
  // Base options common to all server definitions
55
62
  type BaseServerOptions = {
56
63
  logger?: LogHandler;
@@ -130,7 +137,7 @@ export class InternalMastraMCPClient extends MastraBase {
130
137
  private currentOperationContext: RuntimeContext | null = null;
131
138
  public readonly resources: ResourceClientActions;
132
139
  public readonly prompts: PromptClientActions;
133
-
140
+ public readonly elicitation: ElicitationClientActions;
134
141
  constructor({
135
142
  name,
136
143
  version = '1.0.0',
@@ -145,21 +152,25 @@ export class InternalMastraMCPClient extends MastraBase {
145
152
  this.enableServerLogs = server.enableServerLogs ?? true;
146
153
  this.serverConfig = server;
147
154
 
155
+ const clientCapabilities = { ...capabilities, elicitation: {} };
156
+
148
157
  this.client = new Client(
149
158
  {
150
159
  name,
151
160
  version,
152
161
  },
153
162
  {
154
- capabilities,
163
+ capabilities: clientCapabilities,
155
164
  },
156
165
  );
157
166
 
158
167
  // Set up log message capturing
159
168
  this.setupLogging();
160
169
 
170
+
161
171
  this.resources = new ResourceClientActions({ client: this, logger: this.logger });
162
172
  this.prompts = new PromptClientActions({ client: this, logger: this.logger });
173
+ this.elicitation = new ElicitationClientActions({ client: this, logger: this.logger });
163
174
  }
164
175
 
165
176
  /**
@@ -450,6 +461,14 @@ export class InternalMastraMCPClient extends MastraBase {
450
461
  });
451
462
  }
452
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
+
453
472
  private convertInputSchema(
454
473
  inputSchema: Awaited<ReturnType<Client['listTools']>>['tools'][0]['inputSchema'] | JSONSchema,
455
474
  ): z.ZodType {
@@ -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
+ }
@@ -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';