@mastra/mcp 2.8.0-alpha.1 → 2.8.0-alpha.2

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.cjs CHANGED
@@ -15423,6 +15423,57 @@ var TaskRequester = class {
15423
15423
  };
15424
15424
  }
15425
15425
  };
15426
+ // src/client/tasks/suspend.ts
15427
+ const MCP_INPUT_REQUEST_SUSPEND_TYPE = "mcp-input-request";
15428
+ const SuspendPayloadSchema = zod_v4.z.object({
15429
+ type: zod_v4.z.literal(MCP_INPUT_REQUEST_SUSPEND_TYPE),
15430
+ serverName: zod_v4.z.string(),
15431
+ toolName: zod_v4.z.string(),
15432
+ taskId: zod_v4.z.string().min(1),
15433
+ createdAt: zod_v4.z.string(),
15434
+ requestedAt: zod_v4.z.string().optional(),
15435
+ requests: TaskInputRequestsSchema
15436
+ });
15437
+ const ResumeDataSchema = zod_v4.z.object({ inputResponses: zod_v4.z.record(zod_v4.z.string(), zod_v4.z.object({
15438
+ action: zod_v4.z.enum([
15439
+ "accept",
15440
+ "decline",
15441
+ "cancel"
15442
+ ]),
15443
+ content: zod_v4.z.record(zod_v4.z.string(), zod_v4.z.unknown()).optional()
15444
+ })) });
15445
+ /**
15446
+ * Reads a resume of a tool call that suspended on MCP task input. Returns `undefined` when the call
15447
+ * didn't suspend that way, so other resumes (tool approval, for one) take the normal path.
15448
+ */
15449
+ function readInputRequestResume(host, expected) {
15450
+ const raw = host?.suspendPayload;
15451
+ if (!raw || typeof raw !== "object" || raw.type !== "mcp-input-request") return;
15452
+ const payload = SuspendPayloadSchema.safeParse(raw);
15453
+ if (!payload.success) return {
15454
+ ok: false,
15455
+ reason: `the suspended MCP input request is malformed`
15456
+ };
15457
+ if (payload.data.serverName !== expected.serverName || payload.data.toolName !== expected.toolName) return {
15458
+ ok: false,
15459
+ reason: `the suspended MCP input request belongs to another tool`
15460
+ };
15461
+ const resume = ResumeDataSchema.safeParse(host?.resumeData);
15462
+ if (!resume.success) return {
15463
+ ok: false,
15464
+ reason: `resume data must be { inputResponses: { [key]: { action: 'accept' | 'decline' | 'cancel', content? } } }`
15465
+ };
15466
+ const missing = Object.keys(payload.data.requests).filter((key) => !Object.hasOwn(resume.data.inputResponses, key));
15467
+ if (missing.length > 0) return {
15468
+ ok: false,
15469
+ reason: `resume data has no answer for ${missing.join(", ")}`
15470
+ };
15471
+ return {
15472
+ ok: true,
15473
+ payload: payload.data,
15474
+ inputResponses: resume.data.inputResponses
15475
+ };
15476
+ }
15426
15477
  // src/client/tasks/wait.ts
15427
15478
  /** Why a task wait stopped without a terminal task. */
15428
15479
  var TaskWaitError = class extends Error {
@@ -15451,12 +15502,6 @@ const defaultSleep = (ms, signal) => new Promise((resolve, reject) => {
15451
15502
  };
15452
15503
  signal?.addEventListener("abort", onAbort, { once: true });
15453
15504
  });
15454
- /**
15455
- * Polls a remote task until it reaches a terminal status, answering its input requests on the way.
15456
- *
15457
- * Aborting the signal or exceeding `maxWaitMs` sends `tasks/cancel` (fire-and-forget) and rejects.
15458
- * Each input request key is answered at most once, however many polls list it.
15459
- */
15460
15505
  async function waitForTask(port, created, options = {}) {
15461
15506
  const sleep = options.sleep ?? defaultSleep;
15462
15507
  const now = options.now ?? Date.now;
@@ -15507,7 +15552,21 @@ async function waitForTask(port, created, options = {}) {
15507
15552
  const { inputRequests } = task;
15508
15553
  const pending = Object.entries(inputRequests).filter(([key]) => !answered.has(key));
15509
15554
  if (pending.length > 0) {
15510
- const responses = await bounded(answerInputRequests(pending, task, options.inputRequests, stop.signal));
15555
+ const supplied = options.inputResponses ?? {};
15556
+ const unanswered = pending.filter(([key]) => !Object.hasOwn(supplied, key));
15557
+ const responses = {};
15558
+ for (const [key] of pending) if (Object.hasOwn(supplied, key)) responses[key] = supplied[key];
15559
+ if (unanswered.length > 0 && !options.inputRequests && options.stopAtInput) {
15560
+ if (Object.keys(responses).length > 0) {
15561
+ for (const key of Object.keys(responses)) answered.add(key);
15562
+ await bounded(port.update(task.taskId, responses, { signal: stop.signal }));
15563
+ }
15564
+ return {
15565
+ ...task,
15566
+ inputRequests: Object.fromEntries(unanswered)
15567
+ };
15568
+ }
15569
+ if (unanswered.length > 0) Object.assign(responses, await bounded(answerInputRequests(unanswered, task, options.inputRequests, stop.signal)));
15511
15570
  for (const key of Object.keys(responses)) answered.add(key);
15512
15571
  await bounded(port.update(task.taskId, responses, { signal: stop.signal }));
15513
15572
  task = await poll();
@@ -17027,7 +17086,43 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
17027
17086
  if (serverMeta.connectFirst) await this.connect();
17028
17087
  const operationContext = context?.requestContext ?? null;
17029
17088
  return this.operationContextStore.run(operationContext, async () => {
17089
+ const suspendHost = context?.agent ?? context?.workflow;
17090
+ const toolCallSpan = context?.tracingContext?.currentSpan;
17030
17091
  const executeToolCall = async () => {
17092
+ const resume = this.taskOptions ? readInputRequestResume(suspendHost, {
17093
+ serverName: this.name,
17094
+ toolName: tool.name
17095
+ }) : void 0;
17096
+ if (resume) {
17097
+ if (!resume.ok) throw new _mastra_core_error.MastraError({
17098
+ id: "MCP_CLIENT_TOOL_TASK_RESUME_INVALID",
17099
+ domain: _mastra_core_error.ErrorDomain.MCP,
17100
+ category: _mastra_core_error.ErrorCategory.USER,
17101
+ text: `Can't resume MCP tool ${tool.name}: ${resume.reason}`,
17102
+ details: {
17103
+ toolName: tool.name,
17104
+ serverName: this.name
17105
+ }
17106
+ });
17107
+ const { payload, inputResponses } = resume;
17108
+ this.log("debug", `Resuming tool ${tool.name} on MCP task ${payload.taskId}`);
17109
+ toolCallSpan?.update({ metadata: { mcpTaskKey: taskKeyOf(payload.taskId) } });
17110
+ const waiting = {
17111
+ taskId: payload.taskId,
17112
+ status: "input_required",
17113
+ createdAt: payload.createdAt,
17114
+ lastUpdatedAt: payload.createdAt,
17115
+ ttlMs: null,
17116
+ inputRequests: payload.requests
17117
+ };
17118
+ const res = await this.awaitToolTask(tool.name, waiting, {
17119
+ signal: context?.abortSignal,
17120
+ writer: context?.writer,
17121
+ suspend: suspendHost?.suspend,
17122
+ inputResponses
17123
+ });
17124
+ return res && finishToolCall(res);
17125
+ }
17031
17126
  this.log("debug", `Executing tool: ${tool.name}`, {
17032
17127
  toolArgs: input,
17033
17128
  runId: context?.runId
@@ -17045,13 +17140,17 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
17045
17140
  timeout: this.timeout,
17046
17141
  signal: context?.abortSignal
17047
17142
  }));
17048
- const toolCallSpan = context?.tracingContext?.currentSpan;
17049
17143
  if (toolCallSpan && serverSpan) toolCallSpan.links = [...toolCallSpan.links ?? [], serverSpan];
17050
17144
  const task = this.taskOptions ? takeTaskHandle(res) : void 0;
17051
17145
  if (!task) return finishToolCall(res);
17052
17146
  this.log("debug", `Tool ${tool.name} continues as MCP task ${task.taskId}`);
17053
17147
  toolCallSpan?.update({ metadata: { mcpTaskKey: taskKeyOf(task.taskId) } });
17054
- return finishToolCall(await this.awaitToolTask(tool.name, task, context?.abortSignal, context?.writer));
17148
+ const taskResult = await this.awaitToolTask(tool.name, task, {
17149
+ signal: context?.abortSignal,
17150
+ writer: context?.writer,
17151
+ suspend: suspendHost?.suspend
17152
+ });
17153
+ return taskResult && finishToolCall(taskResult);
17055
17154
  };
17056
17155
  const finishToolCall = async (res) => {
17057
17156
  if (res.isError && this.onToolError === "throw") {
@@ -17138,8 +17237,13 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
17138
17237
  maxRetries: 0
17139
17238
  };
17140
17239
  }
17141
- /** Waits for a remote task created by a tool call and returns its `CallToolResult`. */
17142
- async awaitToolTask(toolName, task, signal, writer) {
17240
+ /**
17241
+ * Waits for a remote task created by a tool call and returns its `CallToolResult`.
17242
+ *
17243
+ * When the task needs input that no `inputRequests` handler answers and the caller can `suspend`,
17244
+ * suspends with the requests and returns `undefined`, leaving the task open for the answers.
17245
+ */
17246
+ async awaitToolTask(toolName, task, { signal, writer, suspend, inputResponses }) {
17143
17247
  const details = {
17144
17248
  toolName,
17145
17249
  serverName: this.name,
@@ -17178,6 +17282,8 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
17178
17282
  }, task, {
17179
17283
  signal,
17180
17284
  inputRequests: this.serverConfig.inputRequests,
17285
+ stopAtInput: !this.serverConfig.inputRequests && suspend !== void 0,
17286
+ inputResponses,
17181
17287
  pollIntervalMs: this.taskOptions?.pollIntervalMs,
17182
17288
  maxWaitMs: this.taskOptions?.maxWaitMs,
17183
17289
  onStatus
@@ -17189,6 +17295,17 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
17189
17295
  throw fail("MCP_CLIENT_TOOL_TASK_POLL_FAILED", `Polling MCP task ${task.taskId} failed: ${reason}`, error);
17190
17296
  }
17191
17297
  switch (terminal.status) {
17298
+ case "input_required":
17299
+ await suspend({
17300
+ type: MCP_INPUT_REQUEST_SUSPEND_TYPE,
17301
+ serverName: this.name,
17302
+ toolName,
17303
+ taskId: terminal.taskId,
17304
+ createdAt: terminal.createdAt,
17305
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
17306
+ requests: terminal.inputRequests
17307
+ });
17308
+ return;
17192
17309
  case "completed": return terminal.result;
17193
17310
  case "cancelled": throw fail("MCP_CLIENT_TOOL_TASK_CANCELLED", terminal.statusMessage ?? `MCP task ${task.taskId} was cancelled`);
17194
17311
  case "failed": throw fail("MCP_CLIENT_TOOL_TASK_FAILED", (typeof terminal.error.message === "string" ? terminal.error.message : void 0) ?? terminal.statusMessage ?? `MCP task ${task.taskId} failed`);
@@ -20931,6 +21048,161 @@ function echoes(body, challenge) {
20931
21048
  const b = Buffer.from(challenge);
20932
21049
  return a.length === b.length && (0, crypto$1.timingSafeEqual)(a, b);
20933
21050
  }
21051
+ // src/server/model-output-to-mcp.ts
21052
+ const DATA_URI = /^data:([^;,]*)((?:;[^;,]*)*),(.*)$/is;
21053
+ /**
21054
+ * Maps the value a Mastra tool's `toModelOutput` returns (an AI SDK tool-result output such as
21055
+ * `{ type: 'text' }`, `{ type: 'json' }` or `{ type: 'content', value: [...] }`) to MCP content blocks.
21056
+ *
21057
+ * Returns `undefined` for any other shape, meaning the caller keeps its default result.
21058
+ */
21059
+ function modelOutputToMcpContent(value, toolName) {
21060
+ if (!isRecord(value)) return void 0;
21061
+ switch (value.type) {
21062
+ case "text": return typeof value.value === "string" ? success([{
21063
+ type: "text",
21064
+ text: value.value
21065
+ }]) : void 0;
21066
+ case "json": return success([{
21067
+ type: "text",
21068
+ text: stringify(value.value)
21069
+ }]);
21070
+ case "error-text": return typeof value.value === "string" ? {
21071
+ isError: true,
21072
+ content: [{
21073
+ type: "text",
21074
+ text: value.value
21075
+ }]
21076
+ } : void 0;
21077
+ case "error-json": return {
21078
+ isError: true,
21079
+ content: [{
21080
+ type: "text",
21081
+ text: stringify(value.value)
21082
+ }]
21083
+ };
21084
+ case "content": return Array.isArray(value.value) ? success(value.value.map((part, index) => partToBlock(part, toolName, index))) : void 0;
21085
+ default: return;
21086
+ }
21087
+ }
21088
+ function partToBlock(part, toolName, index) {
21089
+ if (!isRecord(part)) return {
21090
+ type: "text",
21091
+ text: stringify(part)
21092
+ };
21093
+ switch (part.type) {
21094
+ case "text":
21095
+ if (typeof part.text === "string") return {
21096
+ type: "text",
21097
+ text: part.text
21098
+ };
21099
+ break;
21100
+ case "media":
21101
+ case "image-data":
21102
+ case "file-data": {
21103
+ const fallback = part.type === "image-data" ? "image/jpeg" : "application/octet-stream";
21104
+ const mediaType = typeof part.mediaType === "string" && part.mediaType ? part.mediaType : void 0;
21105
+ if (typeof part.data === "string") {
21106
+ const uri = parseDataUri(part.data);
21107
+ if (uri) return mediaBlock(uri.data, mediaType ?? (uri.mediaType || fallback), toolName, index);
21108
+ if (/^data:/i.test(part.data)) break;
21109
+ return mediaBlock(part.data, mediaType ?? fallback, toolName, index);
21110
+ }
21111
+ if (part.data instanceof Uint8Array) return mediaBlock(Buffer.from(part.data).toString("base64"), mediaType ?? fallback, toolName, index);
21112
+ break;
21113
+ }
21114
+ case "image-url":
21115
+ case "file-url": {
21116
+ if (typeof part.url !== "string") break;
21117
+ const uri = parseDataUri(part.url);
21118
+ if (!uri) return {
21119
+ type: "text",
21120
+ text: part.url
21121
+ };
21122
+ const fallback = part.type === "image-url" ? "image/jpeg" : "application/octet-stream";
21123
+ const mediaType = typeof part.mediaType === "string" && part.mediaType ? part.mediaType : void 0;
21124
+ return mediaBlock(uri.data, mediaType ?? (uri.mediaType || fallback), toolName, index);
21125
+ }
21126
+ }
21127
+ return {
21128
+ type: "text",
21129
+ text: stringify(part)
21130
+ };
21131
+ }
21132
+ function mediaBlock(data, mediaType, toolName, index) {
21133
+ const separator = mediaType.indexOf(";");
21134
+ const mimeType = separator === -1 ? mediaType.toLowerCase() : mediaType.slice(0, separator).toLowerCase() + mediaType.slice(separator);
21135
+ if (mimeType.startsWith("image/")) return {
21136
+ type: "image",
21137
+ data,
21138
+ mimeType
21139
+ };
21140
+ if (mimeType.startsWith("audio/")) return {
21141
+ type: "audio",
21142
+ data,
21143
+ mimeType
21144
+ };
21145
+ return {
21146
+ type: "resource",
21147
+ resource: {
21148
+ uri: `mastra://tool-output/${encodeURIComponent(toolName)}/${index}`,
21149
+ mimeType,
21150
+ blob: data
21151
+ }
21152
+ };
21153
+ }
21154
+ /**
21155
+ * Splits a `data:` URI into its media type (with parameters such as `charset`, minus the `base64`
21156
+ * marker) and Base64 payload. Returns `undefined` if it can't be decoded.
21157
+ */
21158
+ function parseDataUri(value) {
21159
+ const match = DATA_URI.exec(value);
21160
+ if (!match) return void 0;
21161
+ const [, type = "", params = "", payload = ""] = match;
21162
+ const parameters = params.split(";").map((param) => param.trim()).filter(Boolean);
21163
+ const base64 = parameters.some((param) => param.toLowerCase() === "base64");
21164
+ let data = payload;
21165
+ if (!base64) {
21166
+ const bytes = decodePercentEncoded(payload);
21167
+ if (!bytes) return void 0;
21168
+ data = bytes.toString("base64");
21169
+ }
21170
+ const mediaType = type.trim().toLowerCase();
21171
+ const kept = parameters.filter((param) => param.toLowerCase() !== "base64");
21172
+ return {
21173
+ mediaType: mediaType && kept.length ? [mediaType, ...kept].join(";") : mediaType,
21174
+ data
21175
+ };
21176
+ }
21177
+ /** Decodes `%XX` escapes as raw octets (RFC 2397), not UTF-8 text. Returns `undefined` for a malformed escape. */
21178
+ function decodePercentEncoded(payload) {
21179
+ const input = Buffer.from(payload, "utf8");
21180
+ const output = Buffer.alloc(input.length);
21181
+ let length = 0;
21182
+ for (let index = 0; index < input.length; index++) {
21183
+ if (input[index] !== 37) {
21184
+ output[length++] = input[index];
21185
+ continue;
21186
+ }
21187
+ const hex = input.toString("latin1", index + 1, index + 3);
21188
+ if (!/^[0-9a-f]{2}$/i.test(hex)) return void 0;
21189
+ output[length++] = Number.parseInt(hex, 16);
21190
+ index += 2;
21191
+ }
21192
+ return output.subarray(0, length);
21193
+ }
21194
+ function success(content) {
21195
+ return {
21196
+ isError: false,
21197
+ content
21198
+ };
21199
+ }
21200
+ function stringify(value) {
21201
+ return JSON.stringify(value) ?? String(value);
21202
+ }
21203
+ function isRecord(value) {
21204
+ return typeof value === "object" && value !== null && !Array.isArray(value);
21205
+ }
20934
21206
  // src/server/tasks/store.ts
20935
21207
  /** How a principal is stored and shown. Principals can carry user and subject ids, so only their hash is kept. */
20936
21208
  function principalKeyOf(principal) {
@@ -22660,6 +22932,7 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
22660
22932
  tasksInspector;
22661
22933
  webhooks;
22662
22934
  legacy;
22935
+ toolResultOptions;
22663
22936
  tasks;
22664
22937
  /** Where tasks are kept: `tasks.store`, or the store picked on first use. */
22665
22938
  taskStore;
@@ -22683,6 +22956,7 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
22683
22956
  this.requestStateCodec = this.createRequestStateCodec(config.requestState);
22684
22957
  this.eventsOptions = config.eventsOptions ?? {};
22685
22958
  this.legacy = config.legacy ?? "reject";
22959
+ this.toolResultOptions = config.toolResult ?? {};
22686
22960
  this.tasks = resolveTasksConfig(config.tasks, {
22687
22961
  tools: Object.keys(config.tools ?? {}),
22688
22962
  workflows: Object.keys(config.workflows ?? {}),
@@ -23303,7 +23577,7 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
23303
23577
  resumeSchema: execution.resumeSchema
23304
23578
  });
23305
23579
  }
23306
- const result = server.projectCallToolResult(this.toCallToolResult(name, tool, execution.output), this.toolInfo(name, tool).outputSchema);
23580
+ const result = server.projectCallToolResult(await this.completedToolResult(name, tool, execution.output, requestContext), this.toolInfo(name, tool).outputSchema);
23307
23581
  this.logger.info(`Tool '${name}' finished in ${Date.now() - startedAt}ms.`);
23308
23582
  return result;
23309
23583
  } catch (error) {
@@ -23370,7 +23644,7 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
23370
23644
  suspendPayload: options.suspendPayload
23371
23645
  },
23372
23646
  initialAttempt: () => ({
23373
- outcome: settled.then((outcome) => "error" in outcome ? this.toolErrorOutcome(name, outcome.error) : this.toTaskOutcome(name, tool, outcome.execution)),
23647
+ outcome: settled.then((outcome) => "error" in outcome ? this.toolErrorOutcome(name, outcome.error) : this.toTaskOutcome(name, tool, outcome.execution, requestContext)),
23374
23648
  abort: (reason) => controller.abort(reason)
23375
23649
  })
23376
23650
  }) };
@@ -23481,7 +23755,7 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
23481
23755
  suspendPayload,
23482
23756
  tracingContext: span ? { currentSpan: span } : void 0
23483
23757
  });
23484
- return this.toTaskOutcome(name, tool, execution);
23758
+ return await this.toTaskOutcome(name, tool, execution, requestContext);
23485
23759
  } catch (error) {
23486
23760
  return this.toolErrorOutcome(name, error);
23487
23761
  }
@@ -23565,7 +23839,8 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
23565
23839
  }
23566
23840
  };
23567
23841
  }
23568
- toTaskOutcome(name, tool, execution) {
23842
+ /** Never rejects: a failure building the result completes the task with the tool error result. */
23843
+ async toTaskOutcome(name, tool, execution, requestContext) {
23569
23844
  if (execution.status === "suspended") {
23570
23845
  this.logger.debug(`Tool task '${name}' requires client input.`);
23571
23846
  return {
@@ -23575,7 +23850,11 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
23575
23850
  };
23576
23851
  }
23577
23852
  if (tool.mcp?.toolType === "agent" && execution.output?.finishReason === "suspended") return completed(errorResult$1(`Agent '${name.slice(4)}' paused for tool approval or input, which MCP tasks don't support yet`));
23578
- return completed(this.toCallToolResult(name, tool, execution.output));
23853
+ try {
23854
+ return completed(await this.completedToolResult(name, tool, execution.output, requestContext));
23855
+ } catch (error) {
23856
+ return this.toolErrorOutcome(name, error);
23857
+ }
23579
23858
  }
23580
23859
  /** A tool that throws inside a task completes it with `isError`, as a synchronous call would answer. */
23581
23860
  toolErrorOutcome(name, error) {
@@ -23890,6 +24169,34 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
23890
24169
  output
23891
24170
  };
23892
24171
  }
24172
+ /**
24173
+ * The result of a tool call that completed: the default result, built from `toModelOutput` when
24174
+ * `toolResult.modelOutput` is on, then passed through `toolResult.transform`. Throws what
24175
+ * `toModelOutput` or `transform` throw.
24176
+ */
24177
+ async completedToolResult(name, tool, output, requestContext) {
24178
+ let result = this.toCallToolResult(name, tool, output);
24179
+ const { modelOutput = false, transform } = this.toolResultOptions;
24180
+ if (modelOutput && !result.isError && tool.toModelOutput && output != null) {
24181
+ const mapped = modelOutputToMcpContent(await tool.toModelOutput(output), name);
24182
+ if (mapped?.isError) result = {
24183
+ isError: true,
24184
+ content: mapped.content,
24185
+ ...result._meta ? { _meta: result._meta } : {}
24186
+ };
24187
+ else if (mapped) result = {
24188
+ ...result,
24189
+ content: result.structuredContent !== void 0 ? [...result.content, ...mapped.content] : mapped.content
24190
+ };
24191
+ }
24192
+ if (transform) result = await transform({
24193
+ toolName: name,
24194
+ output,
24195
+ result,
24196
+ requestContext
24197
+ }) ?? result;
24198
+ return result;
24199
+ }
23893
24200
  toCallToolResult(name, tool, value) {
23894
24201
  if ((0, _mastra_core_tools.isValidationError)(value)) {
23895
24202
  this.logger.warn(`Tool '${name}' rejected its input.`, { error: value.message });