@mastra/mcp 2.0.0-alpha.4 → 2.0.0-alpha.5

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
@@ -18,7 +18,7 @@ import { makeCoreTool } from "@mastra/core/utils";
18
18
  import { PromptSchema } from "@modelcontextprotocol/core";
19
19
  import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "@modelcontextprotocol/ext-apps";
20
20
  import { hostHeaderValidation, originValidation, toNodeHandler } from "@modelcontextprotocol/node";
21
- import { ProtocolError, ProtocolErrorCode as ProtocolErrorCode$1, Server, createMcpHandler, createRequestStateCodec, inputRequired, inputResponse, specTypeSchemas } from "@modelcontextprotocol/server";
21
+ import { CLIENT_INFO_META_KEY, PROTOCOL_VERSION_META_KEY, ProtocolError, ProtocolErrorCode as ProtocolErrorCode$1, Server, createMcpHandler, createRequestStateCodec, inputRequired, inputResponse, specTypeSchemas } from "@modelcontextprotocol/server";
22
22
  import { serveStdio } from "@modelcontextprotocol/server/stdio";
23
23
  //#region \0rolldown/runtime.js
24
24
  var __defProp$3 = Object.defineProperty;
@@ -23251,21 +23251,23 @@ var InternalMastraMCPClient = class extends MastraBase {
23251
23251
  }
23252
23252
  async connectStdio(command) {
23253
23253
  this.log("debug", `Using Stdio transport for command: ${command}`);
23254
+ const transport = new StdioClientTransport({
23255
+ command,
23256
+ args: this.serverConfig.args,
23257
+ env: this.buildStdioEnv(),
23258
+ stderr: this.serverConfig.stderr,
23259
+ cwd: this.serverConfig.cwd
23260
+ });
23254
23261
  try {
23255
- this.transport = new StdioClientTransport({
23256
- command,
23257
- args: this.serverConfig.args,
23258
- env: this.buildStdioEnv(),
23259
- stderr: this.serverConfig.stderr,
23260
- cwd: this.serverConfig.cwd
23261
- });
23262
- await this.client.connect(this.transport, {
23262
+ await this.client.connect(transport, {
23263
23263
  timeout: this.serverConfig.timeout ?? this.timeout,
23264
23264
  prior: this.priorDiscovery
23265
23265
  });
23266
+ this.transport = transport;
23266
23267
  this.log("debug", `Successfully connected to MCP server via Stdio`);
23267
23268
  } catch (e) {
23268
23269
  this.log("error", e instanceof Error ? e.stack || e.message : JSON.stringify(e));
23270
+ await transport.close().catch(() => {});
23269
23271
  throw e;
23270
23272
  }
23271
23273
  }
@@ -23448,11 +23450,20 @@ var InternalMastraMCPClient = class extends MastraBase {
23448
23450
  this.clientConnectionOnClose = connectionOnClose;
23449
23451
  this.client.onclose = connectionOnClose;
23450
23452
  } catch (e) {
23453
+ if (!this.transport) this.unregisterProcessHooks();
23451
23454
  this.isConnected = null;
23452
23455
  this.priorDiscovery = void 0;
23453
23456
  reject(e);
23454
23457
  }
23455
23458
  });
23459
+ this.registerProcessHooks();
23460
+ return this.isConnected;
23461
+ }
23462
+ /**
23463
+ * Installs the process-exit and signal handlers that tear this client down when the
23464
+ * host process goes away. Idempotent, so repeated connects add at most one of each.
23465
+ */
23466
+ registerProcessHooks() {
23456
23467
  if (!this.exitHookUnsubscribe) this.exitHookUnsubscribe = asyncExitHook(async () => {
23457
23468
  this.log("debug", `Disconnecting MCP server during exit`);
23458
23469
  await this.disconnect();
@@ -23465,7 +23476,25 @@ var InternalMastraMCPClient = class extends MastraBase {
23465
23476
  this.sigHupHandler = () => gracefulExit();
23466
23477
  process.on("SIGHUP", this.sigHupHandler);
23467
23478
  }
23468
- return this.isConnected;
23479
+ }
23480
+ /**
23481
+ * Removes whatever {@link registerProcessHooks} installed. Keyed on the handlers this
23482
+ * client actually registered rather than on connection state, so a client that never
23483
+ * reached a transport still releases its process listeners.
23484
+ */
23485
+ unregisterProcessHooks() {
23486
+ if (this.exitHookUnsubscribe) {
23487
+ this.exitHookUnsubscribe();
23488
+ this.exitHookUnsubscribe = void 0;
23489
+ }
23490
+ if (this.sigTermHandler) {
23491
+ process.off("SIGTERM", this.sigTermHandler);
23492
+ this.sigTermHandler = void 0;
23493
+ }
23494
+ if (this.sigHupHandler) {
23495
+ process.off("SIGHUP", this.sigHupHandler);
23496
+ this.sigHupHandler = void 0;
23497
+ }
23469
23498
  }
23470
23499
  /**
23471
23500
  * Gets the stderr stream of the child process, if using stdio transport with `stderr: 'pipe'`.
@@ -23510,6 +23539,7 @@ var InternalMastraMCPClient = class extends MastraBase {
23510
23539
  this.closePendingAuthTransport();
23511
23540
  if (!this.transport) {
23512
23541
  await this.detachStaleClientTransport();
23542
+ this.unregisterProcessHooks();
23513
23543
  this.log("debug", "Disconnect called but no transport was connected.");
23514
23544
  return;
23515
23545
  }
@@ -23527,18 +23557,7 @@ var InternalMastraMCPClient = class extends MastraBase {
23527
23557
  this.transport = void 0;
23528
23558
  this.isConnected = null;
23529
23559
  this.serverInstructions = void 0;
23530
- if (this.exitHookUnsubscribe) {
23531
- this.exitHookUnsubscribe();
23532
- this.exitHookUnsubscribe = void 0;
23533
- }
23534
- if (this.sigTermHandler) {
23535
- process.off("SIGTERM", this.sigTermHandler);
23536
- this.sigTermHandler = void 0;
23537
- }
23538
- if (this.sigHupHandler) {
23539
- process.off("SIGHUP", this.sigHupHandler);
23540
- this.sigHupHandler = void 0;
23541
- }
23560
+ this.unregisterProcessHooks();
23542
23561
  }
23543
23562
  }
23544
23563
  /**
@@ -26396,6 +26415,95 @@ var MCPServer = class extends MCPServerBase {
26396
26415
  this.registerPromptHandlers(server);
26397
26416
  return server;
26398
26417
  }
26418
+ /**
26419
+ * Registers `method` under an `MCP_SERVER_REQUEST` span, so every request the
26420
+ * server answers is recorded — including the list and read methods that never
26421
+ * reach a tool, and requests that fail before any work is done.
26422
+ */
26423
+ setTracedHandler(server, method, handler) {
26424
+ server.setRequestHandler(method, async (request, ctx) => {
26425
+ const params = request.params;
26426
+ let requestContext;
26427
+ try {
26428
+ requestContext = await toRequestContext(ctx, this.mapAuthInfoToUser);
26429
+ } catch (error) {
26430
+ this.startRequestSpan(method, params, {
26431
+ server,
26432
+ ctx
26433
+ })?.error({ error });
26434
+ throw error;
26435
+ }
26436
+ const requestSpan = this.startRequestSpan(method, params, {
26437
+ server,
26438
+ ctx,
26439
+ requestContext
26440
+ });
26441
+ let reportedError;
26442
+ return this.traceRequest(requestSpan, () => handler(request, ctx, {
26443
+ requestSpan,
26444
+ requestContext,
26445
+ reportError: (error) => {
26446
+ reportedError = error;
26447
+ }
26448
+ }), () => reportedError);
26449
+ });
26450
+ }
26451
+ /**
26452
+ * Opens the span for one served request. `connection` is absent for
26453
+ * {@link executeTool}, which is called in-process and negotiates nothing.
26454
+ */
26455
+ startRequestSpan(method, params, connection) {
26456
+ const envelope = connection?.ctx?.mcpReq.envelope;
26457
+ const protocolVersion = envelope?.[PROTOCOL_VERSION_META_KEY] ?? connection?.server?.getNegotiatedProtocolVersion();
26458
+ const client = envelope?.[CLIENT_INFO_META_KEY] ?? connection?.server?.getClientVersion();
26459
+ const target = params?.name ?? params?.uri;
26460
+ const targetName = typeof target === "string" ? target : void 0;
26461
+ return getOrCreateSpan({
26462
+ type: SpanType.MCP_SERVER_REQUEST,
26463
+ name: targetName ? `${method} ${targetName}` : method,
26464
+ entityType: EntityType.MCP_SERVER,
26465
+ entityId: this.id,
26466
+ entityName: this.name,
26467
+ input: params,
26468
+ attributes: {
26469
+ mcpMethod: method,
26470
+ targetName,
26471
+ mcpServer: this.name,
26472
+ serverVersion: this.version,
26473
+ mcpProtocolVersion: typeof protocolVersion === "string" ? protocolVersion : void 0,
26474
+ clientName: client?.name,
26475
+ clientVersion: client?.version
26476
+ },
26477
+ tracingContext: {},
26478
+ requestContext: connection?.requestContext,
26479
+ mastra: this.mastra
26480
+ });
26481
+ }
26482
+ /**
26483
+ * Runs `fn` under a request span. Only the protocol's own `isError` marks a
26484
+ * failure; the rest of a result is opaque handler output that may legitimately
26485
+ * carry any field. A thrown error ends the whole span tree and is rethrown.
26486
+ */
26487
+ async traceRequest(requestSpan, fn, getReportedError) {
26488
+ try {
26489
+ const result = await fn();
26490
+ const errorResult = result;
26491
+ if (errorResult?.isError) requestSpan?.error({ error: getReportedError?.() ?? new MastraError({
26492
+ id: "MCP_SERVER_REQUEST_FAILED",
26493
+ domain: ErrorDomain.MCP,
26494
+ category: ErrorCategory.USER,
26495
+ text: errorResult.content?.map((c) => c.text).filter(Boolean).join("\n")
26496
+ }) });
26497
+ else requestSpan?.end({ output: result });
26498
+ return result;
26499
+ } catch (error) {
26500
+ requestSpan?.error({
26501
+ error,
26502
+ endTree: true
26503
+ });
26504
+ throw error;
26505
+ }
26506
+ }
26399
26507
  /** Answers a suspended round: one keyed form derived from the handler's `resumeSchema`. */
26400
26508
  async inputRequired(ctx, requestContext, suspension) {
26401
26509
  const payload = suspension.suspendPayload;
@@ -26420,9 +26528,8 @@ var MCPServer = class extends MCPServerBase {
26420
26528
  })
26421
26529
  });
26422
26530
  }
26423
- async serverRequest(ctx, method, name, args, resumeSchema) {
26531
+ async serverRequest(ctx, requestContext, method, name, args, resumeSchema) {
26424
26532
  const argsHash = hashArguments(args);
26425
- const requestContext = await toRequestContext(ctx, this.mapAuthInfoToUser);
26426
26533
  const continuation = readContinuation(ctx, requestContext, {
26427
26534
  method,
26428
26535
  name,
@@ -26448,11 +26555,10 @@ var MCPServer = class extends MCPServerBase {
26448
26555
  };
26449
26556
  }
26450
26557
  registerToolHandlers(server) {
26451
- server.setRequestHandler("tools/list", async (_request, ctx) => {
26452
- const requestContext = await toRequestContext(ctx, this.mapAuthInfoToUser);
26453
- return { tools: (await this.authorizedToolEntries(requestContext)).map(([name, tool]) => this.toMCPTool(name, tool)) };
26558
+ this.setTracedHandler(server, "tools/list", async (_request, _ctx, trace) => {
26559
+ return { tools: (await this.authorizedToolEntries(trace.requestContext)).map(([name, tool]) => this.toMCPTool(name, tool)) };
26454
26560
  });
26455
- server.setRequestHandler("tools/call", async (request, ctx) => {
26561
+ this.setTracedHandler(server, "tools/call", async (request, ctx, trace) => {
26456
26562
  const name = request.params.name;
26457
26563
  const tool = this.convertedTools[name];
26458
26564
  if (!tool) {
@@ -26461,43 +26567,25 @@ var MCPServer = class extends MCPServerBase {
26461
26567
  }
26462
26568
  const args = request.params.arguments ?? {};
26463
26569
  const argsHash = hashArguments(args);
26464
- const requestContext = await toRequestContext(ctx, this.mapAuthInfoToUser);
26570
+ const requestContext = trace.requestContext;
26465
26571
  const continuation = readContinuation(ctx, requestContext, {
26466
26572
  method: "tools/call",
26467
26573
  name,
26468
26574
  argsHash
26469
26575
  });
26470
26576
  const mcp = toToolExecutionContext(ctx, this.name);
26471
- const span = getOrCreateSpan({
26472
- type: SpanType.TOOL_CALL,
26473
- name: `tool: '${name}'`,
26474
- input: args,
26475
- entityType: EntityType.TOOL,
26476
- entityId: name,
26477
- entityName: name,
26478
- attributes: {
26479
- toolType: "tool",
26480
- toolDescription: tool.description
26481
- },
26482
- requestContext,
26483
- mastra: this.mastra
26484
- });
26485
26577
  const startedAt = Date.now();
26486
26578
  try {
26487
26579
  await this.enforceToolExecutionFGA(name, requestContext);
26488
- if (continuation && continuation.outcome !== "accept") {
26489
- span?.end({ attributes: { success: false } });
26490
- return errorResult(`Tool '${name}' was ${continuation.outcome === "decline" ? "declined" : "cancelled"}`);
26491
- }
26580
+ if (continuation && continuation.outcome !== "accept") return errorResult(`Tool '${name}' was ${continuation.outcome === "decline" ? "declined" : "cancelled"}`);
26492
26581
  const execution = await this.runTool(name, tool, args, {
26493
26582
  requestContext,
26494
26583
  mcp,
26495
- tracingContext: { currentSpan: span },
26584
+ tracingContext: { currentSpan: trace.requestSpan },
26496
26585
  resumeData: continuation?.resumeData,
26497
26586
  suspendPayload: continuation?.suspendPayload
26498
26587
  });
26499
26588
  if (execution.status === "suspended") {
26500
- span?.end({ attributes: { success: true } });
26501
26589
  this.logger.debug(`Tool '${name}' requires client input.`);
26502
26590
  return this.inputRequired(ctx, requestContext, {
26503
26591
  method: "tools/call",
@@ -26509,17 +26597,9 @@ var MCPServer = class extends MCPServerBase {
26509
26597
  });
26510
26598
  }
26511
26599
  const result = this.toCallToolResult(name, tool, execution.output);
26512
- span?.end({
26513
- output: execution.output,
26514
- attributes: { success: !result.isError }
26515
- });
26516
26600
  this.logger.info(`Tool '${name}' finished in ${Date.now() - startedAt}ms.`);
26517
26601
  return result;
26518
26602
  } catch (error) {
26519
- span?.error({
26520
- error,
26521
- attributes: { success: false }
26522
- });
26523
26603
  if (error instanceof ProtocolError) throw error;
26524
26604
  this.logger.error("Tool execution failed", {
26525
26605
  tool: name,
@@ -26531,6 +26611,7 @@ var MCPServer = class extends MCPServerBase {
26531
26611
  category: ErrorCategory.USER,
26532
26612
  details: { toolName: name }
26533
26613
  }, error);
26614
+ trace.reportError(mastraError);
26534
26615
  return errorResult(JSON.stringify(mastraError.toJSON()));
26535
26616
  }
26536
26617
  });
@@ -26546,6 +26627,7 @@ var MCPServer = class extends MCPServerBase {
26546
26627
  const output = await tool.execute(args, {
26547
26628
  toolCallId: "",
26548
26629
  messages: [],
26630
+ skipToolSpan: true,
26549
26631
  requestContext: options.requestContext,
26550
26632
  tracingContext: options.tracingContext,
26551
26633
  abortSignal: options.mcp?.extra.signal,
@@ -26599,19 +26681,19 @@ var MCPServer = class extends MCPServerBase {
26599
26681
  const options = this.resourceOptions;
26600
26682
  const hasAppResources = this.appResourceList.length > 0;
26601
26683
  if (!options && !hasAppResources) return;
26602
- const listResources = async (ctx) => [...this.appResourceList, ...await options?.listResources({
26684
+ const listResources = async (ctx, requestContext) => [...this.appResourceList, ...await options?.listResources({
26603
26685
  extra: toToolExecutionContext(ctx, this.name).extra,
26604
- requestContext: await toRequestContext(ctx, this.mapAuthInfoToUser)
26686
+ requestContext
26605
26687
  }) ?? []];
26606
- server.setRequestHandler("resources/list", async (_request, ctx) => ({ resources: await listResources(ctx) }));
26607
- server.setRequestHandler("resources/read", async (request, ctx) => {
26688
+ this.setTracedHandler(server, "resources/list", async (_request, ctx, trace) => ({ resources: await listResources(ctx, trace.requestContext) }));
26689
+ this.setTracedHandler(server, "resources/read", async (request, ctx, trace) => {
26608
26690
  const uri = request.params.uri;
26609
- const resource = (await listResources(ctx)).find((r) => r.uri === uri);
26691
+ const resource = (await listResources(ctx, trace.requestContext)).find((r) => r.uri === uri);
26610
26692
  if (!resource) throw new ProtocolError(ProtocolErrorCode$1.InvalidParams, `Resource not found: ${uri}`);
26611
26693
  const html = this.appResourceHtml.get(uri);
26612
26694
  if (html !== void 0) return { contents: [this.resourceContents(resource, { text: html })] };
26613
26695
  if (!options) throw new ProtocolError(ProtocolErrorCode$1.InvalidParams, `Resource not found: ${uri}`);
26614
- const { request: serverRequest, continuation, suspended, argsHash } = await this.serverRequest(ctx, "resources/read", uri, {}, options.resumeSchema);
26696
+ const { request: serverRequest, continuation, suspended, argsHash } = await this.serverRequest(ctx, trace.requestContext, "resources/read", uri, {}, options.resumeSchema);
26615
26697
  if (continuation && continuation.outcome !== "accept") throw new ProtocolError(ProtocolErrorCode$1.InvalidParams, `Reading '${uri}' was ${continuation.outcome}ed`);
26616
26698
  const result = await options.getResourceContent({
26617
26699
  uri,
@@ -26629,9 +26711,9 @@ var MCPServer = class extends MCPServerBase {
26629
26711
  if (result === void 0) throw new Error(`Resource '${uri}' returned no content`);
26630
26712
  return { contents: (Array.isArray(result) ? result : [result]).map((content) => this.resourceContents(resource, content)) };
26631
26713
  });
26632
- if (options?.resourceTemplates) server.setRequestHandler("resources/templates/list", async (_request, ctx) => ({ resourceTemplates: await options.resourceTemplates({
26714
+ if (options?.resourceTemplates) this.setTracedHandler(server, "resources/templates/list", async (_request, ctx, trace) => ({ resourceTemplates: await options.resourceTemplates({
26633
26715
  extra: toToolExecutionContext(ctx, this.name).extra,
26634
- requestContext: await toRequestContext(ctx, this.mapAuthInfoToUser)
26716
+ requestContext: trace.requestContext
26635
26717
  }) }));
26636
26718
  }
26637
26719
  resourceContents(resource, content) {
@@ -26653,22 +26735,22 @@ var MCPServer = class extends MCPServerBase {
26653
26735
  registerPromptHandlers(server) {
26654
26736
  const options = this.promptOptions;
26655
26737
  if (!options) return;
26656
- const listPrompts = async (ctx) => {
26738
+ const listPrompts = async (ctx, requestContext) => {
26657
26739
  const prompts = await options.listPrompts({
26658
26740
  extra: toToolExecutionContext(ctx, this.name).extra,
26659
- requestContext: await toRequestContext(ctx, this.mapAuthInfoToUser)
26741
+ requestContext
26660
26742
  });
26661
26743
  for (const prompt of prompts) PromptSchema.parse(prompt);
26662
26744
  return prompts;
26663
26745
  };
26664
- server.setRequestHandler("prompts/list", async (_request, ctx) => ({ prompts: await listPrompts(ctx) }));
26746
+ this.setTracedHandler(server, "prompts/list", async (_request, ctx, trace) => ({ prompts: await listPrompts(ctx, trace.requestContext) }));
26665
26747
  if (!options.getPromptMessages) return;
26666
- server.setRequestHandler("prompts/get", async (request, ctx) => {
26748
+ this.setTracedHandler(server, "prompts/get", async (request, ctx, trace) => {
26667
26749
  const { name, arguments: args } = request.params;
26668
- const prompt = (await listPrompts(ctx)).find((p) => p.name === name);
26750
+ const prompt = (await listPrompts(ctx, trace.requestContext)).find((p) => p.name === name);
26669
26751
  if (!prompt) throw new ProtocolError(ProtocolErrorCode$1.InvalidParams, `Prompt "${name}" not found`);
26670
26752
  for (const arg of prompt.arguments ?? []) if (arg.required && (args?.[arg.name] === void 0 || args?.[arg.name] === null)) throw new ProtocolError(ProtocolErrorCode$1.InvalidParams, `Missing required argument: ${arg.name}`);
26671
- const { request: serverRequest, continuation, suspended, argsHash } = await this.serverRequest(ctx, "prompts/get", name, args ?? {}, options.resumeSchema);
26753
+ const { request: serverRequest, continuation, suspended, argsHash } = await this.serverRequest(ctx, trace.requestContext, "prompts/get", name, args ?? {}, options.resumeSchema);
26672
26754
  if (continuation && continuation.outcome !== "accept") throw new ProtocolError(ProtocolErrorCode$1.InvalidParams, `Prompt "${name}" was ${continuation.outcome}ed`);
26673
26755
  const messages = await options.getPromptMessages({
26674
26756
  name,
@@ -26881,52 +26963,58 @@ var MCPServer = class extends MCPServerBase {
26881
26963
  * arguments with `resumeData` and `suspendPayload`.
26882
26964
  */
26883
26965
  async executeTool(toolId, args, executionContext = {}) {
26884
- const tool = this.convertedTools[toolId];
26885
- if (!tool) {
26886
- this.logger.warn("Unknown tool requested", {
26887
- tool: toolId,
26888
- server: this.name
26889
- });
26890
- throw new MastraError({
26891
- id: "MCP_SERVER_TOOL_EXECUTE_PREPARATION_FAILED",
26892
- domain: ErrorDomain.MCP,
26893
- category: ErrorCategory.USER,
26894
- text: `Unknown tool: ${toolId}`,
26895
- details: { toolId }
26896
- });
26897
- }
26898
26966
  const requestContext = executionContext.requestContext ?? new RequestContext();
26899
- await this.enforceToolExecutionFGA(toolId, requestContext);
26900
- try {
26901
- const execution = await this.runTool(toolId, tool, args, {
26902
- requestContext,
26903
- tracingContext: { currentSpan: void 0 },
26904
- resumeData: executionContext.resumeData,
26905
- suspendPayload: executionContext.suspendPayload
26906
- });
26907
- if (execution.status === "completed" && isValidationError(execution.output)) throw new MastraError({
26908
- id: "MCP_SERVER_TOOL_INVALID_INPUT",
26909
- domain: ErrorDomain.MCP,
26910
- category: ErrorCategory.USER,
26911
- text: execution.output.message,
26912
- details: { toolId }
26913
- });
26914
- this.logger.info("Tool executed successfully", { tool: toolId });
26915
- return execution;
26916
- } catch (error) {
26917
- if (error instanceof MastraError && error.id === "MCP_SERVER_TOOL_INVALID_INPUT") throw error;
26918
- const mastraError = new MastraError({
26919
- id: "MCP_SERVER_TOOL_EXECUTE_FAILED",
26920
- domain: ErrorDomain.MCP,
26921
- category: ErrorCategory.USER,
26922
- details: {
26923
- toolId,
26924
- args: JSON.stringify(args)
26925
- }
26926
- }, error);
26927
- this.logger.trackException(mastraError);
26928
- throw mastraError;
26929
- }
26967
+ const requestSpan = this.startRequestSpan("tools/call", {
26968
+ name: toolId,
26969
+ arguments: args
26970
+ }, { requestContext });
26971
+ return this.traceRequest(requestSpan, async () => {
26972
+ const tool = this.convertedTools[toolId];
26973
+ if (!tool) {
26974
+ this.logger.warn("Unknown tool requested", {
26975
+ tool: toolId,
26976
+ server: this.name
26977
+ });
26978
+ throw new MastraError({
26979
+ id: "MCP_SERVER_TOOL_EXECUTE_PREPARATION_FAILED",
26980
+ domain: ErrorDomain.MCP,
26981
+ category: ErrorCategory.USER,
26982
+ text: `Unknown tool: ${toolId}`,
26983
+ details: { toolId }
26984
+ });
26985
+ }
26986
+ await this.enforceToolExecutionFGA(toolId, requestContext);
26987
+ try {
26988
+ const execution = await this.runTool(toolId, tool, args, {
26989
+ requestContext,
26990
+ tracingContext: { currentSpan: requestSpan },
26991
+ resumeData: executionContext.resumeData,
26992
+ suspendPayload: executionContext.suspendPayload
26993
+ });
26994
+ if (execution.status === "completed" && isValidationError(execution.output)) throw new MastraError({
26995
+ id: "MCP_SERVER_TOOL_INVALID_INPUT",
26996
+ domain: ErrorDomain.MCP,
26997
+ category: ErrorCategory.USER,
26998
+ text: execution.output.message,
26999
+ details: { toolId }
27000
+ });
27001
+ this.logger.info("Tool executed successfully", { tool: toolId });
27002
+ return execution;
27003
+ } catch (error) {
27004
+ if (error instanceof MastraError && error.id === "MCP_SERVER_TOOL_INVALID_INPUT") throw error;
27005
+ const mastraError = new MastraError({
27006
+ id: "MCP_SERVER_TOOL_EXECUTE_FAILED",
27007
+ domain: ErrorDomain.MCP,
27008
+ category: ErrorCategory.USER,
27009
+ details: {
27010
+ toolId,
27011
+ args: JSON.stringify(args)
27012
+ }
27013
+ }, error);
27014
+ this.logger.trackException(mastraError);
27015
+ throw mastraError;
27016
+ }
27017
+ });
26930
27018
  }
26931
27019
  /** Reads an `ui://` app resource; application resources require a protocol request. */
26932
27020
  async readResource(uri) {