@mastra/mcp 1.6.0 → 1.6.1-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 { AsyncLocalStorage } from 'async_hooks';
2
+ import { createRequire } from 'module';
2
3
  import { MastraBase } from '@mastra/core/base';
3
4
  import { createTool, isValidationError } from '@mastra/core/tools';
4
5
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
@@ -415,7 +416,57 @@ function isReconnectableMCPError(error) {
415
416
 
416
417
  // src/client/client.ts
417
418
  var DEFAULT_SERVER_CONNECT_TIMEOUT_MSEC = 3e3;
419
+ var require2 = createRequire(import.meta.url);
418
420
  var SSE_FALLBACK_STATUS_CODES = [400, 404, 405];
421
+ var DATADOG_TRACER_TEST_SYMBOL = /* @__PURE__ */ Symbol.for("mastra.mcp.dd-trace-test-tracer");
422
+ function shouldDetachPersistentTransportRequest(init) {
423
+ return (init?.method ?? "GET").toUpperCase() === "GET";
424
+ }
425
+ function getDatadogScope() {
426
+ const testTracer = globalThis[DATADOG_TRACER_TEST_SYMBOL];
427
+ const tracer = testTracer ?? loadDatadogTracer();
428
+ if (typeof tracer?.scope === "function") {
429
+ return tracer.scope();
430
+ }
431
+ if (typeof tracer?.default?.scope === "function") {
432
+ return tracer.default.scope();
433
+ }
434
+ return null;
435
+ }
436
+ function loadDatadogTracer() {
437
+ if (!isDatadogTracerLikelyLoaded()) {
438
+ return null;
439
+ }
440
+ try {
441
+ return require2("dd-trace");
442
+ } catch {
443
+ return null;
444
+ }
445
+ }
446
+ function isDatadogTracerLikelyLoaded() {
447
+ if (globalThis[DATADOG_TRACER_TEST_SYMBOL]) {
448
+ return true;
449
+ }
450
+ if (process.execArgv.some((arg) => arg.includes("dd-trace"))) {
451
+ return true;
452
+ }
453
+ if (process.env.NODE_OPTIONS?.includes("dd-trace")) {
454
+ return true;
455
+ }
456
+ try {
457
+ const resolvedPath = require2.resolve("dd-trace");
458
+ return Boolean(require2.cache[resolvedPath]);
459
+ } catch {
460
+ return false;
461
+ }
462
+ }
463
+ function runOutsideDatadogTraceScope(callback) {
464
+ const scope = getDatadogScope();
465
+ if (!scope) {
466
+ return callback();
467
+ }
468
+ return scope.activate(null, callback);
469
+ }
419
470
  function convertLogLevelToLoggerMethod(level) {
420
471
  switch (level) {
421
472
  case "debug":
@@ -610,7 +661,11 @@ var InternalMastraMCPClient = class extends MastraBase {
610
661
  }
611
662
  async connectHttp(url) {
612
663
  const { requestInit, eventSourceInit, authProvider, connectTimeout, fetch: userFetch } = this.serverConfig;
613
- const fetch2 = userFetch ? (url2, init) => userFetch(url2, init, this.operationContextStore.getStore() ?? null) : void 0;
664
+ const fetch2 = (requestUrl, init) => {
665
+ const requestContext = this.operationContextStore.getStore() ?? null;
666
+ const executeFetch = () => userFetch ? userFetch(requestUrl, init, requestContext) : globalThis.fetch(requestUrl, init);
667
+ return shouldDetachPersistentTransportRequest(init) ? runOutsideDatadogTraceScope(executeFetch) : executeFetch();
668
+ };
614
669
  this.log("debug", `Attempting to connect to URL: ${url}`);
615
670
  let shouldTrySSE = url.pathname.endsWith(`/sse`);
616
671
  if (!shouldTrySSE) {
@@ -639,7 +694,7 @@ var InternalMastraMCPClient = class extends MastraBase {
639
694
  if (shouldTrySSE) {
640
695
  this.log("debug", "Falling back to deprecated HTTP+SSE transport...");
641
696
  try {
642
- const sseEventSourceInit = fetch2 ? { ...eventSourceInit, fetch: fetch2 } : eventSourceInit;
697
+ const sseEventSourceInit = { ...eventSourceInit, fetch: fetch2 };
643
698
  const sseTransport = new SSEClientTransport(url, {
644
699
  requestInit,
645
700
  eventSourceInit: sseEventSourceInit,
@@ -2764,9 +2819,11 @@ var MCPServer = class extends MCPServerBase {
2764
2819
  * This allows us to create multiple server instances with identical functionality.
2765
2820
  */
2766
2821
  registerHandlersOnServer(serverInstance) {
2767
- serverInstance.setRequestHandler(ListToolsRequestSchema, async () => {
2822
+ serverInstance.setRequestHandler(ListToolsRequestSchema, async (_request, extra) => {
2823
+ const proxiedContext = this.createProxiedRequestContext(extra);
2824
+ const tools = await this.getAuthorizedConvertedToolEntries(proxiedContext);
2768
2825
  return {
2769
- tools: Object.values(this.convertedTools).map((tool) => {
2826
+ tools: tools.map(([, tool]) => {
2770
2827
  const toolSpec = {
2771
2828
  name: tool.id || "unknown",
2772
2829
  description: tool.description,
@@ -2858,6 +2915,7 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
2858
2915
  throw new Error(`The "extra" key is now nested under "mcp.extra" in tool arguments`);
2859
2916
  }
2860
2917
  };
2918
+ await this.enforceToolExecutionFGA(request.params.name, proxiedContext);
2861
2919
  const result = await tool.execute(validation?.value ?? request.params.arguments ?? {}, mcpOptions);
2862
2920
  const duration = Date.now() - startTime;
2863
2921
  if (isValidationError(result)) {
@@ -4049,7 +4107,24 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
4049
4107
  * });
4050
4108
  * ```
4051
4109
  */
4052
- getToolListInfo() {
4110
+ getToolListInfo(requestContext) {
4111
+ const fgaProvider = this.mastra?.getServer?.()?.fga;
4112
+ if (fgaProvider && requestContext) {
4113
+ return this.getAuthorizedConvertedToolEntries(requestContext).then((tools) => ({
4114
+ tools: tools.map(([toolId, tool]) => ({
4115
+ id: toolId,
4116
+ name: tool.id || toolId,
4117
+ description: tool.description,
4118
+ inputSchema: this.convertSchema(tool.parameters),
4119
+ outputSchema: this.convertSchema(tool.outputSchema),
4120
+ toolType: tool.mcp?.toolType,
4121
+ _meta: withMastraToolStrictMeta(tool.mcp?._meta, tool.strict)
4122
+ }))
4123
+ }));
4124
+ }
4125
+ if (fgaProvider && !requestContext) {
4126
+ return { tools: [] };
4127
+ }
4053
4128
  this.logger.debug("Getting tool list", { server: this.name });
4054
4129
  return {
4055
4130
  tools: Object.entries(this.convertedTools).map(([toolId, tool]) => ({
@@ -4057,7 +4132,7 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
4057
4132
  name: tool.id || toolId,
4058
4133
  description: tool.description,
4059
4134
  inputSchema: this.convertSchema(tool.parameters),
4060
- outputSchema: this.convertSchema(tool.parameters),
4135
+ outputSchema: this.convertSchema(tool.outputSchema),
4061
4136
  toolType: tool.mcp?.toolType,
4062
4137
  _meta: withMastraToolStrictMeta(tool.mcp?._meta, tool.strict)
4063
4138
  }))
@@ -4097,6 +4172,58 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
4097
4172
  _meta: withMastraToolStrictMeta(tool.mcp?._meta, tool.strict)
4098
4173
  };
4099
4174
  }
4175
+ createProxiedRequestContext(extra) {
4176
+ const proxiedContext = new RequestContext();
4177
+ if (extra && typeof extra === "object") {
4178
+ Object.entries(extra).forEach(([key, value]) => {
4179
+ proxiedContext.set(key, value);
4180
+ });
4181
+ }
4182
+ return proxiedContext;
4183
+ }
4184
+ async getAuthorizedConvertedToolEntries(requestContext) {
4185
+ const entries = Object.entries(this.convertedTools);
4186
+ const fgaProvider = this.mastra?.getServer?.()?.fga;
4187
+ if (!fgaProvider) {
4188
+ return entries;
4189
+ }
4190
+ const user = requestContext.get("user");
4191
+ if (!user) {
4192
+ return [];
4193
+ }
4194
+ const accessible = await Promise.all(
4195
+ entries.map(async ([toolId, tool]) => {
4196
+ try {
4197
+ await this.enforceToolExecutionFGA(toolId, requestContext);
4198
+ return [toolId, tool];
4199
+ } catch (error) {
4200
+ if (error instanceof Error && error.name === "FGADeniedError") {
4201
+ return null;
4202
+ }
4203
+ throw error;
4204
+ }
4205
+ })
4206
+ );
4207
+ return accessible.filter((entry) => entry !== null);
4208
+ }
4209
+ async enforceToolExecutionFGA(toolId, requestContext) {
4210
+ const fgaProvider = this.mastra?.getServer?.()?.fga;
4211
+ if (!fgaProvider) {
4212
+ return;
4213
+ }
4214
+ const { checkFGA, FGADeniedError, MastraFGAPermissions } = await import('@mastra/core/auth/ee');
4215
+ const resourceId = JSON.stringify([this.id, toolId]);
4216
+ const user = requestContext?.get("user");
4217
+ if (!user) {
4218
+ throw new FGADeniedError({ id: "unknown" }, { type: "tool", id: resourceId }, MastraFGAPermissions.TOOLS_EXECUTE);
4219
+ }
4220
+ await checkFGA({
4221
+ fgaProvider,
4222
+ user,
4223
+ resource: { type: "tool", id: resourceId },
4224
+ permission: MastraFGAPermissions.TOOLS_EXECUTE
4225
+ });
4226
+ }
4100
4227
  /**
4101
4228
  * Executes a specific tool provided by this MCP server.
4102
4229
  *
@@ -4179,8 +4306,10 @@ Provided arguments: ${JSON.stringify(args, null, 2)}`,
4179
4306
  try {
4180
4307
  const finalExecutionContext = {
4181
4308
  messages: executionContext?.messages || [],
4182
- toolCallId: executionContext?.toolCallId || randomUUID()
4309
+ toolCallId: executionContext?.toolCallId || randomUUID(),
4310
+ requestContext: executionContext?.requestContext
4183
4311
  };
4312
+ await this.enforceToolExecutionFGA(toolId, finalExecutionContext.requestContext);
4184
4313
  const result = await tool.execute(validatedArgs, finalExecutionContext);
4185
4314
  this.logger.info("Tool executed successfully", { tool: toolId });
4186
4315
  return result;