@mastra/mcp 0.10.4 → 0.10.5-alpha.0

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.
@@ -3,6 +3,7 @@ import type * as http from 'node:http';
3
3
  import type { InternalCoreTool } from '@mastra/core';
4
4
  import { createTool, makeCoreTool } from '@mastra/core';
5
5
  import type { ToolsInput, Agent } from '@mastra/core/agent';
6
+ import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
6
7
  import { MCPServerBase } from '@mastra/core/mcp';
7
8
  import type {
8
9
  MCPServerConfig,
@@ -374,8 +375,26 @@ export class MCPServer extends MCPServerBase {
374
375
  }
375
376
  this.logger.info(`Total defined tools registered: ${Object.keys(definedConvertedTools).length}`);
376
377
 
377
- const agentDerivedTools = this.convertAgentsToTools(agentsConfig, definedConvertedTools);
378
- const workflowDerivedTools = this.convertWorkflowsToTools(workflowsConfig, definedConvertedTools);
378
+ let agentDerivedTools: Record<string, ConvertedTool> = {};
379
+ let workflowDerivedTools: Record<string, ConvertedTool> = {};
380
+ try {
381
+ agentDerivedTools = this.convertAgentsToTools(agentsConfig, definedConvertedTools);
382
+ workflowDerivedTools = this.convertWorkflowsToTools(workflowsConfig, definedConvertedTools);
383
+ } catch (e) {
384
+ const mastraError = new MastraError(
385
+ {
386
+ id: 'MCP_SERVER_AGENT_OR_WORKFLOW_TOOL_CONVERSION_FAILED',
387
+ domain: ErrorDomain.MCP,
388
+ category: ErrorCategory.USER,
389
+ },
390
+ e,
391
+ );
392
+ this.logger.trackException(mastraError);
393
+ this.logger.error('Failed to convert tools:', {
394
+ error: mastraError.toString(),
395
+ });
396
+ throw mastraError;
397
+ }
379
398
 
380
399
  const allConvertedTools = { ...definedConvertedTools, ...agentDerivedTools, ...workflowDerivedTools };
381
400
 
@@ -776,7 +795,23 @@ export class MCPServer extends MCPServerBase {
776
795
  */
777
796
  public async startStdio(): Promise<void> {
778
797
  this.stdioTransport = new StdioServerTransport();
779
- await this.server.connect(this.stdioTransport);
798
+ try {
799
+ await this.server.connect(this.stdioTransport);
800
+ } catch (error) {
801
+ const mastraError = new MastraError(
802
+ {
803
+ id: 'MCP_SERVER_STDIO_CONNECTION_FAILED',
804
+ domain: ErrorDomain.MCP,
805
+ category: ErrorCategory.THIRD_PARTY,
806
+ },
807
+ error,
808
+ );
809
+ this.logger.trackException(mastraError);
810
+ this.logger.error('Failed to connect MCP server using stdio transport:', {
811
+ error: mastraError.toString(),
812
+ });
813
+ throw mastraError;
814
+ }
780
815
  this.logger.info('Started MCP Server (stdio)');
781
816
  }
782
817
 
@@ -791,23 +826,42 @@ export class MCPServer extends MCPServerBase {
791
826
  * @param res HTTP response (must support .write/.end)
792
827
  */
793
828
  public async startSSE({ url, ssePath, messagePath, req, res }: MCPServerSSEOptions): Promise<void> {
794
- if (url.pathname === ssePath) {
795
- await this.connectSSE({
796
- messagePath,
797
- res,
798
- });
799
- } else if (url.pathname === messagePath) {
800
- this.logger.debug('Received message');
801
- if (!this.sseTransport) {
802
- res.writeHead(503);
803
- res.end('SSE connection not established');
804
- return;
829
+ try {
830
+ if (url.pathname === ssePath) {
831
+ await this.connectSSE({
832
+ messagePath,
833
+ res,
834
+ });
835
+ } else if (url.pathname === messagePath) {
836
+ this.logger.debug('Received message');
837
+ if (!this.sseTransport) {
838
+ res.writeHead(503);
839
+ res.end('SSE connection not established');
840
+ return;
841
+ }
842
+ await this.sseTransport.handlePostMessage(req, res);
843
+ } else {
844
+ this.logger.debug('Unknown path:', { path: url.pathname });
845
+ res.writeHead(404);
846
+ res.end();
805
847
  }
806
- await this.sseTransport.handlePostMessage(req, res);
807
- } else {
808
- this.logger.debug('Unknown path:', { path: url.pathname });
809
- res.writeHead(404);
810
- res.end();
848
+ } catch (e) {
849
+ const mastraError = new MastraError(
850
+ {
851
+ id: 'MCP_SERVER_SSE_START_FAILED',
852
+ domain: ErrorDomain.MCP,
853
+ category: ErrorCategory.USER,
854
+ details: {
855
+ url: url.toString(),
856
+ ssePath,
857
+ messagePath,
858
+ },
859
+ },
860
+ e,
861
+ );
862
+ this.logger.trackException(mastraError);
863
+ this.logger.error('Failed to start MCP Server (SSE):', { error: mastraError.toString() });
864
+ throw mastraError;
811
865
  }
812
866
  }
813
867
 
@@ -821,31 +875,50 @@ export class MCPServer extends MCPServerBase {
821
875
  * @param context Incoming Hono context
822
876
  */
823
877
  public async startHonoSSE({ url, ssePath, messagePath, context }: MCPServerHonoSSEOptions) {
824
- if (url.pathname === ssePath) {
825
- return streamSSE(context, async stream => {
826
- await this.connectHonoSSE({
827
- messagePath,
828
- stream,
878
+ try {
879
+ if (url.pathname === ssePath) {
880
+ return streamSSE(context, async stream => {
881
+ await this.connectHonoSSE({
882
+ messagePath,
883
+ stream,
884
+ });
829
885
  });
830
- });
831
- } else if (url.pathname === messagePath) {
832
- this.logger.debug('Received message');
833
- const sessionId = context.req.query('sessionId');
834
- this.logger.debug('Received message for sessionId', { sessionId });
835
- if (!sessionId) {
836
- return context.text('No sessionId provided', 400);
837
- }
838
- if (!this.sseHonoTransports.has(sessionId)) {
839
- return context.text(`No transport found for sessionId ${sessionId}`, 400);
840
- }
841
- const message = await this.sseHonoTransports.get(sessionId)?.handlePostMessage(context);
842
- if (!message) {
843
- return context.text('Transport not found', 400);
886
+ } else if (url.pathname === messagePath) {
887
+ this.logger.debug('Received message');
888
+ const sessionId = context.req.query('sessionId');
889
+ this.logger.debug('Received message for sessionId', { sessionId });
890
+ if (!sessionId) {
891
+ return context.text('No sessionId provided', 400);
892
+ }
893
+ if (!this.sseHonoTransports.has(sessionId)) {
894
+ return context.text(`No transport found for sessionId ${sessionId}`, 400);
895
+ }
896
+ const message = await this.sseHonoTransports.get(sessionId)?.handlePostMessage(context);
897
+ if (!message) {
898
+ return context.text('Transport not found', 400);
899
+ }
900
+ return message;
901
+ } else {
902
+ this.logger.debug('Unknown path:', { path: url.pathname });
903
+ return context.text('Unknown path', 404);
844
904
  }
845
- return message;
846
- } else {
847
- this.logger.debug('Unknown path:', { path: url.pathname });
848
- return context.text('Unknown path', 404);
905
+ } catch (e) {
906
+ const mastraError = new MastraError(
907
+ {
908
+ id: 'MCP_SERVER_HONO_SSE_START_FAILED',
909
+ domain: ErrorDomain.MCP,
910
+ category: ErrorCategory.USER,
911
+ details: {
912
+ url: url.toString(),
913
+ ssePath,
914
+ messagePath,
915
+ },
916
+ },
917
+ e,
918
+ );
919
+ this.logger.trackException(mastraError);
920
+ this.logger.error('Failed to start MCP Server (Hono SSE):', { error: mastraError.toString() });
921
+ throw mastraError;
849
922
  }
850
923
  }
851
924
 
@@ -1011,7 +1084,17 @@ export class MCPServer extends MCPServerBase {
1011
1084
  }
1012
1085
  }
1013
1086
  } catch (error) {
1014
- this.logger.error('startHTTP: Error handling Streamable HTTP request:', { error });
1087
+ const mastraError = new MastraError(
1088
+ {
1089
+ id: 'MCP_SERVER_HTTP_CONNECTION_FAILED',
1090
+ domain: ErrorDomain.MCP,
1091
+ category: ErrorCategory.USER,
1092
+ text: 'Failed to connect MCP server using HTTP transport',
1093
+ },
1094
+ error,
1095
+ );
1096
+ this.logger.trackException(mastraError);
1097
+ this.logger.error('startHTTP: Error handling Streamable HTTP request:', { error: mastraError });
1015
1098
  // If headers haven't been sent, send an error response
1016
1099
  if (!res.headersSent) {
1017
1100
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1025,9 +1108,6 @@ export class MCPServer extends MCPServerBase {
1025
1108
  id: null, // Cannot determine original request ID in catch
1026
1109
  }),
1027
1110
  );
1028
- } else {
1029
- // If headers were already sent (e.g., during SSE stream), just log the error
1030
- this.logger.error('startHTTP: Error after headers sent:', error);
1031
1111
  }
1032
1112
  }
1033
1113
  }
@@ -1039,18 +1119,35 @@ export class MCPServer extends MCPServerBase {
1039
1119
  messagePath: string;
1040
1120
  res: http.ServerResponse<http.IncomingMessage>;
1041
1121
  }) {
1042
- this.logger.debug('Received SSE connection');
1043
- this.sseTransport = new SSEServerTransport(messagePath, res);
1044
- await this.server.connect(this.sseTransport);
1122
+ try {
1123
+ this.logger.debug('Received SSE connection');
1124
+ this.sseTransport = new SSEServerTransport(messagePath, res);
1125
+ await this.server.connect(this.sseTransport);
1045
1126
 
1046
- this.server.onclose = async () => {
1047
- this.sseTransport = undefined;
1048
- await this.server.close();
1049
- };
1127
+ this.server.onclose = async () => {
1128
+ this.sseTransport = undefined;
1129
+ await this.server.close();
1130
+ };
1050
1131
 
1051
- res.on('close', () => {
1052
- this.sseTransport = undefined;
1053
- });
1132
+ res.on('close', () => {
1133
+ this.sseTransport = undefined;
1134
+ });
1135
+ } catch (e) {
1136
+ const mastraError = new MastraError(
1137
+ {
1138
+ id: 'MCP_SERVER_SSE_CONNECT_FAILED',
1139
+ domain: ErrorDomain.MCP,
1140
+ category: ErrorCategory.USER,
1141
+ details: {
1142
+ messagePath,
1143
+ },
1144
+ },
1145
+ e,
1146
+ );
1147
+ this.logger.trackException(mastraError);
1148
+ this.logger.error('Failed to connect to MCP Server (SSE):', { error: mastraError });
1149
+ throw mastraError;
1150
+ }
1054
1151
  }
1055
1152
 
1056
1153
  public async connectHonoSSE({ messagePath, stream }: { messagePath: string; stream: SSEStreamingApi }) {
@@ -1064,21 +1161,37 @@ export class MCPServer extends MCPServerBase {
1064
1161
  this.logger.debug('SSE Transport aborted with sessionId:', { sessionId });
1065
1162
  this.sseHonoTransports.delete(sessionId);
1066
1163
  });
1164
+ try {
1165
+ await this.server.connect(sseTransport);
1166
+ this.server.onclose = async () => {
1167
+ this.logger.debug('SSE Transport closed with sessionId:', { sessionId });
1168
+ this.sseHonoTransports.delete(sessionId);
1169
+ await this.server.close();
1170
+ };
1067
1171
 
1068
- await this.server.connect(sseTransport);
1069
- this.server.onclose = async () => {
1070
- this.logger.debug('SSE Transport closed with sessionId:', { sessionId });
1071
- this.sseHonoTransports.delete(sessionId);
1072
- await this.server.close();
1073
- };
1074
-
1075
- while (true) {
1076
- // This will keep the connection alive
1077
- // You can also await for a promise that never resolves
1078
- const sessionIds = Array.from(this.sseHonoTransports.keys() || []);
1079
- this.logger.debug('Active Hono SSE sessions:', { sessionIds });
1080
- await stream.write(':keep-alive\n\n');
1081
- await stream.sleep(60_000);
1172
+ while (true) {
1173
+ // This will keep the connection alive
1174
+ // You can also await for a promise that never resolves
1175
+ const sessionIds = Array.from(this.sseHonoTransports.keys() || []);
1176
+ this.logger.debug('Active Hono SSE sessions:', { sessionIds });
1177
+ await stream.write(':keep-alive\n\n');
1178
+ await stream.sleep(60_000);
1179
+ }
1180
+ } catch (e) {
1181
+ const mastraError = new MastraError(
1182
+ {
1183
+ id: 'MCP_SERVER_HONO_SSE_CONNECT_FAILED',
1184
+ domain: ErrorDomain.MCP,
1185
+ category: ErrorCategory.USER,
1186
+ details: {
1187
+ messagePath,
1188
+ },
1189
+ },
1190
+ e,
1191
+ );
1192
+ this.logger.trackException(mastraError);
1193
+ this.logger.error('Failed to connect to MCP Server (Hono SSE):', { error: mastraError });
1194
+ throw mastraError;
1082
1195
  }
1083
1196
  }
1084
1197
 
@@ -1119,7 +1232,17 @@ export class MCPServer extends MCPServerBase {
1119
1232
  await this.server.close();
1120
1233
  this.logger.info('MCP server closed.');
1121
1234
  } catch (error) {
1122
- this.logger.error('Error closing MCP server:', { error });
1235
+ const mastraError = new MastraError(
1236
+ {
1237
+ id: 'MCP_SERVER_CLOSE_FAILED',
1238
+ domain: ErrorDomain.MCP,
1239
+ category: ErrorCategory.THIRD_PARTY,
1240
+ },
1241
+ error,
1242
+ );
1243
+ this.logger.trackException(mastraError);
1244
+ this.logger.error('Error closing MCP server:', { error: mastraError });
1245
+ throw mastraError;
1123
1246
  }
1124
1247
  }
1125
1248
 
@@ -1210,35 +1333,52 @@ export class MCPServer extends MCPServerBase {
1210
1333
  executionContext?: { messages?: any[]; toolCallId?: string },
1211
1334
  ): Promise<any> {
1212
1335
  const tool = this.convertedTools[toolId];
1213
- if (!tool) {
1214
- this.logger.warn(`ExecuteTool: Unknown tool '${toolId}' requested on MCPServer '${this.name}'.`);
1215
- throw new Error(`Unknown tool: ${toolId}`);
1216
- }
1336
+ let validatedArgs: any;
1337
+ try {
1338
+ if (!tool) {
1339
+ this.logger.warn(`ExecuteTool: Unknown tool '${toolId}' requested on MCPServer '${this.name}'.`);
1340
+ throw new Error(`Unknown tool: ${toolId}`);
1341
+ }
1217
1342
 
1218
- this.logger.debug(`ExecuteTool: Invoking '${toolId}' with arguments:`, args);
1219
-
1220
- let validatedArgs = args;
1221
- if (tool.parameters instanceof z.ZodType && typeof tool.parameters.safeParse === 'function') {
1222
- const validation = tool.parameters.safeParse(args ?? {});
1223
- if (!validation.success) {
1224
- const errorMessages = validation.error.errors
1225
- .map((e: z.ZodIssue) => `${e.path.join('.')}: ${e.message}`)
1226
- .join(', ');
1227
- this.logger.warn(`ExecuteTool: Invalid tool arguments for '${toolId}': ${errorMessages}`, {
1228
- errors: validation.error.format(),
1229
- });
1230
- throw new z.ZodError(validation.error.issues);
1343
+ this.logger.debug(`ExecuteTool: Invoking '${toolId}' with arguments:`, args);
1344
+
1345
+ if (tool.parameters instanceof z.ZodType && typeof tool.parameters.safeParse === 'function') {
1346
+ const validation = tool.parameters.safeParse(args ?? {});
1347
+ if (!validation.success) {
1348
+ const errorMessages = validation.error.errors
1349
+ .map((e: z.ZodIssue) => `${e.path.join('.')}: ${e.message}`)
1350
+ .join(', ');
1351
+ this.logger.warn(`ExecuteTool: Invalid tool arguments for '${toolId}': ${errorMessages}`, {
1352
+ errors: validation.error.format(),
1353
+ });
1354
+ throw new z.ZodError(validation.error.issues);
1355
+ }
1356
+ validatedArgs = validation.data;
1357
+ } else {
1358
+ this.logger.debug(
1359
+ `ExecuteTool: Tool '${toolId}' parameters is not a Zod schema with safeParse or is undefined. Skipping validation.`,
1360
+ );
1231
1361
  }
1232
- validatedArgs = validation.data;
1233
- } else {
1234
- this.logger.debug(
1235
- `ExecuteTool: Tool '${toolId}' parameters is not a Zod schema with safeParse or is undefined. Skipping validation.`,
1236
- );
1237
- }
1238
1362
 
1239
- if (!tool.execute) {
1240
- this.logger.error(`ExecuteTool: Tool '${toolId}' does not have an execute function.`);
1241
- throw new Error(`Tool '${toolId}' cannot be executed.`);
1363
+ if (!tool.execute) {
1364
+ this.logger.error(`ExecuteTool: Tool '${toolId}' does not have an execute function.`);
1365
+ throw new Error(`Tool '${toolId}' cannot be executed.`);
1366
+ }
1367
+ } catch (error) {
1368
+ const mastraError = new MastraError(
1369
+ {
1370
+ id: 'MCP_SERVER_TOOL_EXECUTE_PREPARATION_FAILED',
1371
+ domain: ErrorDomain.MCP,
1372
+ category: ErrorCategory.USER,
1373
+ details: {
1374
+ toolId,
1375
+ args,
1376
+ },
1377
+ },
1378
+ error,
1379
+ );
1380
+ this.logger.trackException(mastraError);
1381
+ throw mastraError;
1242
1382
  }
1243
1383
 
1244
1384
  try {
@@ -1250,8 +1390,21 @@ export class MCPServer extends MCPServerBase {
1250
1390
  this.logger.info(`ExecuteTool: Tool '${toolId}' executed successfully.`);
1251
1391
  return result;
1252
1392
  } catch (error) {
1393
+ const mastraError = new MastraError(
1394
+ {
1395
+ id: 'MCP_SERVER_TOOL_EXECUTE_FAILED',
1396
+ domain: ErrorDomain.MCP,
1397
+ category: ErrorCategory.USER,
1398
+ details: {
1399
+ toolId,
1400
+ validatedArgs: validatedArgs,
1401
+ },
1402
+ },
1403
+ error,
1404
+ );
1405
+ this.logger.trackException(mastraError);
1253
1406
  this.logger.error(`ExecuteTool: Tool execution failed for '${toolId}':`, { error });
1254
- throw error instanceof Error ? error : new Error(`Execution of tool '${toolId}' failed: ${String(error)}`);
1407
+ throw mastraError;
1255
1408
  }
1256
1409
  }
1257
1410
  }
@@ -1,21 +0,0 @@
1
- #!/bin/sh
2
- basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
-
4
- case `uname` in
5
- *CYGWIN*|*MINGW*|*MSYS*)
6
- if command -v cygpath > /dev/null 2>&1; then
7
- basedir=`cygpath -w "$basedir"`
8
- fi
9
- ;;
10
- esac
11
-
12
- if [ -z "$NODE_PATH" ]; then
13
- export NODE_PATH="/home/runner/work/mastra/mastra/packages/cli/dist/node_modules:/home/runner/work/mastra/mastra/packages/cli/node_modules:/home/runner/work/mastra/mastra/packages/node_modules:/home/runner/work/mastra/mastra/node_modules:/home/runner/work/mastra/node_modules:/home/runner/work/node_modules:/home/runner/node_modules:/home/node_modules:/node_modules:/home/runner/work/mastra/mastra/node_modules/.pnpm/node_modules"
14
- else
15
- export NODE_PATH="/home/runner/work/mastra/mastra/packages/cli/dist/node_modules:/home/runner/work/mastra/mastra/packages/cli/node_modules:/home/runner/work/mastra/mastra/packages/node_modules:/home/runner/work/mastra/mastra/node_modules:/home/runner/work/mastra/node_modules:/home/runner/work/node_modules:/home/runner/node_modules:/home/node_modules:/node_modules:/home/runner/work/mastra/mastra/node_modules/.pnpm/node_modules:$NODE_PATH"
16
- fi
17
- if [ -x "$basedir/node" ]; then
18
- exec "$basedir/node" "$basedir/../mastra/dist/index.js" "$@"
19
- else
20
- exec node "$basedir/../mastra/dist/index.js" "$@"
21
- fi