@mastra/mcp 0.11.3-alpha.1 → 0.11.3-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.
Files changed (40) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/package.json +16 -3
  3. package/.turbo/turbo-build.log +0 -4
  4. package/eslint.config.js +0 -11
  5. package/integration-tests/node_modules/.bin/tsc +0 -21
  6. package/integration-tests/node_modules/.bin/tsserver +0 -21
  7. package/integration-tests/node_modules/.bin/vitest +0 -21
  8. package/integration-tests/package.json +0 -29
  9. package/integration-tests/src/mastra/agents/weather.ts +0 -34
  10. package/integration-tests/src/mastra/index.ts +0 -15
  11. package/integration-tests/src/mastra/mcp/index.ts +0 -46
  12. package/integration-tests/src/mastra/tools/weather.ts +0 -13
  13. package/integration-tests/src/server.test.ts +0 -238
  14. package/integration-tests/tsconfig.json +0 -13
  15. package/integration-tests/vitest.config.ts +0 -14
  16. package/src/__fixtures__/fire-crawl-complex-schema.ts +0 -1013
  17. package/src/__fixtures__/server-weather.ts +0 -16
  18. package/src/__fixtures__/stock-price.ts +0 -128
  19. package/src/__fixtures__/tools.ts +0 -94
  20. package/src/__fixtures__/weather.ts +0 -269
  21. package/src/client/client.test.ts +0 -585
  22. package/src/client/client.ts +0 -628
  23. package/src/client/configuration.test.ts +0 -856
  24. package/src/client/configuration.ts +0 -468
  25. package/src/client/elicitationActions.ts +0 -26
  26. package/src/client/index.ts +0 -3
  27. package/src/client/promptActions.ts +0 -70
  28. package/src/client/resourceActions.ts +0 -119
  29. package/src/index.ts +0 -2
  30. package/src/server/index.ts +0 -2
  31. package/src/server/promptActions.ts +0 -48
  32. package/src/server/resourceActions.ts +0 -90
  33. package/src/server/server-logging.test.ts +0 -181
  34. package/src/server/server.test.ts +0 -2142
  35. package/src/server/server.ts +0 -1445
  36. package/src/server/types.ts +0 -59
  37. package/tsconfig.build.json +0 -9
  38. package/tsconfig.json +0 -5
  39. package/tsup.config.ts +0 -17
  40. package/vitest.config.ts +0 -8
@@ -1,585 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { createServer } from 'node:http';
3
- import type { Server as HttpServer } from 'node:http';
4
- import type { AddressInfo } from 'node:net';
5
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
- import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
7
- import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
8
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
9
- import { z } from 'zod';
10
-
11
- import { InternalMastraMCPClient } from './client.js';
12
-
13
- async function setupTestServer(withSessionManagement: boolean) {
14
- const httpServer: HttpServer = createServer();
15
- const mcpServer = new McpServer(
16
- { name: 'test-http-server', version: '1.0.0' },
17
- {
18
- capabilities: {
19
- logging: {},
20
- tools: {},
21
- resources: {},
22
- prompts: {},
23
- },
24
- },
25
- );
26
-
27
- mcpServer.tool(
28
- 'greet',
29
- 'A simple greeting tool',
30
- {
31
- name: z.string().describe('Name to greet').default('World'),
32
- },
33
- async ({ name }): Promise<CallToolResult> => {
34
- return {
35
- content: [{ type: 'text', text: `Hello, ${name}!` }],
36
- };
37
- },
38
- );
39
-
40
- mcpServer.resource('test-resource', 'resource://test', () => {
41
- return {
42
- contents: [
43
- {
44
- uri: 'resource://test',
45
- text: 'Hello, world!',
46
- },
47
- ],
48
- };
49
- });
50
-
51
- mcpServer.prompt(
52
- 'greet',
53
- 'A simple greeting prompt',
54
- () => {
55
- return {
56
- prompt: {
57
- name: 'greet',
58
- version: 'v1',
59
- description: 'A simple greeting prompt',
60
- mimeType: 'application/json',
61
- },
62
- messages: [
63
- {
64
- role: 'assistant',
65
- content: { type: 'text', text: `Hello, World!` }
66
- }
67
- ]
68
- };
69
- },
70
- );
71
-
72
- const serverTransport = new StreamableHTTPServerTransport({
73
- sessionIdGenerator: withSessionManagement ? () => randomUUID() : undefined,
74
- });
75
-
76
- await mcpServer.connect(serverTransport);
77
-
78
- httpServer.on('request', async (req, res) => {
79
- await serverTransport.handleRequest(req, res);
80
- });
81
-
82
- const baseUrl = await new Promise<URL>(resolve => {
83
- httpServer.listen(0, '127.0.0.1', () => {
84
- const addr = httpServer.address() as AddressInfo;
85
- resolve(new URL(`http://127.0.0.1:${addr.port}/mcp`));
86
- });
87
- });
88
-
89
- return { httpServer, mcpServer, serverTransport, baseUrl };
90
- }
91
-
92
- describe('MastraMCPClient with Streamable HTTP', () => {
93
- let testServer: {
94
- httpServer: HttpServer;
95
- mcpServer: McpServer;
96
- serverTransport: StreamableHTTPServerTransport;
97
- baseUrl: URL;
98
- };
99
- let client: InternalMastraMCPClient;
100
-
101
- describe('Stateless Mode', () => {
102
- beforeEach(async () => {
103
- testServer = await setupTestServer(false);
104
- client = new InternalMastraMCPClient({
105
- name: 'test-stateless-client',
106
- server: {
107
- url: testServer.baseUrl,
108
- },
109
- });
110
- await client.connect();
111
- });
112
-
113
- afterEach(async () => {
114
- await client?.disconnect().catch(() => {});
115
- await testServer?.mcpServer.close().catch(() => {});
116
- await testServer?.serverTransport.close().catch(() => {});
117
- testServer?.httpServer.close();
118
- });
119
-
120
- it('should connect and list tools', async () => {
121
- const tools = await client.tools();
122
- expect(tools).toHaveProperty('greet');
123
- expect(tools.greet.description).toBe('A simple greeting tool');
124
- });
125
-
126
- it('should call a tool', async () => {
127
- const tools = await client.tools();
128
- const result = await tools.greet.execute({ context: { name: 'Stateless' } });
129
- expect(result).toEqual({ content: [{ type: 'text', text: 'Hello, Stateless!' }] });
130
- });
131
-
132
- it('should list resources', async () => {
133
- const resourcesResult = await client.listResources();
134
- const resources = resourcesResult.resources;
135
- expect(resources).toBeInstanceOf(Array);
136
- const testResource = resources.find((r) => r.uri === 'resource://test');
137
- expect(testResource).toBeDefined();
138
- expect(testResource!.name).toBe('test-resource');
139
- expect(testResource!.uri).toBe('resource://test');
140
-
141
- const readResult = await client.readResource('resource://test');
142
- expect(readResult.contents).toBeInstanceOf(Array);
143
- expect(readResult.contents.length).toBe(1);
144
- expect(readResult.contents[0].text).toBe('Hello, world!');
145
- });
146
-
147
- it('should list prompts', async () => {
148
- const {prompts} = await client.listPrompts();
149
- expect(prompts).toBeInstanceOf(Array);
150
- expect(prompts).toHaveLength(1);
151
- expect(prompts[0]).toHaveProperty('name');
152
- expect(prompts[0]).toHaveProperty('description');
153
- expect(prompts[0].description).toBe('A simple greeting prompt');
154
- });
155
-
156
- it('should get a specific prompt', async () => {
157
- const result = await client.getPrompt({name: 'greet'});
158
- const {prompt, messages} = result;
159
- expect(prompt).toBeDefined();
160
- expect(prompt).toMatchObject({
161
- name: 'greet',
162
- version: 'v1',
163
- description: expect.any(String),
164
- mimeType: 'application/json',
165
- });
166
- expect(messages).toBeDefined();
167
- const messageItem = messages[0];
168
- expect(messageItem.content.text).toBe('Hello, World!');
169
- });
170
- });
171
-
172
- describe('Stateful Mode', () => {
173
- beforeEach(async () => {
174
- testServer = await setupTestServer(true);
175
- client = new InternalMastraMCPClient({
176
- name: 'test-stateful-client',
177
- server: {
178
- url: testServer.baseUrl,
179
- },
180
- });
181
- await client.connect();
182
- });
183
-
184
- afterEach(async () => {
185
- await client?.disconnect().catch(() => {});
186
- await testServer?.mcpServer.close().catch(() => {});
187
- await testServer?.serverTransport.close().catch(() => {});
188
- testServer?.httpServer.close();
189
- });
190
-
191
- it('should connect and list tools', async () => {
192
- const tools = await client.tools();
193
- expect(tools).toHaveProperty('greet');
194
- });
195
-
196
- it('should capture the session ID after connecting', async () => {
197
- // The setupTestServer(true) is configured for stateful mode
198
- // The client should capture the session ID from the server's response
199
- expect(client.sessionId).toBeDefined();
200
- expect(typeof client.sessionId).toBe('string');
201
- expect(client.sessionId?.length).toBeGreaterThan(0);
202
- });
203
-
204
- it('should call a tool', async () => {
205
- const tools = await client.tools();
206
- const result = await tools.greet.execute({ context: { name: 'Stateful' } });
207
- expect(result).toEqual({ content: [{ type: 'text', text: 'Hello, Stateful!' }] });
208
- });
209
- });
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: 'decline' 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('decline');
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
- });
520
-
521
- describe('MastraMCPClient - AuthProvider Tests', () => {
522
- let testServer: {
523
- httpServer: HttpServer;
524
- mcpServer: McpServer;
525
- serverTransport: StreamableHTTPServerTransport;
526
- baseUrl: URL;
527
- };
528
- let client: InternalMastraMCPClient;
529
-
530
- beforeEach(async () => {
531
- testServer = await setupTestServer(false);
532
- });
533
-
534
- afterEach(async () => {
535
- await client?.disconnect().catch(() => {});
536
- await testServer?.mcpServer.close().catch(() => {});
537
- await testServer?.serverTransport.close().catch(() => {});
538
- testServer?.httpServer.close();
539
- });
540
-
541
- it('should accept authProvider field in HTTP server configuration', async () => {
542
- const mockAuthProvider = { test: 'authProvider' } as any;
543
-
544
- client = new InternalMastraMCPClient({
545
- name: 'auth-config-test',
546
- server: {
547
- url: testServer.baseUrl,
548
- authProvider: mockAuthProvider,
549
- },
550
- });
551
-
552
- const serverConfig = (client as any).serverConfig;
553
- expect(serverConfig.authProvider).toBe(mockAuthProvider);
554
- expect(client).toBeDefined();
555
- expect(typeof client).toBe('object');
556
- });
557
-
558
- it('should handle undefined authProvider gracefully', async () => {
559
- client = new InternalMastraMCPClient({
560
- name: 'auth-undefined-test',
561
- server: {
562
- url: testServer.baseUrl,
563
- authProvider: undefined,
564
- },
565
- });
566
-
567
- await client.connect();
568
- const tools = await client.tools();
569
- expect(tools).toHaveProperty('greet');
570
- });
571
-
572
- it('should work without authProvider for HTTP transport (backward compatibility)', async () => {
573
- client = new InternalMastraMCPClient({
574
- name: 'no-auth-http-client',
575
- server: {
576
- url: testServer.baseUrl,
577
- },
578
- });
579
-
580
- await client.connect();
581
- const tools = await client.tools();
582
- expect(tools).toHaveProperty('greet');
583
- });
584
-
585
- });