@mastra/mcp 0.10.4 → 0.10.5-alpha.1

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.
@@ -10,9 +10,9 @@ case `uname` in
10
10
  esac
11
11
 
12
12
  if [ -z "$NODE_PATH" ]; then
13
- export NODE_PATH="/home/runner/work/mastra/mastra/node_modules/.pnpm/vitest@3.2.3_@edge-runtime+vm@3.2.0_@types+debug@4.1.12_@types+node@20.19.0_@vitest+ui@_3e45f0297eeb1f6a4ee30770ed0f557b/node_modules/vitest/node_modules:/home/runner/work/mastra/mastra/node_modules/.pnpm/vitest@3.2.3_@edge-runtime+vm@3.2.0_@types+debug@4.1.12_@types+node@20.19.0_@vitest+ui@_3e45f0297eeb1f6a4ee30770ed0f557b/node_modules:/home/runner/work/mastra/mastra/node_modules/.pnpm/node_modules"
13
+ export NODE_PATH="/home/runner/work/mastra/mastra/node_modules/.pnpm/vitest@3.2.4_@types+debug@4.1.12_@types+node@20.19.1_@vitest+ui@3.2.3_jiti@2.4.2_jsdom@_9ed2428c73777463a3d92e2c0ddae976/node_modules/vitest/node_modules:/home/runner/work/mastra/mastra/node_modules/.pnpm/vitest@3.2.4_@types+debug@4.1.12_@types+node@20.19.1_@vitest+ui@3.2.3_jiti@2.4.2_jsdom@_9ed2428c73777463a3d92e2c0ddae976/node_modules:/home/runner/work/mastra/mastra/node_modules/.pnpm/node_modules"
14
14
  else
15
- export NODE_PATH="/home/runner/work/mastra/mastra/node_modules/.pnpm/vitest@3.2.3_@edge-runtime+vm@3.2.0_@types+debug@4.1.12_@types+node@20.19.0_@vitest+ui@_3e45f0297eeb1f6a4ee30770ed0f557b/node_modules/vitest/node_modules:/home/runner/work/mastra/mastra/node_modules/.pnpm/vitest@3.2.3_@edge-runtime+vm@3.2.0_@types+debug@4.1.12_@types+node@20.19.0_@vitest+ui@_3e45f0297eeb1f6a4ee30770ed0f557b/node_modules:/home/runner/work/mastra/mastra/node_modules/.pnpm/node_modules:$NODE_PATH"
15
+ export NODE_PATH="/home/runner/work/mastra/mastra/node_modules/.pnpm/vitest@3.2.4_@types+debug@4.1.12_@types+node@20.19.1_@vitest+ui@3.2.3_jiti@2.4.2_jsdom@_9ed2428c73777463a3d92e2c0ddae976/node_modules/vitest/node_modules:/home/runner/work/mastra/mastra/node_modules/.pnpm/vitest@3.2.4_@types+debug@4.1.12_@types+node@20.19.1_@vitest+ui@3.2.3_jiti@2.4.2_jsdom@_9ed2428c73777463a3d92e2c0ddae976/node_modules:/home/runner/work/mastra/mastra/node_modules/.pnpm/node_modules:$NODE_PATH"
16
16
  fi
17
17
  if [ -x "$basedir/node" ]; then
18
18
  exec "$basedir/node" "$basedir/../vitest/vitest.mjs" "$@"
@@ -12,11 +12,11 @@
12
12
  "@mastra/client-js": "workspace:*",
13
13
  "@mastra/mcp": "workspace:*",
14
14
  "dotenv": "^16.5.0",
15
- "zod": "^3.25.56"
15
+ "zod": "^3.25.67"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@mastra/core": "workspace:*",
19
- "@testing-library/react": "^16.2.0",
19
+ "@testing-library/react": "^16.3.0",
20
20
  "@types/node": "^20.17.57",
21
21
  "get-port": "^7.1.0",
22
22
  "mastra": "workspace:*",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/mcp",
3
- "version": "0.10.4",
3
+ "version": "0.10.5-alpha.1",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,11 +22,10 @@
22
22
  "author": "",
23
23
  "license": "Elastic-2.0",
24
24
  "dependencies": {
25
- "@modelcontextprotocol/sdk": "^1.12.1",
25
+ "@modelcontextprotocol/sdk": "^1.13.0",
26
26
  "date-fns": "^4.1.0",
27
27
  "exit-hook": "^4.0.0",
28
28
  "fast-deep-equal": "^3.1.3",
29
- "hono": "^4.7.11",
30
29
  "uuid": "^11.1.0",
31
30
  "zod-from-json-schema": "^0.0.5"
32
31
  },
@@ -42,16 +41,17 @@
42
41
  "@microsoft/api-extractor": "^7.52.8",
43
42
  "@types/node": "^20.19.0",
44
43
  "ai": "4.3.16",
45
- "eslint": "^9.28.0",
46
- "hono-mcp-server-sse-transport": "0.0.6",
44
+ "eslint": "^9.29.0",
45
+ "hono-mcp-server-sse-transport": "0.0.7",
46
+ "hono": "^4.7.11",
47
47
  "tsup": "^8.5.0",
48
48
  "tsx": "^4.19.4",
49
49
  "typescript": "^5.8.3",
50
50
  "vitest": "^3.2.3",
51
- "zod": "^3.25.57",
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.6"
54
+ "@mastra/core": "0.10.7-alpha.2"
55
55
  },
56
56
  "scripts": {
57
57
  "build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting",
@@ -26,15 +26,6 @@ export const weatherTool = createTool({
26
26
  inputSchema: z.object({
27
27
  location: z.string().describe('City name'),
28
28
  }),
29
- outputSchema: z.object({
30
- temperature: z.number(),
31
- feelsLike: z.number(),
32
- humidity: z.number(),
33
- windSpeed: z.number(),
34
- windGust: z.number(),
35
- conditions: z.string(),
36
- location: z.string(),
37
- }),
38
29
  execute: async ({ context }) => {
39
30
  console.log('weather tool', context);
40
31
  return await getWeather(context.location);
@@ -1,6 +1,7 @@
1
1
  import { MastraBase } from '@mastra/core/base';
2
2
 
3
3
  import type { RuntimeContext } from '@mastra/core/di';
4
+ import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
4
5
  import { createTool } from '@mastra/core/tools';
5
6
  import { isZodType } from '@mastra/core/utils';
6
7
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
@@ -475,7 +476,48 @@ export class InternalMastraMCPClient extends MastraBase {
475
476
  originalJsonSchema: inputSchema,
476
477
  });
477
478
 
478
- throw new Error(errorDetails);
479
+ throw new MastraError({
480
+ id: 'MCP_TOOL_INPUT_SCHEMA_CONVERSION_FAILED',
481
+ domain: ErrorDomain.MCP,
482
+ category: ErrorCategory.USER,
483
+ details: { error: errorDetails ?? 'Unknown error' },
484
+ });
485
+ }
486
+ }
487
+
488
+ private convertOutputSchema(
489
+ outputSchema: Awaited<ReturnType<Client['listTools']>>['tools'][0]['outputSchema'] | JSONSchema,
490
+ ): z.ZodType | undefined {
491
+ if (!outputSchema) return
492
+ if (isZodType(outputSchema)) {
493
+ return outputSchema;
494
+ }
495
+
496
+ try {
497
+ return convertJsonSchemaToZod(outputSchema as JSONSchema);
498
+ } catch (error: unknown) {
499
+ let errorDetails: string | undefined;
500
+ if (error instanceof Error) {
501
+ errorDetails = error.stack;
502
+ } else {
503
+ // Attempt to stringify, fallback to String()
504
+ try {
505
+ errorDetails = JSON.stringify(error);
506
+ } catch {
507
+ errorDetails = String(error);
508
+ }
509
+ }
510
+ this.log('error', 'Failed to convert JSON schema to Zod schema using zodFromJsonSchema', {
511
+ error: errorDetails,
512
+ originalJsonSchema: outputSchema,
513
+ });
514
+
515
+ throw new MastraError({
516
+ id: 'MCP_TOOL_OUTPUT_SCHEMA_CONVERSION_FAILED',
517
+ domain: ErrorDomain.MCP,
518
+ category: ErrorCategory.USER,
519
+ details: { error: errorDetails ?? 'Unknown error' },
520
+ });
479
521
  }
480
522
  }
481
523
 
@@ -490,6 +532,7 @@ export class InternalMastraMCPClient extends MastraBase {
490
532
  id: `${this.name}_${tool.name}`,
491
533
  description: tool.description || '',
492
534
  inputSchema: this.convertInputSchema(tool.inputSchema),
535
+ outputSchema: this.convertOutputSchema(tool.outputSchema),
493
536
  execute: async ({ context, runtimeContext }: { context: any; runtimeContext?: RuntimeContext | null }) => {
494
537
  const previousContext = this.currentOperationContext;
495
538
  this.currentOperationContext = runtimeContext || null; // Set current context
@@ -505,6 +548,7 @@ export class InternalMastraMCPClient extends MastraBase {
505
548
  timeout: this.timeout,
506
549
  },
507
550
  );
551
+
508
552
  this.log('debug', `Tool executed successfully: ${tool.name}`);
509
553
  return res;
510
554
  } catch (e) {
@@ -1,4 +1,5 @@
1
1
  import { MastraBase } from '@mastra/core/base';
2
+ import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
2
3
  import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
3
4
  import type { Prompt, Resource, ResourceTemplate } from '@modelcontextprotocol/sdk/types.js';
4
5
  import equal from 'fast-deep-equal';
@@ -74,7 +75,16 @@ To fix this you have three different options:
74
75
  const internalClient = await this.getConnectedClientForServer(serverName);
75
76
  allResources[serverName] = await internalClient.resources.list();
76
77
  } catch (error) {
77
- this.logger.error(`Failed to list resources from server ${serverName}`, { error });
78
+ const mastraError = new MastraError({
79
+ id: 'MCP_CLIENT_LIST_RESOURCES_FAILED',
80
+ domain: ErrorDomain.MCP,
81
+ category: ErrorCategory.THIRD_PARTY,
82
+ details: {
83
+ serverName,
84
+ }
85
+ }, error);
86
+ this.logger.trackException(mastraError);
87
+ this.logger.error('Failed to list resources from server:', { error: mastraError.toString() });
78
88
  }
79
89
  }
80
90
  return allResources;
@@ -86,30 +96,97 @@ To fix this you have three different options:
86
96
  const internalClient = await this.getConnectedClientForServer(serverName);
87
97
  allTemplates[serverName] = await internalClient.resources.templates();
88
98
  } catch (error) {
89
- this.logger.error(`Failed to list resource templates from server ${serverName}`, { error });
99
+ const mastraError = new MastraError({
100
+ id: 'MCP_CLIENT_LIST_RESOURCE_TEMPLATES_FAILED',
101
+ domain: ErrorDomain.MCP,
102
+ category: ErrorCategory.THIRD_PARTY,
103
+ details: {
104
+ serverName,
105
+ }
106
+ }, error);
107
+ this.logger.trackException(mastraError);
108
+ this.logger.error('Failed to list resource templates from server:', { error: mastraError.toString() });
90
109
  }
91
110
  }
92
111
  return allTemplates;
93
112
  },
94
113
  read: async (serverName: string, uri: string) => {
95
- const internalClient = await this.getConnectedClientForServer(serverName);
96
- return internalClient.resources.read(uri);
114
+ try {
115
+ const internalClient = await this.getConnectedClientForServer(serverName);
116
+ return internalClient.resources.read(uri);
117
+ } catch (error) {
118
+ throw new MastraError({
119
+ id: 'MCP_CLIENT_READ_RESOURCE_FAILED',
120
+ domain: ErrorDomain.MCP,
121
+ category: ErrorCategory.THIRD_PARTY,
122
+ details: {
123
+ serverName,
124
+ uri,
125
+ }
126
+ }, error);
127
+ }
97
128
  },
98
129
  subscribe: async (serverName: string, uri: string) => {
99
- const internalClient = await this.getConnectedClientForServer(serverName);
100
- return internalClient.resources.subscribe(uri);
130
+ try {
131
+ const internalClient = await this.getConnectedClientForServer(serverName);
132
+ return internalClient.resources.subscribe(uri);
133
+ } catch (error) {
134
+ throw new MastraError({
135
+ id: 'MCP_CLIENT_SUBSCRIBE_RESOURCE_FAILED',
136
+ domain: ErrorDomain.MCP,
137
+ category: ErrorCategory.THIRD_PARTY,
138
+ details: {
139
+ serverName,
140
+ uri,
141
+ }
142
+ }, error);
143
+ }
101
144
  },
102
145
  unsubscribe: async (serverName: string, uri: string) => {
103
- const internalClient = await this.getConnectedClientForServer(serverName);
104
- return internalClient.resources.unsubscribe(uri);
146
+ try {
147
+ const internalClient = await this.getConnectedClientForServer(serverName);
148
+ return internalClient.resources.unsubscribe(uri);
149
+ } catch (err) {
150
+ throw new MastraError({
151
+ id: 'MCP_CLIENT_UNSUBSCRIBE_RESOURCE_FAILED',
152
+ domain: ErrorDomain.MCP,
153
+ category: ErrorCategory.THIRD_PARTY,
154
+ details: {
155
+ serverName,
156
+ uri,
157
+ }
158
+ }, err);
159
+ }
105
160
  },
106
161
  onUpdated: async (serverName: string, handler: (params: { uri: string }) => void) => {
107
- const internalClient = await this.getConnectedClientForServer(serverName);
108
- return internalClient.resources.onUpdated(handler);
162
+ try {
163
+ const internalClient = await this.getConnectedClientForServer(serverName);
164
+ return internalClient.resources.onUpdated(handler);
165
+ } catch (err) {
166
+ throw new MastraError({
167
+ id: 'MCP_CLIENT_ON_UPDATED_RESOURCE_FAILED',
168
+ domain: ErrorDomain.MCP,
169
+ category: ErrorCategory.THIRD_PARTY,
170
+ details: {
171
+ serverName,
172
+ }
173
+ }, err);
174
+ }
109
175
  },
110
176
  onListChanged: async (serverName: string, handler: () => void) => {
111
- const internalClient = await this.getConnectedClientForServer(serverName);
112
- return internalClient.resources.onListChanged(handler);
177
+ try {
178
+ const internalClient = await this.getConnectedClientForServer(serverName);
179
+ return internalClient.resources.onListChanged(handler);
180
+ } catch (err) {
181
+ throw new MastraError({
182
+ id: 'MCP_CLIENT_ON_LIST_CHANGED_RESOURCE_FAILED',
183
+ domain: ErrorDomain.MCP,
184
+ category: ErrorCategory.THIRD_PARTY,
185
+ details: {
186
+ serverName,
187
+ }
188
+ }, err);
189
+ }
113
190
  },
114
191
  };
115
192
  }
@@ -124,18 +201,50 @@ To fix this you have three different options:
124
201
  const internalClient = await this.getConnectedClientForServer(serverName);
125
202
  allPrompts[serverName] = await internalClient.prompts.list();
126
203
  } catch (error) {
127
- this.logger.error(`Failed to list prompts from server ${serverName}`, { error });
204
+ const mastraError = new MastraError({
205
+ id: 'MCP_CLIENT_LIST_PROMPTS_FAILED',
206
+ domain: ErrorDomain.MCP,
207
+ category: ErrorCategory.THIRD_PARTY,
208
+ details: {
209
+ serverName,
210
+ }
211
+ }, error);
212
+ this.logger.trackException(mastraError);
213
+ this.logger.error('Failed to list prompts from server:', { error: mastraError.toString() });
128
214
  }
129
215
  }
130
216
  return allPrompts;
131
217
  },
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});
218
+ get: async ({ serverName, name, args, version }: { serverName: string, name: string, args?: Record<string, any>, version?: string }) => {
219
+ try {
220
+ const internalClient = await this.getConnectedClientForServer(serverName);
221
+ return internalClient.prompts.get({ name, args, version });
222
+ } catch (error) {
223
+ throw new MastraError({
224
+ id: 'MCP_CLIENT_GET_PROMPT_FAILED',
225
+ domain: ErrorDomain.MCP,
226
+ category: ErrorCategory.THIRD_PARTY,
227
+ details: {
228
+ serverName,
229
+ name,
230
+ }
231
+ }, error);
232
+ }
135
233
  },
136
234
  onListChanged: async (serverName: string, handler: () => void) => {
137
- const internalClient = await this.getConnectedClientForServer(serverName);
138
- return internalClient.prompts.onListChanged(handler);
235
+ try {
236
+ const internalClient = await this.getConnectedClientForServer(serverName);
237
+ return internalClient.prompts.onListChanged(handler);
238
+ } catch (error) {
239
+ throw new MastraError({
240
+ id: 'MCP_CLIENT_ON_LIST_CHANGED_PROMPT_FAILED',
241
+ domain: ErrorDomain.MCP,
242
+ category: ErrorCategory.THIRD_PARTY,
243
+ details: {
244
+ serverName,
245
+ }
246
+ }, error);
247
+ }
139
248
  },
140
249
  };
141
250
  }
@@ -163,7 +272,7 @@ To fix this you have three different options:
163
272
  this.disconnectPromise = (async () => {
164
273
  try {
165
274
  mcpClientInstances.delete(this.id);
166
-
275
+
167
276
  // Disconnect all clients in the cache
168
277
  await Promise.all(Array.from(this.mcpClientsById.values()).map(client => client.disconnect()));
169
278
  this.mcpClientsById.clear();
@@ -179,11 +288,19 @@ To fix this you have three different options:
179
288
  this.addToInstanceCache();
180
289
  const connectedTools: Record<string, any> = {}; // <- any because we don't have proper tool schemas
181
290
 
182
- await this.eachClientTools(async ({ serverName, tools }) => {
183
- for (const [toolName, toolConfig] of Object.entries(tools)) {
184
- connectedTools[`${serverName}_${toolName}`] = toolConfig; // namespace tool to prevent tool name conflicts between servers
185
- }
186
- });
291
+ try {
292
+ await this.eachClientTools(async ({ serverName, tools }) => {
293
+ for (const [toolName, toolConfig] of Object.entries(tools)) {
294
+ connectedTools[`${serverName}_${toolName}`] = toolConfig; // namespace tool to prevent tool name conflicts between servers
295
+ }
296
+ });
297
+ } catch (error) {
298
+ throw new MastraError({
299
+ id: 'MCP_CLIENT_GET_TOOLS_FAILED',
300
+ domain: ErrorDomain.MCP,
301
+ category: ErrorCategory.THIRD_PARTY,
302
+ }, error);
303
+ }
187
304
 
188
305
  return connectedTools;
189
306
  }
@@ -192,11 +309,19 @@ To fix this you have three different options:
192
309
  this.addToInstanceCache();
193
310
  const connectedToolsets: Record<string, Record<string, any>> = {}; // <- any because we don't have proper tool schemas
194
311
 
195
- await this.eachClientTools(async ({ serverName, tools }) => {
196
- if (tools) {
197
- connectedToolsets[serverName] = tools;
198
- }
199
- });
312
+ try {
313
+ await this.eachClientTools(async ({ serverName, tools }) => {
314
+ if (tools) {
315
+ connectedToolsets[serverName] = tools;
316
+ }
317
+ });
318
+ } catch (error) {
319
+ throw new MastraError({
320
+ id: 'MCP_CLIENT_GET_TOOLSETS_FAILED',
321
+ domain: ErrorDomain.MCP,
322
+ category: ErrorCategory.THIRD_PARTY,
323
+ }, error);
324
+ }
200
325
 
201
326
  return connectedToolsets;
202
327
  }
@@ -258,13 +383,19 @@ To fix this you have three different options:
258
383
  try {
259
384
  await mcpClient.connect();
260
385
  } catch (e) {
386
+ const mastraError = new MastraError({
387
+ id: 'MCP_CLIENT_CONNECT_FAILED',
388
+ domain: ErrorDomain.MCP,
389
+ category: ErrorCategory.THIRD_PARTY,
390
+ text: `Failed to connect to MCP server ${name}: ${e instanceof Error ? e.stack || e.message : String(e)}`,
391
+ details: {
392
+ name,
393
+ }
394
+ }, e);
395
+ this.logger.trackException(mastraError);
396
+ this.logger.error('MCPClient errored connecting to MCP server:', { error: mastraError.toString() });
261
397
  this.mcpClientsById.delete(name);
262
- this.logger.error(`MCPClient errored connecting to MCP server ${name}`, {
263
- error: e instanceof Error ? e.message : String(e),
264
- });
265
- throw new Error(
266
- `Failed to connect to MCP server ${name}: ${e instanceof Error ? e.stack || e.message : String(e)}`,
267
- );
398
+ throw mastraError;
268
399
  }
269
400
  this.logger.debug(`Connected to ${name} MCP server`);
270
401
  return mcpClient;
@@ -1,3 +1,4 @@
1
+ import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
1
2
  import type { IMastraLogger } from '@mastra/core/logger';
2
3
  import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
4
 
@@ -28,10 +29,20 @@ export class ServerPromptActions {
28
29
  try {
29
30
  await this.getSdkServer().sendPromptListChanged();
30
31
  } catch (error) {
32
+ const mastraError = new MastraError(
33
+ {
34
+ id: 'MCP_SERVER_PROMPT_LIST_CHANGED_NOTIFICATION_FAILED',
35
+ domain: ErrorDomain.MCP,
36
+ category: ErrorCategory.THIRD_PARTY,
37
+ text: 'Failed to send prompt list changed notification',
38
+ },
39
+ error,
40
+ );
31
41
  this.getLogger().error('Failed to send prompt list changed notification:', {
32
- error: error instanceof Error ? error.message : String(error),
42
+ error: mastraError.toString(),
33
43
  });
34
- throw error;
44
+ this.getLogger().trackException(mastraError);
45
+ throw mastraError;
35
46
  }
36
47
  }
37
48
  }
@@ -1,3 +1,4 @@
1
+ import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
1
2
  import type { IMastraLogger } from '@mastra/core/logger';
2
3
  import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
4
 
@@ -34,8 +35,23 @@ export class ServerResourceActions {
34
35
  try {
35
36
  await this.getSdkServer().sendResourceUpdated({ uri });
36
37
  } catch (error) {
37
- this.getLogger().error('Failed to send resource updated notification:', { error });
38
- throw error;
38
+ const mastraError = new MastraError(
39
+ {
40
+ id: 'MCP_SERVER_RESOURCE_UPDATED_NOTIFICATION_FAILED',
41
+ domain: ErrorDomain.MCP,
42
+ category: ErrorCategory.THIRD_PARTY,
43
+ text: 'Failed to send resource updated notification',
44
+ details: {
45
+ uri,
46
+ },
47
+ },
48
+ error,
49
+ );
50
+ this.getLogger().trackException(mastraError);
51
+ this.getLogger().error('Failed to send resource updated notification:', {
52
+ error: mastraError.toString(),
53
+ });
54
+ throw mastraError;
39
55
  }
40
56
  } else {
41
57
  this.getLogger().debug(`Resource ${uri} was updated, but no active subscriptions for it.`);
@@ -55,8 +71,20 @@ export class ServerResourceActions {
55
71
  try {
56
72
  await this.getSdkServer().sendResourceListChanged();
57
73
  } catch (error) {
58
- this.getLogger().error('Failed to send resource list changed notification:', { error });
59
- throw error;
74
+ const mastraError = new MastraError(
75
+ {
76
+ id: 'MCP_SERVER_RESOURCE_LIST_CHANGED_NOTIFICATION_FAILED',
77
+ domain: ErrorDomain.MCP,
78
+ category: ErrorCategory.THIRD_PARTY,
79
+ text: 'Failed to send resource list changed notification',
80
+ },
81
+ error,
82
+ );
83
+ this.getLogger().trackException(mastraError);
84
+ this.getLogger().error('Failed to send resource list changed notification:', {
85
+ error: mastraError.toString(),
86
+ });
87
+ throw mastraError;
60
88
  }
61
89
  }
62
90
  }
@@ -1347,3 +1347,87 @@ describe('MCPServer - Workflow to Tool Conversion', () => {
1347
1347
  expect(server.tools()['run_unique_workflow_key_789']).toBeDefined();
1348
1348
  });
1349
1349
  });
1350
+
1351
+ describe('MCPServer with Tool Output Schema', () => {
1352
+ let serverWithOutputSchema: MCPServer;
1353
+ let clientWithOutputSchema: MCPClient;
1354
+ const PORT = 9600 + Math.floor(Math.random() * 1000);
1355
+ let httpServerWithOutputSchema: http.Server;
1356
+
1357
+ const structuredTool: ToolsInput = {
1358
+ structuredTool: {
1359
+ description: 'A test tool with structured output',
1360
+ parameters: z.object({ input: z.string() }),
1361
+ outputSchema: z.object({
1362
+ processedInput: z.string(),
1363
+ timestamp: z.string(),
1364
+ }),
1365
+ execute: async ({ input }: { input: string }) => ({
1366
+ structuredContent: {
1367
+ processedInput: `processed: ${input}`,
1368
+ timestamp: mockDateISO,
1369
+ },
1370
+ }),
1371
+ },
1372
+ };
1373
+
1374
+ beforeAll(async () => {
1375
+ serverWithOutputSchema = new MCPServer({
1376
+ name: 'Test MCP Server with OutputSchema',
1377
+ version: '0.1.0',
1378
+ tools: structuredTool,
1379
+ });
1380
+
1381
+ httpServerWithOutputSchema = http.createServer(async (req: http.IncomingMessage, res: http.ServerResponse) => {
1382
+ const url = new URL(req.url || '', `http://localhost:${PORT}`);
1383
+ await serverWithOutputSchema.startHTTP({
1384
+ url,
1385
+ httpPath: '/http',
1386
+ req,
1387
+ res,
1388
+ });
1389
+ });
1390
+
1391
+ await new Promise<void>(resolve => httpServerWithOutputSchema.listen(PORT, () => resolve()));
1392
+
1393
+ clientWithOutputSchema = new MCPClient({
1394
+ servers: {
1395
+ local: {
1396
+ url: new URL(`http://localhost:${PORT}/http`),
1397
+ },
1398
+ },
1399
+ });
1400
+ });
1401
+
1402
+ afterAll(async () => {
1403
+ httpServerWithOutputSchema.closeAllConnections?.();
1404
+ await new Promise<void>(resolve =>
1405
+ httpServerWithOutputSchema.close(() => {
1406
+ resolve();
1407
+ }),
1408
+ );
1409
+ await serverWithOutputSchema.close();
1410
+ });
1411
+
1412
+ it('should list tool with outputSchema', async () => {
1413
+ const tools = await clientWithOutputSchema.getTools();
1414
+ const tool = tools['local_structuredTool'];
1415
+ expect(tool).toBeDefined();
1416
+ expect(tool.outputSchema).toBeDefined();
1417
+ });
1418
+
1419
+ it('should call tool and receive structuredContent', async () => {
1420
+ const tools = await clientWithOutputSchema.getTools();
1421
+ const tool = tools['local_structuredTool'];
1422
+ const result = await tool.execute({ context: { input: 'hello' } });
1423
+
1424
+ expect(result).toBeDefined();
1425
+ expect(result.structuredContent).toBeDefined();
1426
+ expect(result.structuredContent.processedInput).toBe('processed: hello');
1427
+ expect(result.structuredContent.timestamp).toBe(mockDateISO);
1428
+
1429
+ expect(result.content).toBeDefined();
1430
+ expect(result.content[0].type).toBe('text');
1431
+ expect(JSON.parse(result.content[0].text)).toEqual(result.structuredContent);
1432
+ });
1433
+ });