@mastra/mcp 1.9.1 → 1.10.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/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # @mastra/mcp
2
2
 
3
+ ## 1.10.0-alpha.1
4
+
5
+ ### Minor Changes
6
+
7
+ - Added MCP server Fine-Grained Authorization mapping overrides for tool authorization. ([#17529](https://github.com/mastra-ai/mastra/pull/17529))
8
+
9
+ Use the new `fga` option on `MCPServer` to customize the resource and permission mappings used for `tools/list` and `tools/call` checks without changing the Mastra instance-level `tool` mapping used by internal agent and workflow tool execution.
10
+
11
+ ```ts
12
+ const server = new MCPServer({
13
+ name: 'My Server',
14
+ version: '1.0.0',
15
+ tools: { getData },
16
+ fga: {
17
+ resourceMapping: {
18
+ tool: {
19
+ fgaResourceType: 'user',
20
+ deriveId: ({ user }) => user.id,
21
+ },
22
+ },
23
+ permissionMapping: {
24
+ 'tools:execute': 'read',
25
+ },
26
+ },
27
+ });
28
+ ```
29
+
30
+ ### Patch Changes
31
+
32
+ - Updated dependencies [[`575f815`](https://github.com/mastra-ai/mastra/commit/575f815c5c3567b71c0b83cbb7fa98c8253a9d9c), [`306909a`](https://github.com/mastra-ai/mastra/commit/306909a693de77d709b38706e2673c9547d24a28), [`5191af8`](https://github.com/mastra-ai/mastra/commit/5191af80c799eea25357c545fc05d91b3883531d), [`43bd3d4`](https://github.com/mastra-ai/mastra/commit/43bd3d421987463fdf35386a45199c49499ed069), [`e6fa79e`](https://github.com/mastra-ai/mastra/commit/e6fa79ec72a2ddffdd25e85270398951e9d552a4), [`904bcdf`](https://github.com/mastra-ai/mastra/commit/904bcdf7b8004aa7be823f9f70ca63580e47e470), [`7f5ee1d`](https://github.com/mastra-ai/mastra/commit/7f5ee1dca46daee8d2817f2ebe49e6335da81956), [`1e9aab5`](https://github.com/mastra-ai/mastra/commit/1e9aab50ff11e6e88fde4d7cbf512c44a9fe8d61), [`bf8eb6d`](https://github.com/mastra-ai/mastra/commit/bf8eb6d0ec213a403eb9265a594ad283c44ab3dc), [`493a328`](https://github.com/mastra-ai/mastra/commit/493a328f4346a1deeb9f1e2e44c8f2a3a4d7591b), [`029a414`](https://github.com/mastra-ai/mastra/commit/029a4141719793bd3e898a39eb5a0466a55f5f3a), [`b147b29`](https://github.com/mastra-ai/mastra/commit/b147b2907f0cd1aa812efe6d6e3f58d22e66fc88), [`d371ac1`](https://github.com/mastra-ai/mastra/commit/d371ac1d9820afaaf7cfdbc380a475946a994d8f), [`cf182b7`](https://github.com/mastra-ai/mastra/commit/cf182b7fb495767946d9840ef29f19cfa906f31f), [`a049c2a`](https://github.com/mastra-ai/mastra/commit/a049c2a9dfb41d0ee2e7a28874a88cd64fd5669f), [`b147b29`](https://github.com/mastra-ai/mastra/commit/b147b2907f0cd1aa812efe6d6e3f58d22e66fc88), [`2a96528`](https://github.com/mastra-ai/mastra/commit/2a9652848dfa3c5a2426f952e9d93554c26fd90f), [`2656d9c`](https://github.com/mastra-ai/mastra/commit/2656d9c2976d4f3354253bfbbbf9b88a1b2bbf34), [`63e3fe1`](https://github.com/mastra-ai/mastra/commit/63e3fe13cc1ea96f91d7c68aea92f400faf9e4da), [`1d4ce8d`](https://github.com/mastra-ai/mastra/commit/1d4ce8daaa54511f325c1b609d31b8e54009d677), [`8c68372`](https://github.com/mastra-ai/mastra/commit/8c68372e85fe0b066ec12c58bd29ffb93e54c552)]:
33
+ - @mastra/core@1.42.0-alpha.4
34
+
35
+ ## 1.9.2-alpha.0
36
+
37
+ ### Patch Changes
38
+
39
+ - Fixed `MCPServer` leaking one caller's resources to other callers. The result of the first `resources/list` request was cached on the shared, long-lived server instance and replayed to everyone, so a dynamic resource provider that scopes resources per user or tenant (resolved from `extra.authInfo`) served the first caller's resource index — names and URIs — to subsequent callers. The same stale cache also backed `resources/read` URI resolution and the public `listResources()` method. The `resources/templates/list` handler had the same defect for dynamic resource template providers. ([#17610](https://github.com/mastra-ai/mastra/pull/17610))
40
+
41
+ Resource and resource template providers are now invoked on every request with the current caller's context, so each caller only sees their own resources. See https://github.com/mastra-ai/mastra/issues/17609
42
+
43
+ - Fixed flaky MCP server tests by replacing real weather API calls with deterministic mock tool ([#17572](https://github.com/mastra-ai/mastra/pull/17572))
44
+
45
+ - Updated dependencies [[`d468acb`](https://github.com/mastra-ai/mastra/commit/d468acb07aec1bb19a2cb0ada8042b05b46746b2), [`e9be4e7`](https://github.com/mastra-ai/mastra/commit/e9be4e747ec3d8b65548bff92f9377db06105376), [`d53cfc2`](https://github.com/mastra-ai/mastra/commit/d53cfc2c7f8d78343a4aa84ec4e129ba25f3325e), [`65799d4`](https://github.com/mastra-ai/mastra/commit/65799d4d549e5ebb9c848fbe3f51ac090f64becf), [`c268c89`](https://github.com/mastra-ai/mastra/commit/c268c89f4c63a93ee474d3cffdf3ea60bf00d4f2), [`d468acb`](https://github.com/mastra-ai/mastra/commit/d468acb07aec1bb19a2cb0ada8042b05b46746b2), [`0c72f03`](https://github.com/mastra-ai/mastra/commit/0c72f032abb13254df5a7856d64be2f207b8006d), [`3b45ea9`](https://github.com/mastra-ai/mastra/commit/3b45ea95015557a6cb9d70dc5252af54ab1b78ac), [`f084be1`](https://github.com/mastra-ai/mastra/commit/f084be1fcbe33ad7480913e44d6130c421c0976f)]:
46
+ - @mastra/core@1.42.0-alpha.0
47
+
3
48
  ## 1.9.1
4
49
 
5
50
  ### Patch Changes
@@ -3,7 +3,7 @@ name: mastra-mcp
3
3
  description: Documentation for @mastra/mcp. Use when working with @mastra/mcp APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/mcp"
6
- version: "1.9.1"
6
+ version: "1.10.0-alpha.1"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.9.1",
2
+ "version": "1.10.0-alpha.1",
3
3
  "package": "@mastra/mcp",
4
4
  "exports": {
5
5
  "UnauthorizedError": {
@@ -83,7 +83,7 @@ calculatorTool._meta = {
83
83
 
84
84
  ## Connecting MCP Apps to agents
85
85
 
86
- Agents consume tools — they do not need to know about MCP servers. Pass tools to the agent's `tools` config, and register the MCP server at the Mastra level so Studio can resolve app resources.
86
+ Agents consume tools — they don't need to know about MCP servers. Pass tools to the agent's `tools` config, and register the MCP server at the Mastra level so Studio can resolve app resources.
87
87
 
88
88
  ```typescript
89
89
  import { Agent } from '@mastra/core/agent'
@@ -158,8 +158,8 @@ Agent calls tool → Tool returns brief content + structuredContent
158
158
 
159
159
  Tools with app resources should return two fields:
160
160
 
161
- - `content`: A brief text summary for the model. Keep this short so the agent does not parrot the full result.
162
- - `structuredContent`: The data payload that hydrates the UI. The model does not see this field.
161
+ - `content`: A brief text summary for the model. Keep this short so the agent doesn't parrot the full result.
162
+ - `structuredContent`: The data payload that hydrates the UI. The model doesn't see this field.
163
163
 
164
164
  ```typescript
165
165
  execute: async ({ num1, num2, operation }) => {
@@ -294,7 +294,7 @@ App iframes are sandboxed with the following permissions:
294
294
  - `allow-forms`: Allows form submission
295
295
  - `allow-popups`: Permits `window.open()` and link targets
296
296
 
297
- The iframe does not have access to the parent page's DOM, cookies, or storage. All communication happens through the JSON-RPC postMessage protocol managed by `@mcp-ui/client`'s `AppRenderer` on the host side and `@modelcontextprotocol/ext-apps`'s `App` class on the guest side.
297
+ The iframe doesn't have access to the parent page's DOM, cookies, or storage. All communication happens through the JSON-RPC postMessage protocol managed by `@mcp-ui/client`'s `AppRenderer` on the host side and `@modelcontextprotocol/ext-apps`'s `App` class on the guest side.
298
298
 
299
299
  ## Related
300
300
 
@@ -53,7 +53,7 @@ When using `MastraFGAWorkos`, set `fetchMemberships: true` on `MastraAuthWorkos`
53
53
 
54
54
  Use `thread` as the resource-mapping key for memory authorization. `MastraFGAWorkos` still accepts the legacy alias `memory`, but new configs should prefer `thread`.
55
55
 
56
- When `server.fga` is configured, Mastra enforces FGA on protected actions. If a protected action has no authenticated user, Mastra denies it. If `server.fga` is not configured, these FGA checks are skipped and Mastra keeps the previous behavior.
56
+ When `server.fga` is configured, Mastra enforces FGA on protected actions. If a protected action has no authenticated user, Mastra denies it. If `server.fga` isn't configured, these FGA checks are skipped and Mastra keeps the previous behavior.
57
57
 
58
58
  ### Resource mapping
59
59
 
@@ -100,7 +100,7 @@ Use `validatePermissions()` to validate the full set of permissions Mastra may e
100
100
 
101
101
  ### Stored resource scoping
102
102
 
103
- FGA authorizes access to a resource. It does not automatically filter stored records that live in shared storage. Enable stored resource scoping when the built-in stored resource APIs are used in a multi-tenant app.
103
+ FGA authorizes access to a resource. It doesn't automatically filter stored records that live in shared storage. Enable stored resource scoping when the built-in stored resource APIs are used in a multi-tenant app.
104
104
 
105
105
  ```typescript
106
106
  const mastra = new Mastra({
@@ -136,7 +136,7 @@ If `requireScope` is `true` or omitted, scoped stored resource routes fail when
136
136
 
137
137
  Mastra includes route-level FGA metadata for built-in resource routes, including agents, workflows, tools, MCP tools, memory threads, responses, conversations, and stored resources. Stored resource route coverage includes `/stored/agents`, `/stored/mcp-clients`, `/stored/prompt-blocks`, `/stored/scorers`, `/stored/skills`, and `/stored/workspaces`. A route is checked when it has route-level `fga` metadata, when Mastra can derive built-in metadata for that route, or when the provider supplies metadata with `resolveRouteFGA()`.
138
138
 
139
- To deny protected routes that do not resolve FGA metadata, configure route policy coverage on the FGA provider:
139
+ To deny protected routes that don't resolve FGA metadata, configure route policy coverage on the FGA provider:
140
140
 
141
141
  ```typescript
142
142
  const fga = new MastraFGAWorkos({
@@ -202,20 +202,20 @@ const fga = new MastraFGAWorkos({
202
202
 
203
203
  When an FGA provider is configured, Mastra automatically checks authorization at these lifecycle points:
204
204
 
205
- | Lifecycle point | Permission checked | Resource type | Resource ID |
206
- | ---------------------------------------------------------------- | ----------------------------------------------- | -------------------- | ------------------------------------------------------------------- |
207
- | Agent execution (`generate`, `stream`) | `agents:execute` | `agent` | `agentId` |
208
- | Built-in workflow HTTP execution routes and `Workflow.execute()` | `workflows:execute` | `workflow` | `workflowId` |
209
- | Standalone tool execution | `tools:execute` | `tool` | `toolName` |
210
- | Agent tool execution | `tools:execute` | `tool` | `${agentId}:${toolName}` |
211
- | MCP tool execution | `tools:execute` | `tool` | `JSON.stringify([serverName, toolName])` |
212
- | Thread and memory access | `memory:read`, `memory:write`, `memory:delete` | `thread` | `threadId` |
213
- | Stored resource routes | Stored resource permission for the route action | Stored resource type | Route record ID, or the stored-resource scope for collection routes |
214
- | HTTP resource routes | Configured per route | Configured per route | Configured per route |
205
+ | Lifecycle point | Permission checked | Resource type | Resource ID |
206
+ | ---------------------------------------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
207
+ | Agent execution (`generate`, `stream`) | `agents:execute` | `agent` | `agentId` |
208
+ | Built-in workflow HTTP execution routes and `Workflow.execute()` | `workflows:execute` | `workflow` | `workflowId` |
209
+ | Standalone tool execution | `tools:execute` | `tool` | `toolName` |
210
+ | Agent tool execution | `tools:execute` | `tool` | `${agentId}:${toolName}` |
211
+ | MCP tool execution | `tools:execute` | `tool` by default, or the server-level `fga.resourceMapping` override | `JSON.stringify([serverName, toolName])` by default, or the server-level derived ID |
212
+ | Thread and memory access | `memory:read`, `memory:write`, `memory:delete` | `thread` | `threadId` |
213
+ | Stored resource routes | Stored resource permission for the route action | Stored resource type | Route record ID, or the stored-resource scope for collection routes |
214
+ | HTTP resource routes | Configured per route | Configured per route | Configured per route |
215
215
 
216
- For OAuth-protected MCP servers, HTTP MCP transports pass authenticated data as `extra.authInfo`. If an `MCPServer` is registered on an FGA-enabled Mastra instance, configure `mapAuthInfoToUser` so Mastra can set `requestContext.get('user')` before checking `tools/list` and `tools/call`. See [MCPServer authentication context](https://mastra.ai/reference/tools/mcp-server).
216
+ For OAuth-protected MCP servers, HTTP MCP transports pass authenticated data as `extra.authInfo`. If an `MCPServer` is registered on an FGA-enabled Mastra instance, configure `mapAuthInfoToUser` so Mastra can set `requestContext.get('user')` before checking `tools/list` and `tools/call`. Use the server-level `fga` option when MCP tool checks need different resource or permission mapping than internal agent and workflow tool checks. See [MCPServer authentication context](https://mastra.ai/reference/tools/mcp-server).
217
217
 
218
- Direct SDK calls to `createRun().start()`, `resume()`, or `restart()` are not independently checked by core FGA in this release. Make those calls from a protected route or guard them in application code. Pass a `requestContext` with an authenticated user when invoking protected entry points directly.
218
+ Direct SDK calls to `createRun().start()`, `resume()`, or `restart()` aren't independently checked by core FGA in this release. Make those calls from a protected route or guard them in application code. Pass a `requestContext` with an authenticated user when invoking protected entry points directly.
219
219
 
220
220
  Core agent, internal workflow, tool, and memory checks also pass `requestContext` and action metadata to the FGA provider. Route checks pass `requestContext`. Thread checks pass the owning `resourceId` when available.
221
221
 
@@ -153,7 +153,7 @@ const agent = new Agent({
153
153
  })
154
154
  ```
155
155
 
156
- When `forwardInstructions` is omitted (the default), instructions are still cached and can be inspected via [`getServerInstructions()`](#getserverinstructions), but are not added to any agent's system prompt.
156
+ When `forwardInstructions` is omitted (the default), instructions are still cached and can be inspected via [`getServerInstructions()`](#getserverinstructions), but aren't added to any agent's system prompt.
157
157
 
158
158
  > **Security note:** server instructions are forwarded verbatim (subject only to length truncation) into the agent's system prompt. A malicious or compromised MCP server can use them to inject instructions the agent will treat as trusted system guidance. Only enable `forwardInstructions` for servers you trust, and prefer reviewing instructions with `getServerInstructions()` before forwarding instructions from third-party servers.
159
159
 
@@ -179,7 +179,7 @@ const res = await agent.stream(prompt, {
179
179
 
180
180
  ### `getServerInstructions()`
181
181
 
182
- Returns the instructions currently known for each configured MCP server. Servers that have not connected yet, or do not advertise instructions, return `undefined`.
182
+ Returns the instructions currently known for each configured MCP server. Servers that haven't connected yet, or don't advertise instructions, return `undefined`.
183
183
 
184
184
  ```typescript
185
185
  getServerInstructions(): Record<string, string | undefined>
@@ -1065,9 +1065,9 @@ await agent.generate('Hello!', {
1065
1065
 
1066
1066
  ## Handling auth failures inside custom fetch
1067
1067
 
1068
- A custom `fetch` should not `throw` when authentication is unavailable. The Streamable HTTP transport in the MCP SDK opens a long-lived `GET /mcp` "standalone listener" stream in the background to receive server-pushed notifications. Errors on that stream are retried with exponential backoff, and a thrown `fetch` or a cleanly-closed stream can produce an indefinite reconnect loop at roughly one attempt per second.
1068
+ A custom `fetch` shouldn't `throw` when authentication is unavailable. The Streamable HTTP transport in the MCP SDK opens a long-lived `GET /mcp` "standalone listener" stream in the background to receive server-pushed notifications. Errors on that stream are retried with exponential backoff, and a thrown `fetch` or a cleanly-closed stream can produce an indefinite reconnect loop at roughly one attempt per second.
1069
1069
 
1070
- Return a synthetic `Response` instead. The [MCP Streamable HTTP specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports) defines `405 Method Not Allowed` as the signal a server returns when it does not offer the GET SSE stream, and the SDK honors it as a terminal status that stops the listener cleanly. Use this to disable the listener when your server does not push notifications.
1070
+ Return a synthetic `Response` instead. The [MCP Streamable HTTP specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports) defines `405 Method Not Allowed` as the signal a server returns when it doesn't offer the GET SSE stream, and the SDK honors it as a terminal status that stops the listener cleanly. Use this to disable the listener when your server doesn't push notifications.
1071
1071
 
1072
1072
  The following pattern waits for an auth token on POST requests, attaches it to outgoing headers, and short-circuits the GET listener with a synthetic 405:
1073
1073
 
@@ -1108,7 +1108,7 @@ const mcpClient = new MCPClient({
1108
1108
  })
1109
1109
  ```
1110
1110
 
1111
- Return `405` for the GET listener only when your server does not push notifications back to the client. If your server uses the standalone GET stream, attach the auth token on `GET` requests as well and let the request through.
1111
+ Return `405` for the GET listener only when your server doesn't push notifications back to the client. If your server uses the standalone GET stream, attach the auth token on `GET` requests as well and let the request through.
1112
1112
 
1113
1113
  ## Using SSE request headers
1114
1114
 
@@ -69,6 +69,8 @@ The constructor accepts an `MCPServerConfig` object with the following propertie
69
69
 
70
70
  **mapAuthInfoToUser** (`({ authInfo, extra, requestContext }) => unknown | null | undefined | Promise<unknown | null | undefined>`): Maps MCP transport auth data from \`extra.authInfo\` into the \`user\` value used by Mastra FGA checks. Use this when an OAuth-protected MCP server is registered on a Mastra instance with an FGA provider.
71
71
 
72
+ **fga** (`{ resourceMapping?: Partial<Record<'tool' | 'tools', { fgaResourceType: string; deriveId?: ({ user, resourceId, requestContext }) => string | undefined }>>; permissionMapping?: Record<string, string> }`): Overrides resource and permission mappings for this MCP server's \`tools/list\` and \`tools/call\` FGA checks. Use this when MCP authorization should be scoped differently from internal agent or workflow tool execution.
73
+
72
74
  **repository** (`Repository`): Optional repository information for the server's source code.
73
75
 
74
76
  **releaseDate** (`string`): Optional release date of this server version (ISO 8601 string). Defaults to the time of instantiation if not provided.
@@ -1129,6 +1131,49 @@ const server = new MCPServer({
1129
1131
  })
1130
1132
  ```
1131
1133
 
1134
+ ### Scope MCP tool FGA separately
1135
+
1136
+ Use `fga.resourceMapping` and `fga.permissionMapping` when MCP clients need a different authorization scope than internal agent or workflow tool execution. The override applies only to `tools/list` and `tools/call` checks for this MCP server.
1137
+
1138
+ ```typescript
1139
+ import { MastraFGAPermissions } from '@mastra/core/auth/ee'
1140
+
1141
+ const server = new MCPServer({
1142
+ id: 'my-server',
1143
+ name: 'My Server',
1144
+ version: '1.0.0',
1145
+ tools: { getUserData },
1146
+ mapAuthInfoToUser: ({ authInfo }) => {
1147
+ const user = authInfo as {
1148
+ extra?: {
1149
+ userId?: string
1150
+ organizationMembershipId?: string
1151
+ }
1152
+ }
1153
+
1154
+ if (!user.extra?.userId) {
1155
+ return null
1156
+ }
1157
+
1158
+ return {
1159
+ id: user.extra.userId,
1160
+ organizationMembershipId: user.extra.organizationMembershipId,
1161
+ }
1162
+ },
1163
+ fga: {
1164
+ resourceMapping: {
1165
+ tool: {
1166
+ fgaResourceType: 'user',
1167
+ deriveId: ({ user }) => (user as { id: string }).id,
1168
+ },
1169
+ },
1170
+ permissionMapping: {
1171
+ [MastraFGAPermissions.TOOLS_EXECUTE]: 'read',
1172
+ },
1173
+ },
1174
+ })
1175
+ ```
1176
+
1132
1177
  ### Setting Up Authentication Middleware
1133
1178
 
1134
1179
  To pass data to your tools, populate `req.auth` on the Node.js request object in your HTTP server middleware before calling `server.startHTTP()`.
package/dist/index.cjs CHANGED
@@ -2671,8 +2671,6 @@ var ServerResourceActions = class {
2671
2671
  getSubscriptions;
2672
2672
  getLogger;
2673
2673
  getSdkServer;
2674
- clearDefinedResources;
2675
- clearDefinedResourceTemplates;
2676
2674
  /**
2677
2675
  * @internal
2678
2676
  */
@@ -2680,8 +2678,6 @@ var ServerResourceActions = class {
2680
2678
  this.getSubscriptions = dependencies.getSubscriptions;
2681
2679
  this.getLogger = dependencies.getLogger;
2682
2680
  this.getSdkServer = dependencies.getSdkServer;
2683
- this.clearDefinedResources = dependencies.clearDefinedResources;
2684
- this.clearDefinedResourceTemplates = dependencies.clearDefinedResourceTemplates;
2685
2681
  }
2686
2682
  /**
2687
2683
  * Notifies subscribed clients that a specific resource has been updated.
@@ -2730,8 +2726,9 @@ var ServerResourceActions = class {
2730
2726
  /**
2731
2727
  * Notifies clients that the overall list of available resources has changed.
2732
2728
  *
2733
- * This clears the internal resource cache and sends a `notifications/resources/list_changed`
2734
- * message to all clients, prompting them to re-fetch the resource list.
2729
+ * This sends a `notifications/resources/list_changed` message to all clients, prompting
2730
+ * them to re-fetch the resource list. Resource lists and templates are always evaluated
2731
+ * per request, so there is no server-side cache to clear.
2735
2732
  *
2736
2733
  * @throws {MastraError} If sending the notification fails
2737
2734
  *
@@ -2742,11 +2739,7 @@ var ServerResourceActions = class {
2742
2739
  * ```
2743
2740
  */
2744
2741
  async notifyListChanged() {
2745
- this.getLogger().info(
2746
- "Resource list change externally notified. Clearing definedResources and sending notification."
2747
- );
2748
- this.clearDefinedResources();
2749
- this.clearDefinedResourceTemplates();
2742
+ this.getLogger().info("Resource list change externally notified. Sending notification.");
2750
2743
  try {
2751
2744
  await this.getSdkServer().sendResourceListChanged();
2752
2745
  } catch (error$1) {
@@ -2777,13 +2770,16 @@ var MCPServer = class extends mcp.MCPServerBase {
2777
2770
  streamableHTTPTransports = /* @__PURE__ */ new Map();
2778
2771
  // Track server instances for each HTTP session
2779
2772
  httpServerInstances = /* @__PURE__ */ new Map();
2780
- definedResources;
2781
- definedResourceTemplates;
2782
2773
  resourceOptions;
2774
+ // Whether any UI (`ui://`) app resources are configured. Captured at construction so
2775
+ // per-request server instances can advertise the MCP Apps extension without relying on
2776
+ // a shared, per-caller resource cache.
2777
+ hasUiResources = false;
2783
2778
  definedPrompts;
2784
2779
  promptOptions;
2785
2780
  jsonSchemaValidator;
2786
2781
  mapAuthInfoToUser;
2782
+ fga;
2787
2783
  subscriptions = /* @__PURE__ */ new Set();
2788
2784
  currentLoggingLevel;
2789
2785
  /**
@@ -2920,9 +2916,11 @@ var MCPServer = class extends mcp.MCPServerBase {
2920
2916
  constructor(opts) {
2921
2917
  super(opts);
2922
2918
  this.resourceOptions = this.mergeAppResources(opts.resources, opts.appResources);
2919
+ this.hasUiResources = !!opts.appResources && Object.keys(opts.appResources).length > 0;
2923
2920
  this.promptOptions = opts.prompts;
2924
2921
  this.jsonSchemaValidator = opts.jsonSchemaValidator;
2925
2922
  this.mapAuthInfoToUser = opts.mapAuthInfoToUser;
2923
+ this.fga = opts.fga;
2926
2924
  const capabilities = {
2927
2925
  tools: {},
2928
2926
  logging: { enabled: true }
@@ -2965,13 +2963,7 @@ var MCPServer = class extends mcp.MCPServerBase {
2965
2963
  this.resources = new ServerResourceActions({
2966
2964
  getSubscriptions: () => this.subscriptions,
2967
2965
  getLogger: () => this.logger,
2968
- getSdkServer: () => this.server,
2969
- clearDefinedResources: () => {
2970
- this.definedResources = void 0;
2971
- },
2972
- clearDefinedResourceTemplates: () => {
2973
- this.definedResourceTemplates = void 0;
2974
- }
2966
+ getSdkServer: () => this.server
2975
2967
  });
2976
2968
  this.prompts = new ServerPromptActions({
2977
2969
  getLogger: () => this.logger,
@@ -3130,14 +3122,11 @@ var MCPServer = class extends mcp.MCPServerBase {
3130
3122
  const hasUiTools = Object.values(this.convertedTools).some(
3131
3123
  (tool) => tool.mcp?._meta?.ui?.resourceUri
3132
3124
  );
3133
- if (hasUiTools || this.resourceOptions) {
3134
- const hasUiResources = this.definedResources?.some((r) => r.uri.startsWith("ui://"));
3135
- if (hasUiTools || hasUiResources) {
3136
- capabilities.extensions = {
3137
- ...capabilities.extensions,
3138
- "io.modelcontextprotocol/ui": {}
3139
- };
3140
- }
3125
+ if (hasUiTools || this.hasUiResources) {
3126
+ capabilities.extensions = {
3127
+ ...capabilities.extensions,
3128
+ "io.modelcontextprotocol/ui": {}
3129
+ };
3141
3130
  }
3142
3131
  const serverInstance = new index_js$1.Server(
3143
3132
  {
@@ -3354,18 +3343,13 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
3354
3343
  if (!capturedResourceOptions) return;
3355
3344
  if (capturedResourceOptions.listResources) {
3356
3345
  serverInstance.setRequestHandler(types_js.ListResourcesRequestSchema, async (_request, extra) => {
3357
- if (this.definedResources) {
3358
- return { resources: this.definedResources };
3359
- } else {
3360
- try {
3361
- const resources = await capturedResourceOptions.listResources({ extra });
3362
- this.definedResources = resources;
3363
- this.logger.debug("Fetched and cached resources", { count: this.definedResources.length });
3364
- return { resources: this.definedResources };
3365
- } catch (error) {
3366
- this.logger.error("Error fetching resources", { error });
3367
- throw error;
3368
- }
3346
+ try {
3347
+ const resources = await capturedResourceOptions.listResources({ extra });
3348
+ this.logger.debug("Fetched resources", { count: resources.length });
3349
+ return { resources };
3350
+ } catch (error) {
3351
+ this.logger.error("Error fetching resources", { error });
3352
+ throw error;
3369
3353
  }
3370
3354
  });
3371
3355
  }
@@ -3374,12 +3358,9 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
3374
3358
  const startTime = Date.now();
3375
3359
  const uri = request.params.uri;
3376
3360
  this.logger.debug("Handling ReadResource request", { uri });
3377
- if (!this.definedResources) {
3378
- const resources = await this.resourceOptions?.listResources?.({ extra });
3379
- if (!resources) throw new Error("Failed to load resources");
3380
- this.definedResources = resources;
3381
- }
3382
- const resource = this.definedResources?.find((r) => r.uri === uri);
3361
+ const resources = await capturedResourceOptions.listResources?.({ extra });
3362
+ if (!resources) throw new Error("Failed to load resources");
3363
+ const resource = resources.find((r) => r.uri === uri);
3383
3364
  if (!resource) {
3384
3365
  this.logger.warn("Unknown resource URI requested", { uri });
3385
3366
  throw new Error(`Resource not found: ${uri}`);
@@ -3419,18 +3400,13 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
3419
3400
  }
3420
3401
  if (capturedResourceOptions.resourceTemplates) {
3421
3402
  serverInstance.setRequestHandler(types_js.ListResourceTemplatesRequestSchema, async (_request, extra) => {
3422
- if (this.definedResourceTemplates) {
3423
- return { resourceTemplates: this.definedResourceTemplates };
3424
- } else {
3425
- try {
3426
- const templates = await capturedResourceOptions.resourceTemplates({ extra });
3427
- this.definedResourceTemplates = templates;
3428
- this.logger.debug("Fetched and cached resource templates", { count: this.definedResourceTemplates.length });
3429
- return { resourceTemplates: this.definedResourceTemplates };
3430
- } catch (error) {
3431
- this.logger.error("Error fetching resource templates via resourceTemplates():", { error });
3432
- throw error;
3433
- }
3403
+ try {
3404
+ const templates = await capturedResourceOptions.resourceTemplates({ extra });
3405
+ this.logger.debug("Fetched resource templates", { count: templates.length });
3406
+ return { resourceTemplates: templates };
3407
+ } catch (error) {
3408
+ this.logger.error("Error fetching resource templates via resourceTemplates():", { error });
3409
+ throw error;
3434
3410
  }
3435
3411
  });
3436
3412
  }
@@ -4592,11 +4568,17 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
4592
4568
  if (!user) {
4593
4569
  throw new FGADeniedError({ id: "unknown" }, { type: "tool", id: resourceId }, MastraFGAPermissions.TOOLS_EXECUTE);
4594
4570
  }
4571
+ const { resource, permission } = this.resolveToolFGAParams({
4572
+ user,
4573
+ resourceId,
4574
+ requestContext,
4575
+ permission: MastraFGAPermissions.TOOLS_EXECUTE
4576
+ });
4595
4577
  await requireFGA({
4596
4578
  fgaProvider,
4597
4579
  user,
4598
- resource: { type: "tool", id: resourceId },
4599
- permission: MastraFGAPermissions.TOOLS_EXECUTE,
4580
+ resource,
4581
+ permission,
4600
4582
  requestContext,
4601
4583
  context: {
4602
4584
  resourceId
@@ -4608,6 +4590,28 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
4608
4590
  }
4609
4591
  });
4610
4592
  }
4593
+ resolveToolFGAParams({
4594
+ user,
4595
+ resourceId,
4596
+ requestContext,
4597
+ permission
4598
+ }) {
4599
+ const mappedPermission = this.fga?.permissionMapping?.[permission] ?? permission;
4600
+ const resourceMapping = this.fga?.resourceMapping?.tool ?? this.fga?.resourceMapping?.tools;
4601
+ if (!resourceMapping) {
4602
+ return {
4603
+ resource: { type: "tool", id: resourceId },
4604
+ permission: mappedPermission
4605
+ };
4606
+ }
4607
+ return {
4608
+ resource: {
4609
+ type: resourceMapping.fgaResourceType,
4610
+ id: resourceMapping.deriveId?.({ user, resourceId, requestContext }) ?? resourceId
4611
+ },
4612
+ permission: mappedPermission
4613
+ };
4614
+ }
4611
4615
  /**
4612
4616
  * Executes a specific tool provided by this MCP server.
4613
4617
  *
@@ -4754,11 +4758,7 @@ Provided arguments: ${JSON.stringify(args, null, 2)}`,
4754
4758
  return { resources: [] };
4755
4759
  }
4756
4760
  const extra = {};
4757
- if (this.definedResources) {
4758
- return { resources: this.definedResources };
4759
- }
4760
4761
  const resources = await this.resourceOptions.listResources({ extra });
4761
- this.definedResources = resources;
4762
4762
  return { resources };
4763
4763
  }
4764
4764
  };