@mastra/mcp 0.10.5-alpha.0 → 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/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { MastraBase } from '@mastra/core/base';
2
+ import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
2
3
  import { createTool as createTool$1 } from '@mastra/core/tools';
3
4
  import { isZodType } from '@mastra/core/utils';
4
5
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
@@ -10,7 +11,6 @@ import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSche
10
11
  import { asyncExitHook, gracefulExit } from 'exit-hook';
11
12
  import { z } from 'zod';
12
13
  import { convertJsonSchemaToZod } from 'zod-from-json-schema';
13
- import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
14
14
  import equal from 'fast-deep-equal';
15
15
  import { v5 } from 'uuid';
16
16
  import { randomUUID } from 'crypto';
@@ -21,7 +21,6 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
21
21
  import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
22
22
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
23
23
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
24
- import { streamSSE } from 'hono/streaming';
25
24
 
26
25
  // src/client/client.ts
27
26
  var PromptClientActions = class {
@@ -493,7 +492,42 @@ var InternalMastraMCPClient = class extends MastraBase {
493
492
  error: errorDetails,
494
493
  originalJsonSchema: inputSchema
495
494
  });
496
- throw new Error(errorDetails);
495
+ throw new MastraError({
496
+ id: "MCP_TOOL_INPUT_SCHEMA_CONVERSION_FAILED",
497
+ domain: ErrorDomain.MCP,
498
+ category: ErrorCategory.USER,
499
+ details: { error: errorDetails ?? "Unknown error" }
500
+ });
501
+ }
502
+ }
503
+ convertOutputSchema(outputSchema) {
504
+ if (!outputSchema) return;
505
+ if (isZodType(outputSchema)) {
506
+ return outputSchema;
507
+ }
508
+ try {
509
+ return convertJsonSchemaToZod(outputSchema);
510
+ } catch (error) {
511
+ let errorDetails;
512
+ if (error instanceof Error) {
513
+ errorDetails = error.stack;
514
+ } else {
515
+ try {
516
+ errorDetails = JSON.stringify(error);
517
+ } catch {
518
+ errorDetails = String(error);
519
+ }
520
+ }
521
+ this.log("error", "Failed to convert JSON schema to Zod schema using zodFromJsonSchema", {
522
+ error: errorDetails,
523
+ originalJsonSchema: outputSchema
524
+ });
525
+ throw new MastraError({
526
+ id: "MCP_TOOL_OUTPUT_SCHEMA_CONVERSION_FAILED",
527
+ domain: ErrorDomain.MCP,
528
+ category: ErrorCategory.USER,
529
+ details: { error: errorDetails ?? "Unknown error" }
530
+ });
497
531
  }
498
532
  }
499
533
  async tools() {
@@ -507,6 +541,7 @@ var InternalMastraMCPClient = class extends MastraBase {
507
541
  id: `${this.name}_${tool.name}`,
508
542
  description: tool.description || "",
509
543
  inputSchema: this.convertInputSchema(tool.inputSchema),
544
+ outputSchema: this.convertOutputSchema(tool.outputSchema),
510
545
  execute: async ({ context, runtimeContext }) => {
511
546
  const previousContext = this.currentOperationContext;
512
547
  this.currentOperationContext = runtimeContext || null;
@@ -929,6 +964,165 @@ var MCPConfiguration = class extends MCPClient {
929
964
  );
930
965
  }
931
966
  };
967
+
968
+ // ../../node_modules/.pnpm/hono@4.8.1/node_modules/hono/dist/utils/stream.js
969
+ var StreamingApi = class {
970
+ writer;
971
+ encoder;
972
+ writable;
973
+ abortSubscribers = [];
974
+ responseReadable;
975
+ aborted = false;
976
+ closed = false;
977
+ constructor(writable, _readable) {
978
+ this.writable = writable;
979
+ this.writer = writable.getWriter();
980
+ this.encoder = new TextEncoder();
981
+ const reader = _readable.getReader();
982
+ this.abortSubscribers.push(async () => {
983
+ await reader.cancel();
984
+ });
985
+ this.responseReadable = new ReadableStream({
986
+ async pull(controller) {
987
+ const { done, value } = await reader.read();
988
+ done ? controller.close() : controller.enqueue(value);
989
+ },
990
+ cancel: () => {
991
+ this.abort();
992
+ }
993
+ });
994
+ }
995
+ async write(input) {
996
+ try {
997
+ if (typeof input === "string") {
998
+ input = this.encoder.encode(input);
999
+ }
1000
+ await this.writer.write(input);
1001
+ } catch {
1002
+ }
1003
+ return this;
1004
+ }
1005
+ async writeln(input) {
1006
+ await this.write(input + "\n");
1007
+ return this;
1008
+ }
1009
+ sleep(ms) {
1010
+ return new Promise((res) => setTimeout(res, ms));
1011
+ }
1012
+ async close() {
1013
+ try {
1014
+ await this.writer.close();
1015
+ } catch {
1016
+ }
1017
+ this.closed = true;
1018
+ }
1019
+ async pipe(body) {
1020
+ this.writer.releaseLock();
1021
+ await body.pipeTo(this.writable, { preventClose: true });
1022
+ this.writer = this.writable.getWriter();
1023
+ }
1024
+ onAbort(listener) {
1025
+ this.abortSubscribers.push(listener);
1026
+ }
1027
+ abort() {
1028
+ if (!this.aborted) {
1029
+ this.aborted = true;
1030
+ this.abortSubscribers.forEach((subscriber) => subscriber());
1031
+ }
1032
+ }
1033
+ };
1034
+
1035
+ // ../../node_modules/.pnpm/hono@4.8.1/node_modules/hono/dist/helper/streaming/utils.js
1036
+ var isOldBunVersion = () => {
1037
+ const version = typeof Bun !== "undefined" ? Bun.version : void 0;
1038
+ if (version === void 0) {
1039
+ return false;
1040
+ }
1041
+ const result = version.startsWith("1.1") || version.startsWith("1.0") || version.startsWith("0.");
1042
+ isOldBunVersion = () => result;
1043
+ return result;
1044
+ };
1045
+
1046
+ // ../../node_modules/.pnpm/hono@4.8.1/node_modules/hono/dist/utils/html.js
1047
+ var HtmlEscapedCallbackPhase = {
1048
+ Stringify: 1};
1049
+ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
1050
+ if (typeof str === "object" && !(str instanceof String)) {
1051
+ if (!(str instanceof Promise)) {
1052
+ str = str.toString();
1053
+ }
1054
+ if (str instanceof Promise) {
1055
+ str = await str;
1056
+ }
1057
+ }
1058
+ const callbacks = str.callbacks;
1059
+ if (!callbacks?.length) {
1060
+ return Promise.resolve(str);
1061
+ }
1062
+ if (buffer) {
1063
+ buffer[0] += str;
1064
+ } else {
1065
+ buffer = [str];
1066
+ }
1067
+ const resStr = Promise.all(callbacks.map((c) => c({ phase, buffer, context }))).then(
1068
+ (res) => Promise.all(
1069
+ res.filter(Boolean).map((str2) => resolveCallback(str2, phase, false, context, buffer))
1070
+ ).then(() => buffer[0])
1071
+ );
1072
+ {
1073
+ return resStr;
1074
+ }
1075
+ };
1076
+
1077
+ // ../../node_modules/.pnpm/hono@4.8.1/node_modules/hono/dist/helper/streaming/sse.js
1078
+ var SSEStreamingApi = class extends StreamingApi {
1079
+ constructor(writable, readable) {
1080
+ super(writable, readable);
1081
+ }
1082
+ async writeSSE(message) {
1083
+ const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {});
1084
+ const dataLines = data.split("\n").map((line) => {
1085
+ return `data: ${line}`;
1086
+ }).join("\n");
1087
+ const sseData = [
1088
+ message.event && `event: ${message.event}`,
1089
+ dataLines,
1090
+ message.id && `id: ${message.id}`,
1091
+ message.retry && `retry: ${message.retry}`
1092
+ ].filter(Boolean).join("\n") + "\n\n";
1093
+ await this.write(sseData);
1094
+ }
1095
+ };
1096
+ var run = async (stream2, cb, onError) => {
1097
+ try {
1098
+ await cb(stream2);
1099
+ } catch (e) {
1100
+ {
1101
+ console.error(e);
1102
+ }
1103
+ } finally {
1104
+ stream2.close();
1105
+ }
1106
+ };
1107
+ var contextStash = /* @__PURE__ */ new WeakMap();
1108
+ var streamSSE = (c, cb, onError) => {
1109
+ const { readable, writable } = new TransformStream();
1110
+ const stream2 = new SSEStreamingApi(writable, readable);
1111
+ if (isOldBunVersion()) {
1112
+ c.req.raw.signal.addEventListener("abort", () => {
1113
+ if (!stream2.closed) {
1114
+ stream2.abort();
1115
+ }
1116
+ });
1117
+ }
1118
+ contextStash.set(stream2.responseReadable, c);
1119
+ c.header("Transfer-Encoding", "chunked");
1120
+ c.header("Content-Type", "text/event-stream");
1121
+ c.header("Cache-Control", "no-cache");
1122
+ c.header("Connection", "keep-alive");
1123
+ run(stream2, cb);
1124
+ return c.newResponse(stream2.responseReadable);
1125
+ };
932
1126
  var MAXIMUM_MESSAGE_SIZE = 4 * 1024 * 1024;
933
1127
  var SSETransport = class {
934
1128
  messageUrl;
@@ -940,9 +1134,9 @@ var SSETransport = class {
940
1134
  /**
941
1135
  * Creates a new SSETransport, which will direct the MPC client to POST messages to messageUrl
942
1136
  */
943
- constructor(messageUrl, stream) {
1137
+ constructor(messageUrl, stream2) {
944
1138
  this.messageUrl = messageUrl;
945
- this.stream = stream;
1139
+ this.stream = stream2;
946
1140
  this._sessionId = crypto.randomUUID();
947
1141
  this.stream.onAbort(() => {
948
1142
  void this.close();
@@ -959,6 +1153,10 @@ var SSETransport = class {
959
1153
  if (this.stream.closed) {
960
1154
  throw new Error("SSE transport already closed!");
961
1155
  }
1156
+ await this.stream.writeSSE({
1157
+ event: "ping",
1158
+ data: ""
1159
+ });
962
1160
  await this.stream.writeSSE({
963
1161
  event: "endpoint",
964
1162
  data: `${this.messageUrl}?sessionId=${this.sessionId}`
@@ -1330,8 +1528,8 @@ var MCPServer = class extends MCPServerBase {
1330
1528
  context
1331
1529
  );
1332
1530
  try {
1333
- const run = workflow.createRun({ runId: runtimeContext?.get("runId") });
1334
- const response = await run.start({ inputData: context, runtimeContext });
1531
+ const run2 = workflow.createRun({ runId: runtimeContext?.get("runId") });
1532
+ const response = await run2.start({ inputData: context, runtimeContext });
1335
1533
  return response;
1336
1534
  } catch (error) {
1337
1535
  this.logger.error(
@@ -1354,6 +1552,7 @@ var MCPServer = class extends MCPServerBase {
1354
1552
  name: workflowToolName,
1355
1553
  description: coreTool.description,
1356
1554
  parameters: coreTool.parameters,
1555
+ outputSchema: coreTool.outputSchema,
1357
1556
  execute: coreTool.execute,
1358
1557
  toolType: "workflow"
1359
1558
  };
@@ -1393,6 +1592,7 @@ var MCPServer = class extends MCPServerBase {
1393
1592
  name: toolName,
1394
1593
  description: coreTool.description,
1395
1594
  parameters: coreTool.parameters,
1595
+ outputSchema: coreTool.outputSchema,
1396
1596
  execute: coreTool.execute
1397
1597
  };
1398
1598
  this.logger.info(`Registered explicit tool: '${toolName}'`);
@@ -1439,11 +1639,17 @@ var MCPServer = class extends MCPServerBase {
1439
1639
  this.server.setRequestHandler(ListToolsRequestSchema, async () => {
1440
1640
  this.logger.debug("Handling ListTools request");
1441
1641
  return {
1442
- tools: Object.values(this.convertedTools).map((tool) => ({
1443
- name: tool.name,
1444
- description: tool.description,
1445
- inputSchema: tool.parameters.jsonSchema
1446
- }))
1642
+ tools: Object.values(this.convertedTools).map((tool) => {
1643
+ const toolSpec = {
1644
+ name: tool.name,
1645
+ description: tool.description,
1646
+ inputSchema: tool.parameters.jsonSchema
1647
+ };
1648
+ if (tool.outputSchema) {
1649
+ toolSpec.outputSchema = tool.outputSchema.jsonSchema;
1650
+ }
1651
+ return toolSpec;
1652
+ })
1447
1653
  };
1448
1654
  });
1449
1655
  }
@@ -1466,7 +1672,6 @@ var MCPServer = class extends MCPServerBase {
1466
1672
  isError: true
1467
1673
  };
1468
1674
  }
1469
- this.logger.debug(`CallTool: Invoking '${request.params.name}' with arguments:`, request.params.arguments);
1470
1675
  const validation = tool.parameters.validate?.(request.params.arguments ?? {});
1471
1676
  if (validation && !validation.success) {
1472
1677
  this.logger.warn(`CallTool: Invalid tool arguments for '${request.params.name}'`, {
@@ -1485,17 +1690,38 @@ var MCPServer = class extends MCPServerBase {
1485
1690
  };
1486
1691
  }
1487
1692
  const result = await tool.execute(validation?.value, { messages: [], toolCallId: "" });
1693
+ this.logger.debug(`CallTool: Tool '${request.params.name}' executed successfully with result:`, result);
1488
1694
  const duration = Date.now() - startTime;
1489
1695
  this.logger.info(`Tool '${request.params.name}' executed successfully in ${duration}ms.`);
1490
- return {
1491
- content: [
1696
+ const response = { isError: false, content: [] };
1697
+ if (tool.outputSchema) {
1698
+ if (!result.structuredContent) {
1699
+ throw new Error(`Tool ${request.params.name} has an output schema but no structured content was provided.`);
1700
+ }
1701
+ const outputValidation = tool.outputSchema.validate?.(result.structuredContent ?? {});
1702
+ if (outputValidation && !outputValidation.success) {
1703
+ this.logger.warn(`CallTool: Invalid structured content for '${request.params.name}'`, {
1704
+ errors: outputValidation.error
1705
+ });
1706
+ throw new Error(
1707
+ `Invalid structured content for tool ${request.params.name}: ${JSON.stringify(outputValidation.error)}`
1708
+ );
1709
+ }
1710
+ response.structuredContent = result.structuredContent;
1711
+ }
1712
+ if (result.content) {
1713
+ response.content = result.content;
1714
+ } else if (response.structuredContent) {
1715
+ response.content = [{ type: "text", text: JSON.stringify(response.structuredContent) }];
1716
+ } else {
1717
+ response.content = [
1492
1718
  {
1493
1719
  type: "text",
1494
1720
  text: typeof result === "string" ? result : JSON.stringify(result)
1495
1721
  }
1496
- ],
1497
- isError: false
1498
- };
1722
+ ];
1723
+ }
1724
+ return response;
1499
1725
  } catch (error) {
1500
1726
  const duration = Date.now() - startTime;
1501
1727
  if (error instanceof z.ZodError) {
@@ -1849,10 +2075,10 @@ var MCPServer = class extends MCPServerBase {
1849
2075
  async startHonoSSE({ url, ssePath, messagePath, context }) {
1850
2076
  try {
1851
2077
  if (url.pathname === ssePath) {
1852
- return streamSSE(context, async (stream) => {
2078
+ return streamSSE(context, async (stream2) => {
1853
2079
  await this.connectHonoSSE({
1854
2080
  messagePath,
1855
- stream
2081
+ stream: stream2
1856
2082
  });
1857
2083
  });
1858
2084
  } else if (url.pathname === messagePath) {
@@ -2078,13 +2304,13 @@ var MCPServer = class extends MCPServerBase {
2078
2304
  throw mastraError;
2079
2305
  }
2080
2306
  }
2081
- async connectHonoSSE({ messagePath, stream }) {
2307
+ async connectHonoSSE({ messagePath, stream: stream2 }) {
2082
2308
  this.logger.debug("Received SSE connection");
2083
- const sseTransport = new SSETransport(messagePath, stream);
2309
+ const sseTransport = new SSETransport(messagePath, stream2);
2084
2310
  const sessionId = sseTransport.sessionId;
2085
2311
  this.logger.debug("SSE Transport created with sessionId:", { sessionId });
2086
2312
  this.sseHonoTransports.set(sessionId, sseTransport);
2087
- stream.onAbort(() => {
2313
+ stream2.onAbort(() => {
2088
2314
  this.logger.debug("SSE Transport aborted with sessionId:", { sessionId });
2089
2315
  this.sseHonoTransports.delete(sessionId);
2090
2316
  });
@@ -2098,8 +2324,8 @@ var MCPServer = class extends MCPServerBase {
2098
2324
  while (true) {
2099
2325
  const sessionIds = Array.from(this.sseHonoTransports.keys() || []);
2100
2326
  this.logger.debug("Active Hono SSE sessions:", { sessionIds });
2101
- await stream.write(":keep-alive\n\n");
2102
- await stream.sleep(6e4);
2327
+ await stream2.write(":keep-alive\n\n");
2328
+ await stream2.sleep(6e4);
2103
2329
  }
2104
2330
  } catch (e) {
2105
2331
  const mastraError = new MastraError(
@@ -2208,6 +2434,7 @@ var MCPServer = class extends MCPServerBase {
2208
2434
  name: tool.name,
2209
2435
  description: tool.description,
2210
2436
  inputSchema: tool.parameters?.jsonSchema || tool.parameters,
2437
+ outputSchema: tool.outputSchema?.jsonSchema || tool.outputSchema,
2211
2438
  toolType: tool.toolType
2212
2439
  }))
2213
2440
  };
@@ -2228,6 +2455,7 @@ var MCPServer = class extends MCPServerBase {
2228
2455
  name: tool.name,
2229
2456
  description: tool.description,
2230
2457
  inputSchema: tool.parameters?.jsonSchema || tool.parameters,
2458
+ outputSchema: tool.outputSchema?.jsonSchema || tool.outputSchema,
2231
2459
  toolType: tool.toolType
2232
2460
  };
2233
2461
  }
@@ -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.5-alpha.0",
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.7-alpha.1"
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) {
@@ -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
+ });