@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.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +28 -0
- package/dist/_tsup-dts-rollup.d.cts +88 -80
- package/dist/_tsup-dts-rollup.d.ts +88 -80
- package/dist/index.cjs +714 -168
- package/dist/index.js +699 -153
- package/integration-tests/node_modules/.bin/vitest +2 -2
- package/integration-tests/package.json +2 -2
- package/package.json +7 -7
- package/src/__fixtures__/tools.ts +0 -9
- package/src/client/client.ts +45 -1
- package/src/client/configuration.ts +166 -35
- package/src/server/promptActions.ts +13 -2
- package/src/server/resourceActions.ts +32 -4
- package/src/server/server.test.ts +84 -0
- package/src/server/server.ts +302 -114
- package/integration-tests/node_modules/.bin/mastra +0 -21
|
@@ -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.
|
|
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.
|
|
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.
|
|
15
|
+
"zod": "^3.25.67"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@mastra/core": "workspace:*",
|
|
19
|
-
"@testing-library/react": "^16.
|
|
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.
|
|
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.
|
|
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.
|
|
46
|
-
"hono-mcp-server-sse-transport": "0.0.
|
|
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.
|
|
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.
|
|
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);
|
package/src/client/client.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
108
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
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:
|
|
42
|
+
error: mastraError.toString(),
|
|
33
43
|
});
|
|
34
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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
|
+
});
|