@mastra/mcp 2.8.0 → 2.9.0-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
@@ -2,7 +2,7 @@ import { createRequire } from "module";
2
2
  import { AsyncLocalStorage } from "async_hooks";
3
3
  import { MastraBase } from "@mastra/core/base";
4
4
  import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
5
- import { createTool, isValidationError, noopObserve, validateToolInput, validateToolOutput } from "@mastra/core/tools";
5
+ import { createTool, getValidationIssuePaths, isValidationError, noopObserve, validateToolInput, validateToolOutput } from "@mastra/core/tools";
6
6
  import z3, { ZodFirstPartyTypeKind } from "zod/v3";
7
7
  import { toJSONSchema, z } from "zod/v4";
8
8
  import { z as z$1 } from "zod";
@@ -30,101 +30,6 @@ import { SignalProvider } from "@mastra/core/signals";
30
30
  // src/client/types.ts
31
31
  /** The current protocol revision; the one `/mcp` servers speak. */
32
32
  const MCP_CLIENT_PROTOCOL_VERSION = "2026-07-28";
33
- // ../schema-compat/dist/json-schema-traverse-D0CId4oM.js
34
- var __create$1 = Object.create;
35
- var __defProp$2 = Object.defineProperty;
36
- var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
37
- var __getOwnPropNames$1 = Object.getOwnPropertyNames;
38
- var __getProtoOf$1 = Object.getPrototypeOf;
39
- var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
40
- var __commonJSMin$1 = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod, mod.exports), cb = null), mod.exports);
41
- var __copyProps$1 = (to, from, except, desc) => {
42
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames$1(from), i = 0, n = keys.length, key; i < n; i++) {
43
- key = keys[i];
44
- if (!__hasOwnProp$1.call(to, key) && key !== except) __defProp$2(to, key, {
45
- get: ((k) => from[k]).bind(null, key),
46
- enumerable: !(desc = __getOwnPropDesc$1(from, key)) || desc.enumerable
47
- });
48
- }
49
- return to;
50
- };
51
- var __toESM$1 = (mod, isNodeMode, target) => (target = mod != null ? __create$1(__getProtoOf$1(mod)) : {}, __copyProps$1(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp$1.call(mod, "default") ? __defProp$2(target, "default", {
52
- value: mod,
53
- enumerable: true
54
- }) : target, mod));
55
- var require_json_schema_traverse = /* @__PURE__ */ __commonJSMin$1(((exports, module) => {
56
- var traverse = module.exports = function(schema, opts, cb) {
57
- if (typeof opts == "function") {
58
- cb = opts;
59
- opts = {};
60
- }
61
- cb = opts.cb || cb;
62
- var pre = typeof cb == "function" ? cb : cb.pre || function() {};
63
- var post = cb.post || function() {};
64
- _traverse(opts, pre, post, schema, "", schema);
65
- };
66
- traverse.keywords = {
67
- additionalItems: true,
68
- items: true,
69
- contains: true,
70
- additionalProperties: true,
71
- propertyNames: true,
72
- not: true,
73
- if: true,
74
- then: true,
75
- else: true
76
- };
77
- traverse.arrayKeywords = {
78
- items: true,
79
- allOf: true,
80
- anyOf: true,
81
- oneOf: true
82
- };
83
- traverse.propsKeywords = {
84
- $defs: true,
85
- definitions: true,
86
- properties: true,
87
- patternProperties: true,
88
- dependencies: true
89
- };
90
- traverse.skipKeywords = {
91
- default: true,
92
- enum: true,
93
- const: true,
94
- required: true,
95
- maximum: true,
96
- minimum: true,
97
- exclusiveMaximum: true,
98
- exclusiveMinimum: true,
99
- multipleOf: true,
100
- maxLength: true,
101
- minLength: true,
102
- pattern: true,
103
- format: true,
104
- maxItems: true,
105
- minItems: true,
106
- uniqueItems: true,
107
- maxProperties: true,
108
- minProperties: true
109
- };
110
- function _traverse(opts, pre, post, schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex) {
111
- if (schema && typeof schema == "object" && !Array.isArray(schema)) {
112
- pre(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
113
- for (var key in schema) {
114
- var sch = schema[key];
115
- if (Array.isArray(sch)) {
116
- if (key in traverse.arrayKeywords) for (var i = 0; i < sch.length; i++) _traverse(opts, pre, post, sch[i], jsonPtr + "/" + key + "/" + i, rootSchema, jsonPtr, key, schema, i);
117
- } else if (key in traverse.propsKeywords) {
118
- if (sch && typeof sch == "object") for (var prop in sch) _traverse(opts, pre, post, sch[prop], jsonPtr + "/" + key + "/" + escapeJsonPtr(prop), rootSchema, jsonPtr, key, schema, prop);
119
- } else if (key in traverse.keywords || opts.allKeys && !(key in traverse.skipKeywords)) _traverse(opts, pre, post, sch, jsonPtr + "/" + key, rootSchema, jsonPtr, key, schema);
120
- }
121
- post(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
122
- }
123
- }
124
- function escapeJsonPtr(str) {
125
- return str.replace(/~/g, "~0").replace(/\//g, "~1");
126
- }
127
- }));
128
33
  // ../schema-compat/dist/esm-BG70JlG8.js
129
34
  const ignoreOverride$1 = Symbol("Let zodToJsonSchema decide on which parser to use");
130
35
  const defaultOptions$1 = {
@@ -1330,6 +1235,101 @@ var AiSdkSchemaWrapper = class {
1330
1235
  function toStandardSchema$4(schema) {
1331
1236
  return new AiSdkSchemaWrapper(schema);
1332
1237
  }
1238
+ // ../schema-compat/dist/json-schema-traverse-D0CId4oM.js
1239
+ var __create$1 = Object.create;
1240
+ var __defProp$2 = Object.defineProperty;
1241
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
1242
+ var __getOwnPropNames$1 = Object.getOwnPropertyNames;
1243
+ var __getProtoOf$1 = Object.getPrototypeOf;
1244
+ var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
1245
+ var __commonJSMin$1 = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod, mod.exports), cb = null), mod.exports);
1246
+ var __copyProps$1 = (to, from, except, desc) => {
1247
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames$1(from), i = 0, n = keys.length, key; i < n; i++) {
1248
+ key = keys[i];
1249
+ if (!__hasOwnProp$1.call(to, key) && key !== except) __defProp$2(to, key, {
1250
+ get: ((k) => from[k]).bind(null, key),
1251
+ enumerable: !(desc = __getOwnPropDesc$1(from, key)) || desc.enumerable
1252
+ });
1253
+ }
1254
+ return to;
1255
+ };
1256
+ var __toESM$1 = (mod, isNodeMode, target) => (target = mod != null ? __create$1(__getProtoOf$1(mod)) : {}, __copyProps$1(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp$1.call(mod, "default") ? __defProp$2(target, "default", {
1257
+ value: mod,
1258
+ enumerable: true
1259
+ }) : target, mod));
1260
+ var require_json_schema_traverse = /* @__PURE__ */ __commonJSMin$1(((exports, module) => {
1261
+ var traverse = module.exports = function(schema, opts, cb) {
1262
+ if (typeof opts == "function") {
1263
+ cb = opts;
1264
+ opts = {};
1265
+ }
1266
+ cb = opts.cb || cb;
1267
+ var pre = typeof cb == "function" ? cb : cb.pre || function() {};
1268
+ var post = cb.post || function() {};
1269
+ _traverse(opts, pre, post, schema, "", schema);
1270
+ };
1271
+ traverse.keywords = {
1272
+ additionalItems: true,
1273
+ items: true,
1274
+ contains: true,
1275
+ additionalProperties: true,
1276
+ propertyNames: true,
1277
+ not: true,
1278
+ if: true,
1279
+ then: true,
1280
+ else: true
1281
+ };
1282
+ traverse.arrayKeywords = {
1283
+ items: true,
1284
+ allOf: true,
1285
+ anyOf: true,
1286
+ oneOf: true
1287
+ };
1288
+ traverse.propsKeywords = {
1289
+ $defs: true,
1290
+ definitions: true,
1291
+ properties: true,
1292
+ patternProperties: true,
1293
+ dependencies: true
1294
+ };
1295
+ traverse.skipKeywords = {
1296
+ default: true,
1297
+ enum: true,
1298
+ const: true,
1299
+ required: true,
1300
+ maximum: true,
1301
+ minimum: true,
1302
+ exclusiveMaximum: true,
1303
+ exclusiveMinimum: true,
1304
+ multipleOf: true,
1305
+ maxLength: true,
1306
+ minLength: true,
1307
+ pattern: true,
1308
+ format: true,
1309
+ maxItems: true,
1310
+ minItems: true,
1311
+ uniqueItems: true,
1312
+ maxProperties: true,
1313
+ minProperties: true
1314
+ };
1315
+ function _traverse(opts, pre, post, schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex) {
1316
+ if (schema && typeof schema == "object" && !Array.isArray(schema)) {
1317
+ pre(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
1318
+ for (var key in schema) {
1319
+ var sch = schema[key];
1320
+ if (Array.isArray(sch)) {
1321
+ if (key in traverse.arrayKeywords) for (var i = 0; i < sch.length; i++) _traverse(opts, pre, post, sch[i], jsonPtr + "/" + key + "/" + i, rootSchema, jsonPtr, key, schema, i);
1322
+ } else if (key in traverse.propsKeywords) {
1323
+ if (sch && typeof sch == "object") for (var prop in sch) _traverse(opts, pre, post, sch[prop], jsonPtr + "/" + key + "/" + escapeJsonPtr(prop), rootSchema, jsonPtr, key, schema, prop);
1324
+ } else if (key in traverse.keywords || opts.allKeys && !(key in traverse.skipKeywords)) _traverse(opts, pre, post, sch, jsonPtr + "/" + key, rootSchema, jsonPtr, key, schema);
1325
+ }
1326
+ post(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
1327
+ }
1328
+ }
1329
+ function escapeJsonPtr(str) {
1330
+ return str.replace(/~/g, "~0").replace(/\//g, "~1");
1331
+ }
1332
+ }));
1333
1333
  // ../schema-compat/dist/json-schema-BdCwqjfN.js
1334
1334
  var require_code$1 = /* @__PURE__ */ __commonJSMin$1(((exports) => {
1335
1335
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -14385,6 +14385,35 @@ function isReconnectableMCPError(error) {
14385
14385
  const errorMessage = error.message.toLowerCase();
14386
14386
  return errorMessage.includes("not connected") || errorMessage.includes("http 400") || errorMessage.includes("http 401") || errorMessage.includes("http 403") || errorMessage.includes("http 404") || errorMessage.includes("econnrefused") || errorMessage.includes("fetch failed") || errorMessage.includes("connection refused") || errorMessage.includes("connection closed") || errorMessage.includes("typeerror: terminated");
14387
14387
  }
14388
+ const CONNECTION_NOT_ESTABLISHED_CODES = /* @__PURE__ */ new Set([
14389
+ "ECONNREFUSED",
14390
+ "ENOTFOUND",
14391
+ "EAI_AGAIN",
14392
+ "UND_ERR_CONNECT_TIMEOUT"
14393
+ ]);
14394
+ const REJECTED_BEFORE_DISPATCH_STATUSES = /* @__PURE__ */ new Set([
14395
+ 400,
14396
+ 401,
14397
+ 403,
14398
+ 404
14399
+ ]);
14400
+ /**
14401
+ * Whether a reconnectable failure proves the request never reached the server's
14402
+ * handler, so sending it again cannot repeat its effects.
14403
+ *
14404
+ * A reset socket (`fetch failed` with `UND_ERR_SOCKET`), a cut-off response
14405
+ * (`terminated`) or a closed transport (`Connection closed`) can all follow a
14406
+ * request the server already received and ran, so they return false.
14407
+ */
14408
+ function isUnsentRequestError(error) {
14409
+ if (!isReconnectableMCPError(error)) return false;
14410
+ const { httpStatus, code } = getMCPDiscoveryErrorDetails(error);
14411
+ if (typeof code === "number") return false;
14412
+ if (httpStatus !== void 0) return REJECTED_BEFORE_DISPATCH_STATUSES.has(httpStatus);
14413
+ const message = error.message.toLowerCase();
14414
+ if (message.includes("not connected") || message.includes("econnrefused") || message.includes("connection refused") || /\bhttp 40[0134]\b/.test(message)) return true;
14415
+ return typeof code === "string" && CONNECTION_NOT_ESTABLISHED_CODES.has(code);
14416
+ }
14388
14417
  const GATEWAY_UNAVAILABLE_STATUSES = /* @__PURE__ */ new Set([
14389
14418
  502,
14390
14419
  503,
@@ -15951,6 +15980,50 @@ function runOutsideDatadogTraceScope(callback) {
15951
15980
  if (!scope) return callback();
15952
15981
  return scope.activate(null, callback);
15953
15982
  }
15983
+ const LOGGABLE_ERROR_NAMES = /* @__PURE__ */ new Set([
15984
+ "Error",
15985
+ "TypeError",
15986
+ "RangeError",
15987
+ "SyntaxError",
15988
+ "AbortError",
15989
+ "TimeoutError",
15990
+ "McpError",
15991
+ "ProtocolError",
15992
+ "SdkError",
15993
+ "SdkHttpError",
15994
+ "SseError",
15995
+ "StreamableHTTPError",
15996
+ "UnauthorizedError",
15997
+ "OAuthError",
15998
+ "UnsupportedProtocolVersionError"
15999
+ ]);
16000
+ const LOGGABLE_ERROR_CODES = /* @__PURE__ */ new Set([
16001
+ "ECONNRESET",
16002
+ "ECONNREFUSED",
16003
+ "ECONNABORTED",
16004
+ "ETIMEDOUT",
16005
+ "ENOTFOUND",
16006
+ "EPIPE",
16007
+ "EAI_AGAIN",
16008
+ "ABORT_ERR",
16009
+ "UND_ERR_SOCKET",
16010
+ "UND_ERR_CONNECT_TIMEOUT",
16011
+ "UND_ERR_HEADERS_TIMEOUT",
16012
+ "UND_ERR_BODY_TIMEOUT"
16013
+ ]);
16014
+ /**
16015
+ * Names a tool-call failure for logs without its message: server error text can echo the submitted
16016
+ * arguments back (Issue #29318). The full error still reaches the caller.
16017
+ */
16018
+ function errorKind(error) {
16019
+ if (error instanceof MastraError) return error.id;
16020
+ if (error instanceof Error) {
16021
+ const name = LOGGABLE_ERROR_NAMES.has(error.name) ? error.name : "Error";
16022
+ const code = error.code;
16023
+ return typeof code === "number" || typeof code === "string" && LOGGABLE_ERROR_CODES.has(code) ? `${name} (${code})` : name;
16024
+ }
16025
+ return typeof error;
16026
+ }
15954
16027
  /**
15955
16028
  * Convert an MCP LoggingLevel to a logger method name that exists in our logger
15956
16029
  */
@@ -17097,10 +17170,7 @@ var InternalMastraMCPClient = class extends MastraBase {
17097
17170
  });
17098
17171
  return res && finishToolCall(res);
17099
17172
  }
17100
- this.log("debug", `Executing tool: ${tool.name}`, {
17101
- toolArgs: input,
17102
- runId: context?.runId
17103
- });
17173
+ this.log("debug", `Executing tool: ${tool.name}`, { runId: context?.runId });
17104
17174
  const progressMeta = this.enableProgressTracking ? { progressToken: context?.runId || crypto.randomUUID() } : void 0;
17105
17175
  const _meta = this.requestMeta({
17106
17176
  ...context?._meta,
@@ -17129,7 +17199,7 @@ var InternalMastraMCPClient = class extends MastraBase {
17129
17199
  const finishToolCall = async (res) => {
17130
17200
  if (res.isError && this.onToolError === "throw") {
17131
17201
  const errorText = extractToolErrorText(res.content);
17132
- this.log("debug", `Tool reported an error: ${tool.name}`, { error: errorText });
17202
+ this.log("debug", `Tool reported an error: ${tool.name}`);
17133
17203
  throw new MastraError({
17134
17204
  id: "MCP_CLIENT_TOOL_EXECUTION_FAILED",
17135
17205
  domain: ErrorDomain.MCP,
@@ -17148,7 +17218,7 @@ var InternalMastraMCPClient = class extends MastraBase {
17148
17218
  const validationSchema = await getOutputValidationSchema();
17149
17219
  const validation = validateToolOutput(validationSchema, res.structuredContent, tool.name);
17150
17220
  if (validation.error) {
17151
- this.log("debug", `Tool output failed schema validation: ${tool.name}`, { message: validation.error.message });
17221
+ this.log("debug", `Tool output failed schema validation: ${tool.name}`, { issuePaths: getValidationIssuePaths(validation.error, validationSchema, "output") });
17152
17222
  return validation.error;
17153
17223
  }
17154
17224
  }
@@ -17161,25 +17231,40 @@ var InternalMastraMCPClient = class extends MastraBase {
17161
17231
  try {
17162
17232
  return await executeToolCall();
17163
17233
  } catch (e) {
17164
- if (!(e instanceof MastraError && (e.id === "MCP_CLIENT_TOOL_EXECUTION_FAILED" || e.id.startsWith("MCP_CLIENT_TOOL_TASK_"))) && isReconnectableMCPError(e)) {
17165
- this.log("debug", `Transport error detected for tool ${tool.name}, attempting reconnection...`, { error: e instanceof Error ? e.message : String(e) });
17234
+ const isToolExecutionError = e instanceof MastraError && (e.id === "MCP_CLIENT_TOOL_EXECUTION_FAILED" || e.id.startsWith("MCP_CLIENT_TOOL_TASK_"));
17235
+ if (!isToolExecutionError && isReconnectableMCPError(e) && !isUnsentRequestError(e)) {
17236
+ if (annotations?.readOnlyHint === true || annotations?.idempotentHint === true) {
17237
+ this.log("debug", `Retrying replay-safe tool ${tool.name} after a transport error`, { error: errorKind(e) });
17238
+ if (this.transport !== failedTransport || this.reconnectPromise) await this.reconnectAfterTransportFailure(failedTransport, lifecycleGeneration);
17239
+ return await executeToolCall();
17240
+ }
17241
+ this.log("error", `Lost the connection during tool ${tool.name}; it may have run`, { error: errorKind(e) });
17242
+ throw new MastraError({
17243
+ id: "MCP_CLIENT_TOOL_CALL_OUTCOME_UNKNOWN",
17244
+ domain: ErrorDomain.MCP,
17245
+ category: ErrorCategory.THIRD_PARTY,
17246
+ text: `The connection to MCP server "${this.name}" failed before tool "${tool.name}" returned a result. The tool may have run on the server, so it was not called again.`,
17247
+ details: {
17248
+ toolName: tool.name,
17249
+ serverName: this.name
17250
+ }
17251
+ }, e);
17252
+ }
17253
+ if (!isToolExecutionError && isReconnectableMCPError(e)) {
17254
+ this.log("debug", `Transport error detected for tool ${tool.name}, attempting reconnection...`, { error: errorKind(e) });
17166
17255
  try {
17167
17256
  await this.reconnectAfterTransportFailure(failedTransport, lifecycleGeneration);
17168
17257
  this.log("debug", `Retrying tool ${tool.name} after reconnection...`);
17169
17258
  return await executeToolCall();
17170
17259
  } catch (reconnectError) {
17171
17260
  this.log("error", `Reconnection or retry failed for tool ${tool.name}`, {
17172
- originalError: e instanceof Error ? e.message : String(e),
17173
- reconnectError: reconnectError instanceof Error ? reconnectError.stack : String(reconnectError),
17174
- toolArgs: input
17261
+ originalError: errorKind(e),
17262
+ reconnectError: errorKind(reconnectError)
17175
17263
  });
17176
17264
  throw reconnectError;
17177
17265
  }
17178
17266
  }
17179
- this.log("error", `Error calling tool: ${tool.name}`, {
17180
- error: e instanceof Error ? e.stack : JSON.stringify(e, null, 2),
17181
- toolArgs: input
17182
- });
17267
+ this.log("error", `Error calling tool: ${tool.name}`, { error: errorKind(e) });
17183
17268
  throw e;
17184
17269
  }
17185
17270
  });
@@ -17607,12 +17692,12 @@ async function createOAuthCallbackServer(options) {
17607
17692
  },
17608
17693
  close() {
17609
17694
  settle({ error: /* @__PURE__ */ new Error("OAuth callback server closed before receiving an authorization code") });
17610
- server.closeIdleConnections();
17611
17695
  return new Promise((resolve, reject) => {
17612
17696
  server.close((error) => {
17613
17697
  if (error && error.code !== "ERR_SERVER_NOT_RUNNING") reject(error);
17614
17698
  else resolve();
17615
17699
  });
17700
+ server.closeAllConnections();
17616
17701
  });
17617
17702
  }
17618
17703
  };
@@ -18981,7 +19066,7 @@ onListChanged: async (serverName, handler) => {
18981
19066
  const errors = {};
18982
19067
  const errorDetails = {};
18983
19068
  const durations = {};
18984
- const settled = await this.discoverAcrossServers((serverName) => this.getToolsForServer(serverName), {
19069
+ const settled = await this.discoverAcrossServers(async (serverName) => modelVisibleTools(await this.getToolsForServer(serverName)), {
18985
19070
  errorId: "MCP_CLIENT_GET_TOOLS_FAILED",
18986
19071
  logMessage: "Failed to list tools from server:"
18987
19072
  }, options);
@@ -19036,7 +19121,7 @@ onListChanged: async (serverName, handler) => {
19036
19121
  const errors = {};
19037
19122
  const errorDetails = {};
19038
19123
  const durations = {};
19039
- const settled = await this.discoverAcrossServers((serverName) => this.getToolsForServer(serverName), {
19124
+ const settled = await this.discoverAcrossServers(async (serverName) => modelVisibleTools(await this.getToolsForServer(serverName)), {
19040
19125
  errorId: "MCP_CLIENT_GET_TOOLSETS_FAILED",
19041
19126
  logMessage: "Failed to list toolsets from server:"
19042
19127
  }, options);
@@ -19100,7 +19185,7 @@ onListChanged: async (serverName, handler) => {
19100
19185
  const errorDetails = {};
19101
19186
  const durations = {};
19102
19187
  const settled = await this.discoverAcrossServers(async (serverName) => {
19103
- return (await this.getConnectedClientForServer(serverName)).toolDefinitions();
19188
+ return modelVisibleDefinitions(await (await this.getConnectedClientForServer(serverName)).toolDefinitions());
19104
19189
  }, {
19105
19190
  errorId: "MCP_CLIENT_GET_TOOL_DEFINITIONS_FAILED",
19106
19191
  logMessage: "Failed to list tool definitions from server:"
@@ -19169,7 +19254,7 @@ onListChanged: async (serverName, handler) => {
19169
19254
  this.logger.warn("Skipping cached MCP tool definitions for a server that is no longer configured", { serverName });
19170
19255
  continue;
19171
19256
  }
19172
- for (const [toolName, definition] of Object.entries(definitions)) try {
19257
+ for (const [toolName, definition] of Object.entries(modelVisibleDefinitions(definitions))) try {
19173
19258
  tools[`${serverName}_${toolName}`] = await this.toolFromDefinition({
19174
19259
  serverName,
19175
19260
  definition
@@ -19437,6 +19522,22 @@ onListChanged: async (serverName, handler) => {
19437
19522
  return {};
19438
19523
  }
19439
19524
  };
19525
+ /**
19526
+ * Whether a tool may be handed to the model. MCP App servers mark helpers their app calls itself
19527
+ * with `_meta.ui.visibility` lacking `'model'`; per the MCP Apps spec, hosts must not give those
19528
+ * to the model. They stay callable by the app through the server.
19529
+ */
19530
+ function isModelVisible(meta) {
19531
+ const ui = meta?.ui;
19532
+ const visibility = ui && typeof ui === "object" && "visibility" in ui ? ui.visibility : void 0;
19533
+ return !Array.isArray(visibility) || visibility.includes("model");
19534
+ }
19535
+ function modelVisibleTools(tools) {
19536
+ return Object.fromEntries(Object.entries(tools).filter(([, tool]) => isModelVisible(tool.mcp?._meta)));
19537
+ }
19538
+ function modelVisibleDefinitions(definitions) {
19539
+ return Object.fromEntries(Object.entries(definitions).filter(([, definition]) => isModelVisible(definition._meta)));
19540
+ }
19440
19541
  // src/server/actions.ts
19441
19542
  /** Runtime tool catalogue mutations. Every change is published to listening clients. */
19442
19543
  var ServerToolActions = class {
@@ -19568,8 +19669,17 @@ function callerTrace(ctx) {
19568
19669
  return callerTraceContext(meta?.traceparent, meta?.tracestate) ?? callerTraceContext(headers?.get("traceparent"), headers?.get("tracestate"));
19569
19670
  }
19570
19671
  /**
19672
+ * Where `@mastra/server` attaches, on `req.auth`, the request context its auth
19673
+ * resolved (user, permissions, roles, resource id, tenant, auth mode). A symbol
19674
+ * cannot come out of a token or JSON payload, so only server code can supply it.
19675
+ */
19676
+ const MCP_TRUSTED_REQUEST_CONTEXT = Symbol.for("mastra.mcp.trustedRequestContext");
19677
+ const MASTRA_USER_KEY = "mastra__user";
19678
+ /**
19571
19679
  * Builds the trusted application context for one request. Auth is re-derived from
19572
- * the transport every time; nothing is carried between continuation rounds. The W3C
19680
+ * the transport every time; nothing is carried between continuation rounds. When
19681
+ * the request came through a Mastra server route, the values its auth resolved are
19682
+ * copied in, so tools are authorized the same way as that server's routes. The W3C
19573
19683
  * trace fields sent by the client are exposed under `traceContext` as opaque
19574
19684
  * strings for observability only; they are never consulted for authorization.
19575
19685
  */
@@ -19579,6 +19689,12 @@ async function toRequestContext(ctx, mapAuthInfoToUser) {
19579
19689
  if (traceContext) requestContext.set("traceContext", traceContext);
19580
19690
  const authInfo = ctx.http?.authInfo;
19581
19691
  if (!authInfo) return requestContext;
19692
+ const trusted = authInfo[MCP_TRUSTED_REQUEST_CONTEXT];
19693
+ if (trusted && typeof trusted === "object") {
19694
+ for (const [key, value] of Object.entries(trusted)) requestContext.set(key, value);
19695
+ const trustedUser = trusted[MASTRA_USER_KEY];
19696
+ if (trustedUser) requestContext.set("user", trustedUser);
19697
+ }
19582
19698
  requestContext.set("authInfo", authInfo);
19583
19699
  const user = await mapAuthInfoToUser?.({
19584
19700
  authInfo,
@@ -22781,6 +22897,17 @@ function toSummary(listing) {
22781
22897
  function isWorkflowTaskContext(context) {
22782
22898
  return context?.kind === "workflow" && typeof context.runId === "string" && typeof context.workflowKey === "string";
22783
22899
  }
22900
+ /**
22901
+ * A run's result with its step results read: a storage v2 result's `steps` is a loader that reads
22902
+ * them from storage, so a view, and the text a tool answers with, gets them as for v1.
22903
+ */
22904
+ async function withLoadedSteps(result) {
22905
+ const steps = result.steps;
22906
+ return typeof steps === "function" ? {
22907
+ ...result,
22908
+ steps: await steps()
22909
+ } : result;
22910
+ }
22784
22911
  /** Statuses a run never leaves. */
22785
22912
  const FINAL_STATUSES = /* @__PURE__ */ new Set([
22786
22913
  "success",
@@ -22846,6 +22973,136 @@ function stepAt(workflow, path) {
22846
22973
  }
22847
22974
  return step;
22848
22975
  }
22976
+ // src/server/uri-template.ts
22977
+ /**
22978
+ * Whether `uri` is an RFC 6570 expansion of `template`. Decides membership only,
22979
+ * without extracting variables.
22980
+ *
22981
+ * The SDK's `UriTemplate.match` is a backtracking regex that takes minutes on a
22982
+ * few KB of client URI when variables are adjacent (`x://{a}-{b}-{c}-{d}`); this
22983
+ * walks the URI once per template segment instead.
22984
+ *
22985
+ * Plain `{x}` and `{+x}` must be non-empty, since an empty path segment never
22986
+ * names a resource. Outside `{+x}` and `{#x}`, values must be percent-encoded:
22987
+ * an ASCII reserved character other than `,` ends the value. Raw non-ASCII
22988
+ * characters are accepted because clients often send them unencoded.
22989
+ */
22990
+ function matchesUriTemplate(template, uri) {
22991
+ return advance(uri, /* @__PURE__ */ new Set([0]), parse(template)).has(uri.length);
22992
+ }
22993
+ const encoded = (char) => /[\w\-.~,]/.test(char) || char.charCodeAt(0) > 127;
22994
+ const anything = () => true;
22995
+ const literal = (text) => ({
22996
+ kind: "literal",
22997
+ text
22998
+ });
22999
+ const run = (allowed, min = 0) => ({
23000
+ kind: "run",
23001
+ allowed,
23002
+ min
23003
+ });
23004
+ const optional = (...segments) => ({
23005
+ kind: "optional",
23006
+ segments
23007
+ });
23008
+ const repeat = (...segments) => ({
23009
+ kind: "repeat",
23010
+ segments
23011
+ });
23012
+ const choice = (options) => ({
23013
+ kind: "choice",
23014
+ options
23015
+ });
23016
+ function parse(template) {
23017
+ const segments = [];
23018
+ let i = 0;
23019
+ while (i < template.length) {
23020
+ const open = template.indexOf("{", i);
23021
+ const close = open === -1 ? -1 : template.indexOf("}", open);
23022
+ if (close === -1) {
23023
+ segments.push(literal(template.slice(i)));
23024
+ break;
23025
+ }
23026
+ if (open > i) segments.push(literal(template.slice(i, open)));
23027
+ segments.push(...expression(template.slice(open + 1, close)));
23028
+ i = close + 1;
23029
+ }
23030
+ return segments;
23031
+ }
23032
+ function expression(body) {
23033
+ const operator = "+#./;?&".includes(body[0] ?? "") ? body[0] : "";
23034
+ const specs = body.slice(operator.length).split(",").map((spec) => ({
23035
+ name: spec.replace(/[*]|:\d*$/g, "").trim(),
23036
+ exploded: spec.includes("*")
23037
+ })).filter((spec) => spec.name);
23038
+ switch (operator) {
23039
+ case "": return [run(encoded, 1)];
23040
+ case "+": return [run(anything, 1)];
23041
+ case "#": return [optional(literal("#"), run(anything))];
23042
+ case "/": return specs.map(({ exploded }) => exploded ? repeat(literal("/"), run(encoded)) : optional(literal("/"), run(encoded)));
23043
+ case ".": return specs.map(() => optional(literal("."), run(encoded)));
23044
+ case ";": return specs.map(({ name, exploded }) => (exploded ? repeat : optional)(literal(`;${name}`), optional(literal("="), run(encoded))));
23045
+ default: {
23046
+ const pair = ({ name, exploded }, separator) => {
23047
+ const first = [literal(`${separator}${name}=`), run(encoded)];
23048
+ return exploded ? [...first, repeat(literal(`&${name}=`), run(encoded))] : first;
23049
+ };
23050
+ const firstPresent = specs.map((spec, i) => [...pair(spec, operator), ...specs.slice(i + 1).map((later) => optional(...pair(later, "&")))]);
23051
+ return [optional(choice(firstPresent))];
23052
+ }
23053
+ }
23054
+ }
23055
+ /** Every URI offset reachable after matching `segments` from any offset in `from`. */
23056
+ function advance(uri, from, segments) {
23057
+ let positions = from;
23058
+ for (const segment of segments) {
23059
+ if (positions.size === 0) break;
23060
+ positions = step(uri, positions, segment);
23061
+ }
23062
+ return positions;
23063
+ }
23064
+ function step(uri, positions, segment) {
23065
+ const next = /* @__PURE__ */ new Set();
23066
+ switch (segment.kind) {
23067
+ case "literal":
23068
+ for (const p of positions) if (uri.startsWith(segment.text, p)) next.add(p + segment.text.length);
23069
+ return next;
23070
+ case "choice":
23071
+ for (const option of segment.options) for (const p of advance(uri, positions, option)) next.add(p);
23072
+ return next;
23073
+ case "run": {
23074
+ let end = 0;
23075
+ for (const p of [...positions].sort((a, b) => a - b)) {
23076
+ const covered = p < end && next.has(p);
23077
+ if (segment.min === 0) next.add(p);
23078
+ if (covered) continue;
23079
+ end = p;
23080
+ for (let width = charWidth(uri, end, segment.allowed); width > 0; width = charWidth(uri, end, segment.allowed)) next.add(end += width);
23081
+ }
23082
+ return next;
23083
+ }
23084
+ case "optional":
23085
+ for (const p of positions) next.add(p);
23086
+ for (const p of advance(uri, positions, segment.segments)) next.add(p);
23087
+ return next;
23088
+ case "repeat": {
23089
+ for (const p of positions) next.add(p);
23090
+ let frontier = positions;
23091
+ while (frontier.size > 0) {
23092
+ const reached = [...advance(uri, frontier, segment.segments)].filter((p) => !next.has(p));
23093
+ for (const p of reached) next.add(p);
23094
+ frontier = new Set(reached);
23095
+ }
23096
+ return next;
23097
+ }
23098
+ }
23099
+ }
23100
+ /** Characters a run consumes at `i`: a whole `%HH` triplet, one allowed character, or none. */
23101
+ function charWidth(uri, i, allowed) {
23102
+ if (i >= uri.length) return 0;
23103
+ if (uri[i] === "%") return /^%[0-9A-Fa-f]{2}/.test(uri.slice(i, i + 3)) ? 3 : 0;
23104
+ return allowed(uri[i]) ? 1 : 0;
23105
+ }
22849
23106
  // src/server/server.ts
22850
23107
  const EMPTY_OBJECT_SCHEMA = {
22851
23108
  type: "object",
@@ -23050,11 +23307,11 @@ var MCPServer = class extends MCPServerBase {
23050
23307
  inputSchema: workflow.inputSchema,
23051
23308
  mcp: { toolType: "workflow" },
23052
23309
  execute: async (inputData, context) => {
23053
- return (await workflow.createRun({ runId: context?.requestContext?.get("runId") })).start({
23310
+ return withLoadedSteps(await (await workflow.createRun({ runId: context?.requestContext?.get("runId") })).start({
23054
23311
  inputData,
23055
23312
  requestContext: context?.requestContext,
23056
23313
  tracingContext: context?.tracingContext
23057
- });
23314
+ }));
23058
23315
  }
23059
23316
  }));
23060
23317
  }
@@ -23844,7 +24101,7 @@ var MCPServer = class extends MCPServerBase {
23844
24101
  if (!workflow) throw new Error(`Workflow '${workflowKey}' is not served`);
23845
24102
  const input = validateToolInput(workflow.inputSchema, args, name);
23846
24103
  if (input.error) {
23847
- this.logger.warn(`Tool '${name}' rejected its input.`, { error: input.error.message });
24104
+ this.logger.warn(`Tool '${name}' rejected its input.`, { issuePaths: getValidationIssuePaths(input.error, workflow.inputSchema) });
23848
24105
  return errorResult$1(input.error.message);
23849
24106
  }
23850
24107
  const context = {
@@ -23867,11 +24124,11 @@ var MCPServer = class extends MCPServerBase {
23867
24124
  };
23868
24125
  const start = (runTracing) => {
23869
24126
  const run = workflow.createRun({ runId: context.runId });
23870
- const result = run.then((started) => started.start({
24127
+ const result = run.then(async (started) => withLoadedSteps(await started.start({
23871
24128
  inputData: input.data,
23872
24129
  requestContext,
23873
24130
  tracingContext: runTracing
23874
- }));
24131
+ })));
23875
24132
  const cancel = () => void run.then((started) => started.cancel()).catch((error) => this.logger.warn("Could not cancel workflow run", {
23876
24133
  tool: name,
23877
24134
  error
@@ -24000,12 +24257,12 @@ var MCPServer = class extends MCPServerBase {
24000
24257
  error
24001
24258
  })));
24002
24259
  try {
24003
- view = await run.resume({
24260
+ view = await withLoadedSteps(await run.resume({
24004
24261
  step: answered.step,
24005
24262
  resumeData: answer.content ?? {},
24006
24263
  requestContext,
24007
24264
  ...span && { tracingContext: { currentSpan: span } }
24008
- });
24265
+ }));
24009
24266
  } catch (error) {
24010
24267
  if (isResumeClaimLost(error)) return this.watchWorkflowRun(workflow, context, attempt.signal);
24011
24268
  const now = await workflow.getWorkflowRunById(context.runId, { withNestedWorkflows: false });
@@ -24173,7 +24430,7 @@ var MCPServer = class extends MCPServerBase {
24173
24430
  }
24174
24431
  toCallToolResult(name, tool, value) {
24175
24432
  if (isValidationError(value)) {
24176
- this.logger.warn(`Tool '${name}' rejected its input.`, { error: value.message });
24433
+ this.logger.warn(`Tool '${name}' rejected its input.`, { issuePaths: getValidationIssuePaths(value, tool.parameters) });
24177
24434
  return errorResult$1(value.message);
24178
24435
  }
24179
24436
  const resourceUri = uiResourceUri(tool.mcp?._meta);
@@ -24218,10 +24475,24 @@ var MCPServer = class extends MCPServerBase {
24218
24475
  extra: toToolExecutionContext(ctx, this.name).extra,
24219
24476
  requestContext
24220
24477
  }) ?? []];
24478
+ const findResource = async (uri, ctx, requestContext) => {
24479
+ const listed = (await listResources(ctx, requestContext)).find((r) => r.uri === uri);
24480
+ if (listed || !options?.resourceTemplates) return listed;
24481
+ const template = (await options.resourceTemplates({
24482
+ extra: toToolExecutionContext(ctx, this.name).extra,
24483
+ requestContext
24484
+ })).find((t) => matchesUriTemplate(t.uriTemplate, uri));
24485
+ return template && {
24486
+ uri,
24487
+ name: template.name,
24488
+ mimeType: template.mimeType,
24489
+ _meta: template._meta
24490
+ };
24491
+ };
24221
24492
  this.setTracedHandler(server, "resources/list", async (_request, ctx, trace) => ({ resources: await listResources(ctx, trace.requestContext) }));
24222
24493
  this.setTracedHandler(server, "resources/read", async (request, ctx, trace) => {
24223
24494
  const uri = request.params.uri;
24224
- const resource = (await listResources(ctx, trace.requestContext)).find((r) => r.uri === uri);
24495
+ const resource = await findResource(uri, ctx, trace.requestContext);
24225
24496
  if (!resource) throw new ProtocolError$1(ProtocolErrorCode$1.InvalidParams, `Resource not found: ${uri}`);
24226
24497
  const html = this.appResourceHtml.get(uri);
24227
24498
  if (html !== void 0) return { contents: [this.resourceContents(resource, { text: html })] };
@@ -24618,10 +24889,7 @@ var MCPServer = class extends MCPServerBase {
24618
24889
  id: "MCP_SERVER_TOOL_EXECUTE_FAILED",
24619
24890
  domain: ErrorDomain.MCP,
24620
24891
  category: ErrorCategory.USER,
24621
- details: {
24622
- toolId,
24623
- args: JSON.stringify(args)
24624
- }
24892
+ details: { toolId }
24625
24893
  }, error);
24626
24894
  this.logger.trackException(mastraError);
24627
24895
  throw mastraError;