@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
package/src/server/server.ts
CHANGED
|
@@ -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,
|
|
@@ -38,6 +39,7 @@ import type {
|
|
|
38
39
|
ResourceTemplate,
|
|
39
40
|
ServerCapabilities,
|
|
40
41
|
Prompt,
|
|
42
|
+
CallToolResult,
|
|
41
43
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
42
44
|
import type { SSEStreamingApi } from 'hono/streaming';
|
|
43
45
|
import { streamSSE } from 'hono/streaming';
|
|
@@ -319,6 +321,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
319
321
|
name: workflowToolName,
|
|
320
322
|
description: coreTool.description,
|
|
321
323
|
parameters: coreTool.parameters,
|
|
324
|
+
outputSchema: coreTool.outputSchema,
|
|
322
325
|
execute: coreTool.execute!,
|
|
323
326
|
toolType: 'workflow',
|
|
324
327
|
};
|
|
@@ -368,14 +371,33 @@ export class MCPServer extends MCPServerBase {
|
|
|
368
371
|
name: toolName,
|
|
369
372
|
description: coreTool.description,
|
|
370
373
|
parameters: coreTool.parameters,
|
|
374
|
+
outputSchema: coreTool.outputSchema,
|
|
371
375
|
execute: coreTool.execute!,
|
|
372
376
|
};
|
|
373
377
|
this.logger.info(`Registered explicit tool: '${toolName}'`);
|
|
374
378
|
}
|
|
375
379
|
this.logger.info(`Total defined tools registered: ${Object.keys(definedConvertedTools).length}`);
|
|
376
380
|
|
|
377
|
-
|
|
378
|
-
|
|
381
|
+
let agentDerivedTools: Record<string, ConvertedTool> = {};
|
|
382
|
+
let workflowDerivedTools: Record<string, ConvertedTool> = {};
|
|
383
|
+
try {
|
|
384
|
+
agentDerivedTools = this.convertAgentsToTools(agentsConfig, definedConvertedTools);
|
|
385
|
+
workflowDerivedTools = this.convertWorkflowsToTools(workflowsConfig, definedConvertedTools);
|
|
386
|
+
} catch (e) {
|
|
387
|
+
const mastraError = new MastraError(
|
|
388
|
+
{
|
|
389
|
+
id: 'MCP_SERVER_AGENT_OR_WORKFLOW_TOOL_CONVERSION_FAILED',
|
|
390
|
+
domain: ErrorDomain.MCP,
|
|
391
|
+
category: ErrorCategory.USER,
|
|
392
|
+
},
|
|
393
|
+
e,
|
|
394
|
+
);
|
|
395
|
+
this.logger.trackException(mastraError);
|
|
396
|
+
this.logger.error('Failed to convert tools:', {
|
|
397
|
+
error: mastraError.toString(),
|
|
398
|
+
});
|
|
399
|
+
throw mastraError;
|
|
400
|
+
}
|
|
379
401
|
|
|
380
402
|
const allConvertedTools = { ...definedConvertedTools, ...agentDerivedTools, ...workflowDerivedTools };
|
|
381
403
|
|
|
@@ -401,11 +423,17 @@ export class MCPServer extends MCPServerBase {
|
|
|
401
423
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
402
424
|
this.logger.debug('Handling ListTools request');
|
|
403
425
|
return {
|
|
404
|
-
tools: Object.values(this.convertedTools).map(tool =>
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
426
|
+
tools: Object.values(this.convertedTools).map(tool => {
|
|
427
|
+
const toolSpec: any = {
|
|
428
|
+
name: tool.name,
|
|
429
|
+
description: tool.description,
|
|
430
|
+
inputSchema: tool.parameters.jsonSchema,
|
|
431
|
+
};
|
|
432
|
+
if (tool.outputSchema) {
|
|
433
|
+
toolSpec.outputSchema = tool.outputSchema.jsonSchema;
|
|
434
|
+
}
|
|
435
|
+
return toolSpec;
|
|
436
|
+
}),
|
|
409
437
|
};
|
|
410
438
|
});
|
|
411
439
|
}
|
|
@@ -430,8 +458,6 @@ export class MCPServer extends MCPServerBase {
|
|
|
430
458
|
};
|
|
431
459
|
}
|
|
432
460
|
|
|
433
|
-
this.logger.debug(`CallTool: Invoking '${request.params.name}' with arguments:`, request.params.arguments);
|
|
434
|
-
|
|
435
461
|
const validation = tool.parameters.validate?.(request.params.arguments ?? {});
|
|
436
462
|
if (validation && !validation.success) {
|
|
437
463
|
this.logger.warn(`CallTool: Invalid tool arguments for '${request.params.name}'`, {
|
|
@@ -451,17 +477,43 @@ export class MCPServer extends MCPServerBase {
|
|
|
451
477
|
}
|
|
452
478
|
|
|
453
479
|
const result = await tool.execute(validation?.value, { messages: [], toolCallId: '' });
|
|
480
|
+
|
|
481
|
+
this.logger.debug(`CallTool: Tool '${request.params.name}' executed successfully with result:`, result);
|
|
454
482
|
const duration = Date.now() - startTime;
|
|
455
483
|
this.logger.info(`Tool '${request.params.name}' executed successfully in ${duration}ms.`);
|
|
456
|
-
|
|
457
|
-
|
|
484
|
+
|
|
485
|
+
const response: CallToolResult = { isError: false, content: [] };
|
|
486
|
+
|
|
487
|
+
if (tool.outputSchema) {
|
|
488
|
+
if (!result.structuredContent) {
|
|
489
|
+
throw new Error(`Tool ${request.params.name} has an output schema but no structured content was provided.`);
|
|
490
|
+
}
|
|
491
|
+
const outputValidation = tool.outputSchema.validate?.(result.structuredContent ?? {});
|
|
492
|
+
if (outputValidation && !outputValidation.success) {
|
|
493
|
+
this.logger.warn(`CallTool: Invalid structured content for '${request.params.name}'`, {
|
|
494
|
+
errors: outputValidation.error,
|
|
495
|
+
});
|
|
496
|
+
throw new Error(
|
|
497
|
+
`Invalid structured content for tool ${request.params.name}: ${JSON.stringify(outputValidation.error)}`,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
response.structuredContent = result.structuredContent;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (result.content) {
|
|
504
|
+
response.content = result.content;
|
|
505
|
+
} else if (response.structuredContent) {
|
|
506
|
+
response.content = [{ type: 'text', text: JSON.stringify(response.structuredContent) }];
|
|
507
|
+
} else {
|
|
508
|
+
response.content = [
|
|
458
509
|
{
|
|
459
510
|
type: 'text',
|
|
460
511
|
text: typeof result === 'string' ? result : JSON.stringify(result),
|
|
461
512
|
},
|
|
462
|
-
]
|
|
463
|
-
|
|
464
|
-
|
|
513
|
+
];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return response;
|
|
465
517
|
} catch (error) {
|
|
466
518
|
const duration = Date.now() - startTime;
|
|
467
519
|
if (error instanceof z.ZodError) {
|
|
@@ -645,7 +697,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
645
697
|
return;
|
|
646
698
|
}
|
|
647
699
|
this.subscribeResourceHandlerIsRegistered = true;
|
|
648
|
-
this.server.setRequestHandler(SubscribeRequestSchema
|
|
700
|
+
this.server.setRequestHandler(SubscribeRequestSchema, async (request: { params: { uri: string } }) => {
|
|
649
701
|
const uri = request.params.uri;
|
|
650
702
|
this.logger.info(`Received resources/subscribe request for URI: ${uri}`);
|
|
651
703
|
this.subscriptions.add(uri);
|
|
@@ -662,7 +714,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
662
714
|
}
|
|
663
715
|
this.unsubscribeResourceHandlerIsRegistered = true;
|
|
664
716
|
|
|
665
|
-
this.server.setRequestHandler(UnsubscribeRequestSchema
|
|
717
|
+
this.server.setRequestHandler(UnsubscribeRequestSchema, async (request: { params: { uri: string } }) => {
|
|
666
718
|
const uri = request.params.uri;
|
|
667
719
|
this.logger.info(`Received resources/unsubscribe request for URI: ${uri}`);
|
|
668
720
|
this.subscriptions.delete(uri);
|
|
@@ -776,7 +828,23 @@ export class MCPServer extends MCPServerBase {
|
|
|
776
828
|
*/
|
|
777
829
|
public async startStdio(): Promise<void> {
|
|
778
830
|
this.stdioTransport = new StdioServerTransport();
|
|
779
|
-
|
|
831
|
+
try {
|
|
832
|
+
await this.server.connect(this.stdioTransport);
|
|
833
|
+
} catch (error) {
|
|
834
|
+
const mastraError = new MastraError(
|
|
835
|
+
{
|
|
836
|
+
id: 'MCP_SERVER_STDIO_CONNECTION_FAILED',
|
|
837
|
+
domain: ErrorDomain.MCP,
|
|
838
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
839
|
+
},
|
|
840
|
+
error,
|
|
841
|
+
);
|
|
842
|
+
this.logger.trackException(mastraError);
|
|
843
|
+
this.logger.error('Failed to connect MCP server using stdio transport:', {
|
|
844
|
+
error: mastraError.toString(),
|
|
845
|
+
});
|
|
846
|
+
throw mastraError;
|
|
847
|
+
}
|
|
780
848
|
this.logger.info('Started MCP Server (stdio)');
|
|
781
849
|
}
|
|
782
850
|
|
|
@@ -791,23 +859,42 @@ export class MCPServer extends MCPServerBase {
|
|
|
791
859
|
* @param res HTTP response (must support .write/.end)
|
|
792
860
|
*/
|
|
793
861
|
public async startSSE({ url, ssePath, messagePath, req, res }: MCPServerSSEOptions): Promise<void> {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
862
|
+
try {
|
|
863
|
+
if (url.pathname === ssePath) {
|
|
864
|
+
await this.connectSSE({
|
|
865
|
+
messagePath,
|
|
866
|
+
res,
|
|
867
|
+
});
|
|
868
|
+
} else if (url.pathname === messagePath) {
|
|
869
|
+
this.logger.debug('Received message');
|
|
870
|
+
if (!this.sseTransport) {
|
|
871
|
+
res.writeHead(503);
|
|
872
|
+
res.end('SSE connection not established');
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
await this.sseTransport.handlePostMessage(req, res);
|
|
876
|
+
} else {
|
|
877
|
+
this.logger.debug('Unknown path:', { path: url.pathname });
|
|
878
|
+
res.writeHead(404);
|
|
879
|
+
res.end();
|
|
805
880
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
881
|
+
} catch (e) {
|
|
882
|
+
const mastraError = new MastraError(
|
|
883
|
+
{
|
|
884
|
+
id: 'MCP_SERVER_SSE_START_FAILED',
|
|
885
|
+
domain: ErrorDomain.MCP,
|
|
886
|
+
category: ErrorCategory.USER,
|
|
887
|
+
details: {
|
|
888
|
+
url: url.toString(),
|
|
889
|
+
ssePath,
|
|
890
|
+
messagePath,
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
e,
|
|
894
|
+
);
|
|
895
|
+
this.logger.trackException(mastraError);
|
|
896
|
+
this.logger.error('Failed to start MCP Server (SSE):', { error: mastraError.toString() });
|
|
897
|
+
throw mastraError;
|
|
811
898
|
}
|
|
812
899
|
}
|
|
813
900
|
|
|
@@ -821,31 +908,50 @@ export class MCPServer extends MCPServerBase {
|
|
|
821
908
|
* @param context Incoming Hono context
|
|
822
909
|
*/
|
|
823
910
|
public async startHonoSSE({ url, ssePath, messagePath, context }: MCPServerHonoSSEOptions) {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
911
|
+
try {
|
|
912
|
+
if (url.pathname === ssePath) {
|
|
913
|
+
return streamSSE(context, async stream => {
|
|
914
|
+
await this.connectHonoSSE({
|
|
915
|
+
messagePath,
|
|
916
|
+
stream,
|
|
917
|
+
});
|
|
829
918
|
});
|
|
830
|
-
})
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
919
|
+
} else if (url.pathname === messagePath) {
|
|
920
|
+
this.logger.debug('Received message');
|
|
921
|
+
const sessionId = context.req.query('sessionId');
|
|
922
|
+
this.logger.debug('Received message for sessionId', { sessionId });
|
|
923
|
+
if (!sessionId) {
|
|
924
|
+
return context.text('No sessionId provided', 400);
|
|
925
|
+
}
|
|
926
|
+
if (!this.sseHonoTransports.has(sessionId)) {
|
|
927
|
+
return context.text(`No transport found for sessionId ${sessionId}`, 400);
|
|
928
|
+
}
|
|
929
|
+
const message = await this.sseHonoTransports.get(sessionId)?.handlePostMessage(context);
|
|
930
|
+
if (!message) {
|
|
931
|
+
return context.text('Transport not found', 400);
|
|
932
|
+
}
|
|
933
|
+
return message;
|
|
934
|
+
} else {
|
|
935
|
+
this.logger.debug('Unknown path:', { path: url.pathname });
|
|
936
|
+
return context.text('Unknown path', 404);
|
|
844
937
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
938
|
+
} catch (e) {
|
|
939
|
+
const mastraError = new MastraError(
|
|
940
|
+
{
|
|
941
|
+
id: 'MCP_SERVER_HONO_SSE_START_FAILED',
|
|
942
|
+
domain: ErrorDomain.MCP,
|
|
943
|
+
category: ErrorCategory.USER,
|
|
944
|
+
details: {
|
|
945
|
+
url: url.toString(),
|
|
946
|
+
ssePath,
|
|
947
|
+
messagePath,
|
|
948
|
+
},
|
|
949
|
+
},
|
|
950
|
+
e,
|
|
951
|
+
);
|
|
952
|
+
this.logger.trackException(mastraError);
|
|
953
|
+
this.logger.error('Failed to start MCP Server (Hono SSE):', { error: mastraError.toString() });
|
|
954
|
+
throw mastraError;
|
|
849
955
|
}
|
|
850
956
|
}
|
|
851
957
|
|
|
@@ -1011,7 +1117,17 @@ export class MCPServer extends MCPServerBase {
|
|
|
1011
1117
|
}
|
|
1012
1118
|
}
|
|
1013
1119
|
} catch (error) {
|
|
1014
|
-
|
|
1120
|
+
const mastraError = new MastraError(
|
|
1121
|
+
{
|
|
1122
|
+
id: 'MCP_SERVER_HTTP_CONNECTION_FAILED',
|
|
1123
|
+
domain: ErrorDomain.MCP,
|
|
1124
|
+
category: ErrorCategory.USER,
|
|
1125
|
+
text: 'Failed to connect MCP server using HTTP transport',
|
|
1126
|
+
},
|
|
1127
|
+
error,
|
|
1128
|
+
);
|
|
1129
|
+
this.logger.trackException(mastraError);
|
|
1130
|
+
this.logger.error('startHTTP: Error handling Streamable HTTP request:', { error: mastraError });
|
|
1015
1131
|
// If headers haven't been sent, send an error response
|
|
1016
1132
|
if (!res.headersSent) {
|
|
1017
1133
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
@@ -1025,9 +1141,6 @@ export class MCPServer extends MCPServerBase {
|
|
|
1025
1141
|
id: null, // Cannot determine original request ID in catch
|
|
1026
1142
|
}),
|
|
1027
1143
|
);
|
|
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
1144
|
}
|
|
1032
1145
|
}
|
|
1033
1146
|
}
|
|
@@ -1039,18 +1152,35 @@ export class MCPServer extends MCPServerBase {
|
|
|
1039
1152
|
messagePath: string;
|
|
1040
1153
|
res: http.ServerResponse<http.IncomingMessage>;
|
|
1041
1154
|
}) {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1155
|
+
try {
|
|
1156
|
+
this.logger.debug('Received SSE connection');
|
|
1157
|
+
this.sseTransport = new SSEServerTransport(messagePath, res);
|
|
1158
|
+
await this.server.connect(this.sseTransport);
|
|
1045
1159
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1160
|
+
this.server.onclose = async () => {
|
|
1161
|
+
this.sseTransport = undefined;
|
|
1162
|
+
await this.server.close();
|
|
1163
|
+
};
|
|
1050
1164
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1165
|
+
res.on('close', () => {
|
|
1166
|
+
this.sseTransport = undefined;
|
|
1167
|
+
});
|
|
1168
|
+
} catch (e) {
|
|
1169
|
+
const mastraError = new MastraError(
|
|
1170
|
+
{
|
|
1171
|
+
id: 'MCP_SERVER_SSE_CONNECT_FAILED',
|
|
1172
|
+
domain: ErrorDomain.MCP,
|
|
1173
|
+
category: ErrorCategory.USER,
|
|
1174
|
+
details: {
|
|
1175
|
+
messagePath,
|
|
1176
|
+
},
|
|
1177
|
+
},
|
|
1178
|
+
e,
|
|
1179
|
+
);
|
|
1180
|
+
this.logger.trackException(mastraError);
|
|
1181
|
+
this.logger.error('Failed to connect to MCP Server (SSE):', { error: mastraError });
|
|
1182
|
+
throw mastraError;
|
|
1183
|
+
}
|
|
1054
1184
|
}
|
|
1055
1185
|
|
|
1056
1186
|
public async connectHonoSSE({ messagePath, stream }: { messagePath: string; stream: SSEStreamingApi }) {
|
|
@@ -1064,21 +1194,37 @@ export class MCPServer extends MCPServerBase {
|
|
|
1064
1194
|
this.logger.debug('SSE Transport aborted with sessionId:', { sessionId });
|
|
1065
1195
|
this.sseHonoTransports.delete(sessionId);
|
|
1066
1196
|
});
|
|
1197
|
+
try {
|
|
1198
|
+
await this.server.connect(sseTransport);
|
|
1199
|
+
this.server.onclose = async () => {
|
|
1200
|
+
this.logger.debug('SSE Transport closed with sessionId:', { sessionId });
|
|
1201
|
+
this.sseHonoTransports.delete(sessionId);
|
|
1202
|
+
await this.server.close();
|
|
1203
|
+
};
|
|
1067
1204
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1205
|
+
while (true) {
|
|
1206
|
+
// This will keep the connection alive
|
|
1207
|
+
// You can also await for a promise that never resolves
|
|
1208
|
+
const sessionIds = Array.from(this.sseHonoTransports.keys() || []);
|
|
1209
|
+
this.logger.debug('Active Hono SSE sessions:', { sessionIds });
|
|
1210
|
+
await stream.write(':keep-alive\n\n');
|
|
1211
|
+
await stream.sleep(60_000);
|
|
1212
|
+
}
|
|
1213
|
+
} catch (e) {
|
|
1214
|
+
const mastraError = new MastraError(
|
|
1215
|
+
{
|
|
1216
|
+
id: 'MCP_SERVER_HONO_SSE_CONNECT_FAILED',
|
|
1217
|
+
domain: ErrorDomain.MCP,
|
|
1218
|
+
category: ErrorCategory.USER,
|
|
1219
|
+
details: {
|
|
1220
|
+
messagePath,
|
|
1221
|
+
},
|
|
1222
|
+
},
|
|
1223
|
+
e,
|
|
1224
|
+
);
|
|
1225
|
+
this.logger.trackException(mastraError);
|
|
1226
|
+
this.logger.error('Failed to connect to MCP Server (Hono SSE):', { error: mastraError });
|
|
1227
|
+
throw mastraError;
|
|
1082
1228
|
}
|
|
1083
1229
|
}
|
|
1084
1230
|
|
|
@@ -1119,7 +1265,17 @@ export class MCPServer extends MCPServerBase {
|
|
|
1119
1265
|
await this.server.close();
|
|
1120
1266
|
this.logger.info('MCP server closed.');
|
|
1121
1267
|
} catch (error) {
|
|
1122
|
-
|
|
1268
|
+
const mastraError = new MastraError(
|
|
1269
|
+
{
|
|
1270
|
+
id: 'MCP_SERVER_CLOSE_FAILED',
|
|
1271
|
+
domain: ErrorDomain.MCP,
|
|
1272
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1273
|
+
},
|
|
1274
|
+
error,
|
|
1275
|
+
);
|
|
1276
|
+
this.logger.trackException(mastraError);
|
|
1277
|
+
this.logger.error('Error closing MCP server:', { error: mastraError });
|
|
1278
|
+
throw mastraError;
|
|
1123
1279
|
}
|
|
1124
1280
|
}
|
|
1125
1281
|
|
|
@@ -1160,7 +1316,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
1160
1316
|
* @returns An object containing an array of tool information.
|
|
1161
1317
|
*/
|
|
1162
1318
|
public getToolListInfo(): {
|
|
1163
|
-
tools: Array<{ name: string; description?: string; inputSchema: any; toolType?: MCPToolType }>;
|
|
1319
|
+
tools: Array<{ name: string; description?: string; inputSchema: any; outputSchema?: any; toolType?: MCPToolType }>;
|
|
1164
1320
|
} {
|
|
1165
1321
|
this.logger.debug(`Getting tool list information for MCPServer '${this.name}'`);
|
|
1166
1322
|
return {
|
|
@@ -1169,6 +1325,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
1169
1325
|
name: tool.name,
|
|
1170
1326
|
description: tool.description,
|
|
1171
1327
|
inputSchema: tool.parameters?.jsonSchema || tool.parameters,
|
|
1328
|
+
outputSchema: tool.outputSchema?.jsonSchema || tool.outputSchema,
|
|
1172
1329
|
toolType: tool.toolType,
|
|
1173
1330
|
})),
|
|
1174
1331
|
};
|
|
@@ -1181,7 +1338,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
1181
1338
|
*/
|
|
1182
1339
|
public getToolInfo(
|
|
1183
1340
|
toolId: string,
|
|
1184
|
-
): { name: string; description?: string; inputSchema: any; toolType?: MCPToolType } | undefined {
|
|
1341
|
+
): { name: string; description?: string; inputSchema: any; outputSchema?: any; toolType?: MCPToolType } | undefined {
|
|
1185
1342
|
const tool = this.convertedTools[toolId];
|
|
1186
1343
|
if (!tool) {
|
|
1187
1344
|
this.logger.debug(`Tool '${toolId}' not found on MCPServer '${this.name}'`);
|
|
@@ -1192,6 +1349,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
1192
1349
|
name: tool.name,
|
|
1193
1350
|
description: tool.description,
|
|
1194
1351
|
inputSchema: tool.parameters?.jsonSchema || tool.parameters,
|
|
1352
|
+
outputSchema: tool.outputSchema?.jsonSchema || tool.outputSchema,
|
|
1195
1353
|
toolType: tool.toolType,
|
|
1196
1354
|
};
|
|
1197
1355
|
}
|
|
@@ -1210,35 +1368,52 @@ export class MCPServer extends MCPServerBase {
|
|
|
1210
1368
|
executionContext?: { messages?: any[]; toolCallId?: string },
|
|
1211
1369
|
): Promise<any> {
|
|
1212
1370
|
const tool = this.convertedTools[toolId];
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1371
|
+
let validatedArgs: any;
|
|
1372
|
+
try {
|
|
1373
|
+
if (!tool) {
|
|
1374
|
+
this.logger.warn(`ExecuteTool: Unknown tool '${toolId}' requested on MCPServer '${this.name}'.`);
|
|
1375
|
+
throw new Error(`Unknown tool: ${toolId}`);
|
|
1376
|
+
}
|
|
1217
1377
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
.
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1378
|
+
this.logger.debug(`ExecuteTool: Invoking '${toolId}' with arguments:`, args);
|
|
1379
|
+
|
|
1380
|
+
if (tool.parameters instanceof z.ZodType && typeof tool.parameters.safeParse === 'function') {
|
|
1381
|
+
const validation = tool.parameters.safeParse(args ?? {});
|
|
1382
|
+
if (!validation.success) {
|
|
1383
|
+
const errorMessages = validation.error.errors
|
|
1384
|
+
.map((e: z.ZodIssue) => `${e.path.join('.')}: ${e.message}`)
|
|
1385
|
+
.join(', ');
|
|
1386
|
+
this.logger.warn(`ExecuteTool: Invalid tool arguments for '${toolId}': ${errorMessages}`, {
|
|
1387
|
+
errors: validation.error.format(),
|
|
1388
|
+
});
|
|
1389
|
+
throw new z.ZodError(validation.error.issues);
|
|
1390
|
+
}
|
|
1391
|
+
validatedArgs = validation.data;
|
|
1392
|
+
} else {
|
|
1393
|
+
this.logger.debug(
|
|
1394
|
+
`ExecuteTool: Tool '${toolId}' parameters is not a Zod schema with safeParse or is undefined. Skipping validation.`,
|
|
1395
|
+
);
|
|
1231
1396
|
}
|
|
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
1397
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1398
|
+
if (!tool.execute) {
|
|
1399
|
+
this.logger.error(`ExecuteTool: Tool '${toolId}' does not have an execute function.`);
|
|
1400
|
+
throw new Error(`Tool '${toolId}' cannot be executed.`);
|
|
1401
|
+
}
|
|
1402
|
+
} catch (error) {
|
|
1403
|
+
const mastraError = new MastraError(
|
|
1404
|
+
{
|
|
1405
|
+
id: 'MCP_SERVER_TOOL_EXECUTE_PREPARATION_FAILED',
|
|
1406
|
+
domain: ErrorDomain.MCP,
|
|
1407
|
+
category: ErrorCategory.USER,
|
|
1408
|
+
details: {
|
|
1409
|
+
toolId,
|
|
1410
|
+
args,
|
|
1411
|
+
},
|
|
1412
|
+
},
|
|
1413
|
+
error,
|
|
1414
|
+
);
|
|
1415
|
+
this.logger.trackException(mastraError);
|
|
1416
|
+
throw mastraError;
|
|
1242
1417
|
}
|
|
1243
1418
|
|
|
1244
1419
|
try {
|
|
@@ -1250,8 +1425,21 @@ export class MCPServer extends MCPServerBase {
|
|
|
1250
1425
|
this.logger.info(`ExecuteTool: Tool '${toolId}' executed successfully.`);
|
|
1251
1426
|
return result;
|
|
1252
1427
|
} catch (error) {
|
|
1428
|
+
const mastraError = new MastraError(
|
|
1429
|
+
{
|
|
1430
|
+
id: 'MCP_SERVER_TOOL_EXECUTE_FAILED',
|
|
1431
|
+
domain: ErrorDomain.MCP,
|
|
1432
|
+
category: ErrorCategory.USER,
|
|
1433
|
+
details: {
|
|
1434
|
+
toolId,
|
|
1435
|
+
validatedArgs: validatedArgs,
|
|
1436
|
+
},
|
|
1437
|
+
},
|
|
1438
|
+
error,
|
|
1439
|
+
);
|
|
1440
|
+
this.logger.trackException(mastraError);
|
|
1253
1441
|
this.logger.error(`ExecuteTool: Tool execution failed for '${toolId}':`, { error });
|
|
1254
|
-
throw
|
|
1442
|
+
throw mastraError;
|
|
1255
1443
|
}
|
|
1256
1444
|
}
|
|
1257
1445
|
}
|
|
@@ -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
|