@mastra/mcp 0.10.2 → 0.10.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.
@@ -11,7 +11,7 @@ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/
11
11
  import type { StreamableHTTPClientTransportOptions } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
12
12
  import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
13
13
  import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
14
- import type { ClientCapabilities, LoggingLevel } from '@modelcontextprotocol/sdk/types.js';
14
+ import type { ClientCapabilities, GetPromptResult, ListPromptsResult, LoggingLevel } from '@modelcontextprotocol/sdk/types.js';
15
15
  import {
16
16
  CallToolResultSchema,
17
17
  ListResourcesResultSchema,
@@ -19,12 +19,16 @@ import {
19
19
  ResourceListChangedNotificationSchema,
20
20
  ResourceUpdatedNotificationSchema,
21
21
  ListResourceTemplatesResultSchema,
22
+ ListPromptsResultSchema,
23
+ GetPromptResultSchema,
24
+ PromptListChangedNotificationSchema,
22
25
  } from '@modelcontextprotocol/sdk/types.js';
23
26
 
24
27
  import { asyncExitHook, gracefulExit } from 'exit-hook';
25
28
  import { z } from 'zod';
26
29
  import { convertJsonSchemaToZod } from 'zod-from-json-schema';
27
30
  import type { JSONSchema } from 'zod-from-json-schema';
31
+ import { PromptClientActions } from './promptActions';
28
32
  import { ResourceClientActions } from './resourceActions';
29
33
 
30
34
  // Re-export MCP SDK LoggingLevel for convenience
@@ -119,6 +123,7 @@ export class InternalMastraMCPClient extends MastraBase {
119
123
  private transport?: Transport;
120
124
  private currentOperationContext: RuntimeContext | null = null;
121
125
  public readonly resources: ResourceClientActions;
126
+ public readonly prompts: PromptClientActions;
122
127
 
123
128
  constructor({
124
129
  name,
@@ -148,6 +153,7 @@ export class InternalMastraMCPClient extends MastraBase {
148
153
  this.setupLogging();
149
154
 
150
155
  this.resources = new ResourceClientActions({ client: this, logger: this.logger });
156
+ this.prompts = new PromptClientActions({ client: this, logger: this.logger });
151
157
  }
152
158
 
153
159
  /**
@@ -363,6 +369,42 @@ export class InternalMastraMCPClient extends MastraBase {
363
369
  });
364
370
  }
365
371
 
372
+ /**
373
+ * Fetch the list of available prompts from the MCP server.
374
+ */
375
+ async listPrompts(): Promise<ListPromptsResult> {
376
+ this.log('debug', `Requesting prompts from MCP server`);
377
+ return await this.client.request({ method: 'prompts/list' }, ListPromptsResultSchema, {
378
+ timeout: this.timeout,
379
+ });
380
+ }
381
+
382
+ /**
383
+ * Get a prompt and its dynamic messages from the server.
384
+ * @param name The prompt name
385
+ * @param args Arguments for the prompt
386
+ * @param version (optional) The prompt version to retrieve
387
+ */
388
+ async getPrompt({ name, args, version }: { name: string; args?: Record<string, any>; version?: string }): Promise<GetPromptResult> {
389
+ this.log('debug', `Requesting prompt from MCP server: ${name}`);
390
+ return await this.client.request(
391
+ { method: 'prompts/get', params: { name, arguments: args, version } },
392
+ GetPromptResultSchema,
393
+ { timeout: this.timeout }
394
+ );
395
+ }
396
+
397
+ /**
398
+ * Register a handler to be called when the prompt list changes on the server.
399
+ * Use this to refresh cached prompt lists in the client/UI if needed.
400
+ */
401
+ setPromptListChangedNotificationHandler(handler: () => void): void {
402
+ this.log('debug', 'Setting prompt list changed notification handler');
403
+ this.client.setNotificationHandler(PromptListChangedNotificationSchema, () => {
404
+ handler();
405
+ });
406
+ }
407
+
366
408
  setResourceUpdatedNotificationHandler(
367
409
  handler: (params: z.infer<typeof ResourceUpdatedNotificationSchema>['params']) => void,
368
410
  ): void {
@@ -81,219 +81,317 @@ describe('MCPClient', () => {
81
81
  weatherProcess.kill('SIGINT');
82
82
  });
83
83
 
84
- it('should initialize with server configurations', () => {
85
- expect(mcp['serverConfigs']).toEqual({
86
- stockPrice: {
87
- command: 'npx',
88
- args: ['-y', 'tsx', path.join(__dirname, '..', '__fixtures__/stock-price.ts')],
84
+ describe('Instance Management', () => {
85
+ it('should initialize with server configurations', () => {
86
+ expect(mcp['serverConfigs']).toEqual({
87
+ stockPrice: {
88
+ command: 'npx',
89
+ args: ['-y', 'tsx', path.join(__dirname, '..', '__fixtures__/stock-price.ts')],
89
90
  env: {
90
91
  FAKE_CREDS: 'test',
91
92
  },
92
- },
93
- weather: {
94
- url: new URL(`http://localhost:${weatherServerPort}/sse`),
95
- },
93
+ },
94
+ weather: {
95
+ url: new URL(`http://localhost:${weatherServerPort}/sse`),
96
+ },
97
+ });
96
98
  });
97
- });
98
99
 
99
- it('should get connected tools with namespaced tool names', async () => {
100
- const connectedTools = await mcp.getTools();
100
+ it('should get connected tools with namespaced tool names', async () => {
101
+ const connectedTools = await mcp.getTools();
101
102
 
102
- // Each tool should be namespaced with its server name
103
- expect(connectedTools).toHaveProperty('stockPrice_getStockPrice');
104
- expect(connectedTools).toHaveProperty('weather_getWeather');
105
- });
103
+ // Each tool should be namespaced with its server name
104
+ expect(connectedTools).toHaveProperty('stockPrice_getStockPrice');
105
+ expect(connectedTools).toHaveProperty('weather_getWeather');
106
+ });
106
107
 
107
- it('should get connected toolsets grouped by server', async () => {
108
- const connectedToolsets = await mcp.getToolsets();
108
+ it('should get connected toolsets grouped by server', async () => {
109
+ const connectedToolsets = await mcp.getToolsets();
109
110
 
110
- expect(connectedToolsets).toHaveProperty('stockPrice');
111
- expect(connectedToolsets).toHaveProperty('weather');
112
- expect(connectedToolsets.stockPrice).toHaveProperty('getStockPrice');
113
- expect(connectedToolsets.weather).toHaveProperty('getWeather');
111
+ expect(connectedToolsets).toHaveProperty('stockPrice');
112
+ expect(connectedToolsets).toHaveProperty('weather');
113
+ expect(connectedToolsets.stockPrice).toHaveProperty('getStockPrice');
114
+ expect(connectedToolsets.weather).toHaveProperty('getWeather');
115
+ });
114
116
  });
115
117
 
116
- it('should get resources from connected MCP servers', async () => {
117
- const resources = await mcp.resources.list();
118
-
119
- expect(resources).toHaveProperty('weather');
120
- expect(resources.weather).toBeDefined();
121
- expect(resources.weather).toHaveLength(3);
122
-
123
- // Verify that each expected resource exists with the correct structure
124
- const weatherResources = resources.weather;
125
- const currentWeather = weatherResources.find(r => r.uri === 'weather://current');
126
- expect(currentWeather).toBeDefined();
127
- expect(currentWeather).toMatchObject({
128
- uri: 'weather://current',
129
- name: 'Current Weather Data',
130
- description: expect.any(String),
131
- mimeType: 'application/json',
118
+ describe('Resources', () => {
119
+ it('should get resources from connected MCP servers', async () => {
120
+ const resources = await mcp.resources.list();
121
+
122
+ expect(resources).toHaveProperty('weather');
123
+ expect(resources.weather).toBeDefined();
124
+ expect(resources.weather).toHaveLength(3);
125
+
126
+ // Verify that each expected resource exists with the correct structure
127
+ const weatherResources = resources.weather;
128
+ const currentWeather = weatherResources.find(r => r.uri === 'weather://current');
129
+ expect(currentWeather).toBeDefined();
130
+ expect(currentWeather).toMatchObject({
131
+ uri: 'weather://current',
132
+ name: 'Current Weather Data',
133
+ description: expect.any(String),
134
+ mimeType: 'application/json',
135
+ });
136
+
137
+ const forecast = weatherResources.find(r => r.uri === 'weather://forecast');
138
+ expect(forecast).toBeDefined();
139
+ expect(forecast).toMatchObject({
140
+ uri: 'weather://forecast',
141
+ name: 'Weather Forecast',
142
+ description: expect.any(String),
143
+ mimeType: 'application/json',
144
+ });
145
+
146
+ const historical = weatherResources.find(r => r.uri === 'weather://historical');
147
+ expect(historical).toBeDefined();
148
+ expect(historical).toMatchObject({
149
+ uri: 'weather://historical',
150
+ name: 'Historical Weather Data',
151
+ description: expect.any(String),
152
+ mimeType: 'application/json',
153
+ });
132
154
  });
133
155
 
134
- const forecast = weatherResources.find(r => r.uri === 'weather://forecast');
135
- expect(forecast).toBeDefined();
136
- expect(forecast).toMatchObject({
137
- uri: 'weather://forecast',
138
- name: 'Weather Forecast',
139
- description: expect.any(String),
140
- mimeType: 'application/json',
156
+ it('should list resource templates from connected MCP servers', async () => {
157
+ const templates = await mcp.resources.templates();
158
+ expect(templates).toHaveProperty('weather');
159
+ expect(templates.weather).toBeDefined();
160
+ expect(templates.weather.length).toBeGreaterThan(0);
161
+ const customForecastTemplate = templates.weather.find(
162
+ (t: ResourceTemplate) => t.uriTemplate === 'weather://custom/{city}/{days}',
163
+ );
164
+ expect(customForecastTemplate).toBeDefined();
165
+ expect(customForecastTemplate).toMatchObject({
166
+ uriTemplate: 'weather://custom/{city}/{days}',
167
+ name: 'Custom Weather Forecast',
168
+ description: expect.any(String),
169
+ mimeType: 'application/json',
170
+ });
141
171
  });
142
172
 
143
- const historical = weatherResources.find(r => r.uri === 'weather://historical');
144
- expect(historical).toBeDefined();
145
- expect(historical).toMatchObject({
146
- uri: 'weather://historical',
147
- name: 'Historical Weather Data',
148
- description: expect.any(String),
149
- mimeType: 'application/json',
173
+ it('should read a specific resource from a server', async () => {
174
+ const resourceContent = await mcp.resources.read('weather', 'weather://current');
175
+ expect(resourceContent).toBeDefined();
176
+ expect(resourceContent.contents).toBeInstanceOf(Array);
177
+ expect(resourceContent.contents.length).toBe(1);
178
+ const contentItem = resourceContent.contents[0];
179
+ expect(contentItem.uri).toBe('weather://current');
180
+ expect(contentItem.mimeType).toBe('application/json');
181
+ expect(contentItem.text).toBeDefined();
182
+ let parsedText: any = {};
183
+ if (contentItem.text && typeof contentItem.text === 'string') {
184
+ try {
185
+ parsedText = JSON.parse(contentItem.text);
186
+ } catch {
187
+ // If parsing fails, parsedText remains an empty object
188
+ // console.error("Failed to parse resource content text:", _e);
189
+ }
190
+ }
191
+ expect(parsedText).toHaveProperty('location');
150
192
  });
151
- });
152
193
 
153
- it('should list resource templates from connected MCP servers', async () => {
154
- const templates = await mcp.resources.templates();
155
- expect(templates).toHaveProperty('weather');
156
- expect(templates.weather).toBeDefined();
157
- expect(templates.weather.length).toBeGreaterThan(0);
158
- const customForecastTemplate = templates.weather.find(
159
- (t: ResourceTemplate) => t.uriTemplate === 'weather://custom/{city}/{days}',
160
- );
161
- expect(customForecastTemplate).toBeDefined();
162
- expect(customForecastTemplate).toMatchObject({
163
- uriTemplate: 'weather://custom/{city}/{days}',
164
- name: 'Custom Weather Forecast',
165
- description: expect.any(String),
166
- mimeType: 'application/json',
194
+ it('should subscribe and unsubscribe from a resource on a specific server', async () => {
195
+ const serverName = 'weather';
196
+ const resourceUri = 'weather://current';
197
+
198
+ const subResult = await mcp.resources.subscribe(serverName, resourceUri);
199
+ expect(subResult).toEqual({});
200
+
201
+ const unsubResult = await mcp.resources.unsubscribe(serverName, resourceUri);
202
+ expect(unsubResult).toEqual({});
167
203
  });
168
- });
169
204
 
170
- it('should read a specific resource from a server', async () => {
171
- const resourceContent = await mcp.resources.read('weather', 'weather://current');
172
- expect(resourceContent).toBeDefined();
173
- expect(resourceContent.contents).toBeInstanceOf(Array);
174
- expect(resourceContent.contents.length).toBe(1);
175
- const contentItem = resourceContent.contents[0];
176
- expect(contentItem.uri).toBe('weather://current');
177
- expect(contentItem.mimeType).toBe('application/json');
178
- expect(contentItem.text).toBeDefined();
179
- let parsedText: any = {};
180
- if (contentItem.text && typeof contentItem.text === 'string') {
181
- try {
182
- parsedText = JSON.parse(contentItem.text);
183
- } catch {
184
- // If parsing fails, parsedText remains an empty object
185
- // console.error("Failed to parse resource content text:", _e);
186
- }
187
- }
188
- expect(parsedText).toHaveProperty('location');
189
- });
205
+ it('should receive resource updated notification from a specific server', async () => {
206
+ const serverName = 'weather';
207
+ const resourceUri = 'weather://current';
208
+ let notificationReceived = false;
209
+ let receivedUri = '';
210
+
211
+ await mcp.resources.list(); // Initial call to establish connection if needed
212
+ // Create the promise for the notification BEFORE subscribing
213
+ const resourceUpdatedPromise = new Promise<void>((resolve, reject) => {
214
+ mcp.resources.onUpdated(serverName, (params: { uri: string }) => {
215
+ if (params.uri === resourceUri) {
216
+ notificationReceived = true;
217
+ receivedUri = params.uri;
218
+ resolve();
219
+ } else {
220
+ console.log(`[Test LOG] Received update for ${params.uri}, waiting for ${resourceUri}`);
221
+ }
222
+ });
223
+ setTimeout(() => reject(new Error(`Timeout waiting for resourceUpdated notification for ${resourceUri}`)), 4500);
224
+ });
190
225
 
191
- it('should subscribe and unsubscribe from a resource on a specific server', async () => {
192
- const serverName = 'weather';
193
- const resourceUri = 'weather://current';
226
+ await mcp.resources.subscribe(serverName, resourceUri); // Ensure subscription is active
194
227
 
195
- const subResult = await mcp.resources.subscribe(serverName, resourceUri);
196
- expect(subResult).toEqual({});
228
+ await expect(resourceUpdatedPromise).resolves.toBeUndefined(); // Wait for the notification
197
229
 
198
- const unsubResult = await mcp.resources.unsubscribe(serverName, resourceUri);
199
- expect(unsubResult).toEqual({});
200
- });
230
+ expect(notificationReceived).toBe(true);
231
+ expect(receivedUri).toBe(resourceUri);
232
+
233
+ await mcp.resources.unsubscribe(serverName, resourceUri); // Cleanup
234
+ }, 5000);
235
+
236
+ it('should receive resource list changed notification from a specific server', async () => {
237
+ const serverName = 'weather';
238
+ let notificationReceived = false;
201
239
 
202
- it('should receive resource updated notification from a specific server', async () => {
203
- const serverName = 'weather';
204
- const resourceUri = 'weather://current';
205
- let notificationReceived = false;
206
- let receivedUri = '';
207
-
208
- await mcp.resources.list(); // Initial call to establish connection if needed
209
- // Create the promise for the notification BEFORE subscribing
210
- const resourceUpdatedPromise = new Promise<void>((resolve, reject) => {
211
- mcp.resources.onUpdated(serverName, (params: { uri: string }) => {
212
- if (params.uri === resourceUri) {
240
+ await mcp.resources.list(); // Initial call to establish connection
241
+
242
+ const resourceListChangedPromise = new Promise<void>((resolve, reject) => {
243
+ mcp.resources.onListChanged(serverName, () => {
213
244
  notificationReceived = true;
214
- receivedUri = params.uri;
215
245
  resolve();
216
- } else {
217
- console.log(`[Test LOG] Received update for ${params.uri}, waiting for ${resourceUri}`);
218
- }
246
+ });
247
+ setTimeout(() => reject(new Error('Timeout waiting for resourceListChanged notification')), 4500);
219
248
  });
220
- setTimeout(() => reject(new Error(`Timeout waiting for resourceUpdated notification for ${resourceUri}`)), 4500);
249
+
250
+ // In a real scenario, something would trigger the server to send this.
251
+ // For the test, we rely on the interval in weather.ts or a direct call if available.
252
+ // Adding a small delay or an explicit trigger if the fixture supported it would be more robust.
253
+ // For now, we assume the interval in weather.ts will eventually fire it.
254
+
255
+ await expect(resourceListChangedPromise).resolves.toBeUndefined(); // Wait for the notification
256
+
257
+ expect(notificationReceived).toBe(true);
221
258
  });
222
259
 
223
- await mcp.resources.subscribe(serverName, resourceUri); // Ensure subscription is active
260
+ it('should handle errors when getting resources', async () => {
261
+ const errorClient = new MCPClient({
262
+ id: 'error-test-client',
263
+ servers: {
264
+ weather: {
265
+ url: new URL(`http://localhost:${weatherServerPort}/sse`),
266
+ },
267
+ nonexistentServer: {
268
+ command: 'nonexistent-command',
269
+ args: [],
270
+ },
271
+ },
272
+ });
224
273
 
225
- await expect(resourceUpdatedPromise).resolves.toBeUndefined(); // Wait for the notification
274
+ try {
275
+ const resources = await errorClient.resources.list();
226
276
 
227
- expect(notificationReceived).toBe(true);
228
- expect(receivedUri).toBe(resourceUri);
277
+ expect(resources).toHaveProperty('weather');
278
+ expect(resources.weather).toBeDefined();
279
+ expect(resources.weather.length).toBeGreaterThan(0);
229
280
 
230
- await mcp.resources.unsubscribe(serverName, resourceUri); // Cleanup
231
- }, 5000);
281
+ expect(resources).not.toHaveProperty('nonexistentServer');
282
+ } finally {
283
+ await errorClient.disconnect();
284
+ }
285
+ });
286
+ })
287
+
288
+ describe('Prompts', () => {
289
+ it('should get prompts from connected MCP servers', async () => {
290
+ const prompts = await mcp.prompts.list();
291
+
292
+ expect(prompts).toHaveProperty('weather');
293
+ expect(prompts['weather']).toBeDefined();
294
+ expect(prompts['weather']).toHaveLength(3);
295
+
296
+ // Verify that each expected resource exists with the correct structure
297
+ const promptResources = prompts['weather'];
298
+ const currentWeatherPrompt = promptResources.find(r => r.name === 'current');
299
+ expect(currentWeatherPrompt).toBeDefined();
300
+ expect(currentWeatherPrompt).toMatchObject({
301
+ name: 'current',
302
+ version: 'v1',
303
+ description: expect.any(String),
304
+ mimeType: 'application/json',
305
+ });
232
306
 
233
- it('should receive resource list changed notification from a specific server', async () => {
234
- const serverName = 'weather';
235
- let notificationReceived = false;
307
+ const forecast = promptResources.find(r => r.name === 'forecast');
308
+ expect(forecast).toBeDefined();
309
+ expect(forecast).toMatchObject({
310
+ name: 'forecast',
311
+ version: 'v1',
312
+ description: expect.any(String),
313
+ mimeType: 'application/json',
314
+ });
236
315
 
237
- await mcp.resources.list(); // Initial call to establish connection
316
+ const historical = promptResources.find(r => r.name === 'historical');
317
+ expect(historical).toBeDefined();
318
+ expect(historical).toMatchObject({
319
+ name: 'historical',
320
+ version: 'v1',
321
+ description: expect.any(String),
322
+ mimeType: 'application/json',
323
+ });
324
+ });
238
325
 
239
- const resourceListChangedPromise = new Promise<void>((resolve, reject) => {
240
- mcp.resources.onListChanged(serverName, () => {
241
- notificationReceived = true;
242
- resolve();
326
+ it('should get a specific prompt from a server', async () => {
327
+ const {prompt, messages} = await mcp.prompts.get({serverName: 'weather', name: 'current'});
328
+ expect(prompt).toBeDefined();
329
+ expect(prompt).toMatchObject({
330
+ name: 'current',
331
+ version: 'v1',
332
+ description: expect.any(String),
333
+ mimeType: 'application/json',
243
334
  });
244
- setTimeout(() => reject(new Error('Timeout waiting for resourceListChanged notification')), 4500);
335
+ expect(messages).toBeDefined();
336
+ const messageItem = messages[0];
337
+ let parsedText: any = {};
338
+ if (messageItem.content.text && typeof messageItem.content.text === 'string') {
339
+ try {
340
+ parsedText = JSON.parse(messageItem.content.text);
341
+ } catch {
342
+ // If parsing fails, parsedText remains an empty object
343
+ // console.error("Failed to parse resource content text:", _e);
344
+ }
345
+ }
346
+ expect(parsedText).toHaveProperty('location');
245
347
  });
246
348
 
247
- // In a real scenario, something would trigger the server to send this.
248
- // For the test, we rely on the interval in weather.ts or a direct call if available.
249
- // Adding a small delay or an explicit trigger if the fixture supported it would be more robust.
250
- // For now, we assume the interval in weather.ts will eventually fire it.
349
+ it('should receive prompt list changed notification from a specific server', async () => {
350
+ const serverName = 'weather';
351
+ let notificationReceived = false;
251
352
 
252
- await expect(resourceListChangedPromise).resolves.toBeUndefined(); // Wait for the notification
353
+ await mcp.prompts.list();
253
354
 
254
- expect(notificationReceived).toBe(true);
255
- });
355
+ const promptListChangedPromise = new Promise<void>((resolve, reject) => {
356
+ mcp.prompts.onListChanged(serverName, () => {
357
+ notificationReceived = true;
358
+ resolve();
359
+ });
360
+ setTimeout(() => reject(new Error('Timeout waiting for promptListChanged notification')), 4500);
361
+ });
256
362
 
257
- it('should handle errors when getting resources', async () => {
258
- const errorClient = new MCPClient({
259
- id: 'error-test-client',
260
- servers: {
261
- weather: {
262
- url: new URL(`http://localhost:${weatherServerPort}/sse`),
263
- },
264
- nonexistentServer: {
265
- command: 'nonexistent-command',
266
- args: [],
267
- },
268
- },
363
+ await expect(promptListChangedPromise).resolves.toBeUndefined();
364
+
365
+ expect(notificationReceived).toBe(true);
269
366
  });
270
367
 
271
- try {
272
- const resources = await errorClient.resources.list();
368
+ it('should handle errors when getting prompts', async () => {
369
+ const errorClient = new MCPClient({
370
+ id: 'error-test-client',
371
+ servers: {
372
+ weather: {
373
+ url: new URL(`http://localhost:${weatherServerPort}/sse`),
374
+ },
375
+ nonexistentServer: {
376
+ command: 'nonexistent-command',
377
+ args: [],
378
+ },
379
+ },
380
+ });
273
381
 
274
- expect(resources).toHaveProperty('weather');
275
- expect(resources.weather).toBeDefined();
276
- expect(resources.weather.length).toBeGreaterThan(0);
382
+ try {
383
+ const prompts = await errorClient.prompts.list();
277
384
 
278
- expect(resources).not.toHaveProperty('nonexistentServer');
279
- } finally {
280
- await errorClient.disconnect();
281
- }
282
- });
385
+ expect(prompts).toHaveProperty('weather');
386
+ expect(prompts['weather']).toBeDefined();
387
+ expect(prompts['weather'].length).toBeGreaterThan(0);
283
388
 
284
- it('should handle connection errors gracefully', async () => {
285
- const badConfig = new MCPClient({
286
- servers: {
287
- badServer: {
288
- command: 'nonexistent-command',
289
- args: [],
290
- },
291
- },
389
+ expect(prompts).not.toHaveProperty('nonexistentServer');
390
+ } finally {
391
+ await errorClient.disconnect();
392
+ }
292
393
  });
293
-
294
- await expect(badConfig.getTools()).rejects.toThrow();
295
- await badConfig.disconnect();
296
- });
394
+ })
297
395
 
298
396
  describe('Instance Management', () => {
299
397
  it('should allow multiple instances with different IDs', async () => {
@@ -479,6 +577,20 @@ describe('MCPClient', () => {
479
577
  await expect(mixedConfig.getTools()).rejects.toThrow(/Request timed out/);
480
578
  await mixedConfig.disconnect();
481
579
  });
580
+
581
+ it('should handle connection errors gracefully', async () => {
582
+ const badConfig = new MCPClient({
583
+ servers: {
584
+ badServer: {
585
+ command: 'nonexistent-command',
586
+ args: [],
587
+ },
588
+ },
589
+ });
590
+
591
+ await expect(badConfig.getTools()).rejects.toThrow();
592
+ await badConfig.disconnect();
593
+ });
482
594
  });
483
595
 
484
596
  describe('Schema Handling', () => {
@@ -1,6 +1,6 @@
1
1
  import { MastraBase } from '@mastra/core/base';
2
2
  import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
3
- import type { Resource, ResourceTemplate } from '@modelcontextprotocol/sdk/types.js';
3
+ import type { Prompt, Resource, ResourceTemplate } from '@modelcontextprotocol/sdk/types.js';
4
4
  import equal from 'fast-deep-equal';
5
5
  import { v5 as uuidv5 } from 'uuid';
6
6
  import { InternalMastraMCPClient } from './client';
@@ -114,6 +114,32 @@ To fix this you have three different options:
114
114
  };
115
115
  }
116
116
 
117
+ public get prompts() {
118
+ this.addToInstanceCache();
119
+ return {
120
+ list: async (): Promise<Record<string, Prompt[]>> => {
121
+ const allPrompts: Record<string, Prompt[]> = {};
122
+ for (const serverName of Object.keys(this.serverConfigs)) {
123
+ try {
124
+ const internalClient = await this.getConnectedClientForServer(serverName);
125
+ allPrompts[serverName] = await internalClient.prompts.list();
126
+ } catch (error) {
127
+ this.logger.error(`Failed to list prompts from server ${serverName}`, { error });
128
+ }
129
+ }
130
+ return allPrompts;
131
+ },
132
+ get: async ({serverName, name, args, version}: {serverName: string, name: string, args?: Record<string, any>, version?: string}) => {
133
+ const internalClient = await this.getConnectedClientForServer(serverName);
134
+ return internalClient.prompts.get({name, args, version});
135
+ },
136
+ onListChanged: async (serverName: string, handler: () => void) => {
137
+ const internalClient = await this.getConnectedClientForServer(serverName);
138
+ return internalClient.prompts.onListChanged(handler);
139
+ },
140
+ };
141
+ }
142
+
117
143
  private addToInstanceCache() {
118
144
  if (!mcpClientInstances.has(this.id)) {
119
145
  mcpClientInstances.set(this.id, this);
@@ -0,0 +1,3 @@
1
+ export type { LoggingLevel, LogMessage, LogHandler, MastraMCPServerDefinition } from './client';
2
+ export { MastraMCPClient } from './client';
3
+ export * from './configuration';