@mastra/mcp 0.11.3-alpha.1 → 0.11.3-alpha.3
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/CHANGELOG.md +22 -0
- package/dist/client/configuration.d.ts +1 -1
- package/dist/client/configuration.d.ts.map +1 -1
- package/dist/client/promptActions.d.ts +1 -1
- package/dist/client/promptActions.d.ts.map +1 -1
- package/dist/index.cjs +223 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +225 -118
- package/dist/index.js.map +1 -1
- package/dist/server/server.d.ts +6 -5
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/types.d.ts +69 -5
- package/dist/server/types.d.ts.map +1 -1
- package/package.json +15 -2
- package/.turbo/turbo-build.log +0 -4
- package/eslint.config.js +0 -11
- package/integration-tests/node_modules/.bin/tsc +0 -21
- package/integration-tests/node_modules/.bin/tsserver +0 -21
- package/integration-tests/node_modules/.bin/vitest +0 -21
- package/integration-tests/package.json +0 -29
- package/integration-tests/src/mastra/agents/weather.ts +0 -34
- package/integration-tests/src/mastra/index.ts +0 -15
- package/integration-tests/src/mastra/mcp/index.ts +0 -46
- package/integration-tests/src/mastra/tools/weather.ts +0 -13
- package/integration-tests/src/server.test.ts +0 -238
- package/integration-tests/tsconfig.json +0 -13
- package/integration-tests/vitest.config.ts +0 -14
- package/src/__fixtures__/fire-crawl-complex-schema.ts +0 -1013
- package/src/__fixtures__/server-weather.ts +0 -16
- package/src/__fixtures__/stock-price.ts +0 -128
- package/src/__fixtures__/tools.ts +0 -94
- package/src/__fixtures__/weather.ts +0 -269
- package/src/client/client.test.ts +0 -585
- package/src/client/client.ts +0 -628
- package/src/client/configuration.test.ts +0 -856
- package/src/client/configuration.ts +0 -468
- package/src/client/elicitationActions.ts +0 -26
- package/src/client/index.ts +0 -3
- package/src/client/promptActions.ts +0 -70
- package/src/client/resourceActions.ts +0 -119
- package/src/index.ts +0 -2
- package/src/server/index.ts +0 -2
- package/src/server/promptActions.ts +0 -48
- package/src/server/resourceActions.ts +0 -90
- package/src/server/server-logging.test.ts +0 -181
- package/src/server/server.test.ts +0 -2142
- package/src/server/server.ts +0 -1445
- package/src/server/types.ts +0 -59
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -5
- package/tsup.config.ts +0 -17
- 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
|
-
});
|