@mastra/mcp 1.9.0-alpha.0 → 1.9.0
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 +79 -0
- package/dist/docs/SKILL.md +2 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-mcp-mcp-apps.md +3 -3
- package/dist/docs/references/docs-mcp-overview.md +1 -1
- package/dist/docs/references/docs-server-auth-fga.md +258 -0
- package/dist/docs/references/reference-tools-mcp-client.md +2 -2
- package/dist/docs/references/reference-tools-mcp-server.md +33 -1
- package/dist/index.cjs +50 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +50 -15
- package/dist/index.js.map +1 -1
- package/dist/server/server.d.ts +3 -0
- package/dist/server/server.d.ts.map +1 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,84 @@
|
|
|
1
1
|
# @mastra/mcp
|
|
2
2
|
|
|
3
|
+
## 1.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Added opt-in MCP server instructions forwarding into agent system prompts. ([#17155](https://github.com/mastra-ai/mastra/pull/17155))
|
|
8
|
+
|
|
9
|
+
When an MCP server advertises instructions during initialization, you can now forward that guidance into the system prompt of agents that use the server's tools. This is **opt-in** — set `forwardInstructions: true` per server to enable it. Forwarded instructions are injected into the agent's system prompt, so only enable this for servers you trust.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
const mcp = new MCPClient({
|
|
13
|
+
servers: {
|
|
14
|
+
db: {
|
|
15
|
+
url: new URL('http://localhost:4111/mcp'),
|
|
16
|
+
forwardInstructions: true, // opt in; defaults to false
|
|
17
|
+
instructionsMaxLength: 512, // max chars forwarded per server
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const agent = new Agent({
|
|
23
|
+
id: 'db-agent',
|
|
24
|
+
name: 'DB Agent',
|
|
25
|
+
instructions: 'Help with database changes.',
|
|
26
|
+
model,
|
|
27
|
+
tools: await mcp.listTools(),
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
You can always inspect cached instructions without forwarding them:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const instructions = mcp.getServerInstructions();
|
|
35
|
+
// => { db: 'Always validate before migrating.', other: undefined }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- Added native multimodal tool-result support. Core now converts MCP-style tool results with image and audio `content` parts into model-native media output when building model prompts, without requiring MCP tools to persist duplicate media payloads in `providerMetadata.mastra.modelOutput`. ([#16866](https://github.com/mastra-ai/mastra/pull/16866))
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
return {
|
|
42
|
+
content: [
|
|
43
|
+
{ type: 'text', text: 'Screenshot captured' },
|
|
44
|
+
{ type: 'image', data: base64Png, mimeType: 'image/png' },
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Patch Changes
|
|
50
|
+
|
|
51
|
+
- Support conditional, function-based tool approvals. ([#17337](https://github.com/mastra-ai/mastra/pull/17337))
|
|
52
|
+
- MCP tools that wrap a server-level `requireToolApproval` function are now honored end-to-end. The per-tool approval function was previously dropped when the agent converted MCP tools (it kept only the boolean flag), so conditional approval silently fell back to always-on. `CoreToolBuilder` now preserves a `needsApprovalFn` attached directly to a tool instance.
|
|
53
|
+
- The global `requireToolApproval` option on `agent.stream`/`agent.generate` now accepts a function in addition to a boolean. It is evaluated per tool call with the tool name, arguments, and request context, enabling policies such as regex allowlists on tool names. Returning `true` requires approval for that call; `false` allows it. On error the call defaults to requiring approval. When a function policy is set, tool calls run sequentially so approval suspensions don't race. Durable agents and stored agents continue to accept only a boolean (a function degrades to requiring approval for every call, since their options must be serializable).
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
// Approve only tool calls whose name is not on an allowlist.
|
|
57
|
+
const allowlist = /^(get|list|search)_/;
|
|
58
|
+
await agent.generate('...', {
|
|
59
|
+
requireToolApproval: ({ toolName }) => !allowlist.test(toolName),
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- Precedence is unchanged from before: a per-tool approval function (`createTool({ requireApproval: fn })` or an MCP-derived `needsApprovalFn`) is authoritative for that tool and overrides the global setting, so a tool can still opt out of approval by returning `false` even when the global option is on. The only new behavior is that the global option may now be a function in addition to a boolean.
|
|
64
|
+
- The previously implicit, runtime-attached per-tool approval predicate is now a typed contract: `@mastra/core` exports `NeedsApprovalFn` and declares the optional `needsApprovalFn` property on the `Tool` class. The MCP client and the agent runtime now share this typed contract instead of reaching through `any`. This is additive — no public API changes.
|
|
65
|
+
|
|
66
|
+
- Close the stale MCP transport before reconnecting so SSE connections no longer leak orphaned EventSource instances and accumulate server-side sessions on implicit reconnect. ([#17326](https://github.com/mastra-ai/mastra/pull/17326))
|
|
67
|
+
|
|
68
|
+
- Fixed FGA-enabled MCP servers so OAuth authInfo can be mapped to a Mastra user before tools/list and tools/call authorization. ([#17475](https://github.com/mastra-ai/mastra/pull/17475))
|
|
69
|
+
|
|
70
|
+
- Updated dependencies [[`fa63872`](https://github.com/mastra-ai/mastra/commit/fa6387280954e6b667bec5714b55ba082bc627ff), [`d779de3`](https://github.com/mastra-ai/mastra/commit/d779de3cd9d2e7ed8110547190e2f15e786a0e41), [`1750c97`](https://github.com/mastra-ai/mastra/commit/1750c975d6179fbf6db2813b15229d4f8f23fc55), [`9283971`](https://github.com/mastra-ai/mastra/commit/928397157009b4aef4d5fdf3a0a273cb371beb55), [`f07b646`](https://github.com/mastra-ai/mastra/commit/f07b64604ab7d25391179790b7fd4823df9e2dff), [`d8838ae`](https://github.com/mastra-ai/mastra/commit/d8838ae80b69780361693d27098f7f6684af12fe), [`40f9297`](https://github.com/mastra-ai/mastra/commit/40f9297003b921c62373d3e8d3a4bda76c9f6de3), [`19a8658`](https://github.com/mastra-ai/mastra/commit/19a86589c788ef48bb6c1b0612cc82a201857379), [`850af77`](https://github.com/mastra-ai/mastra/commit/850af7779cb87c350804488734544a5b1843de25), [`0f0d1ba`](https://github.com/mastra-ai/mastra/commit/0f0d1ba67bfcb2204e571401662f1eceefc03357), [`a18775a`](https://github.com/mastra-ai/mastra/commit/a18775a693172546ee2378d39b67d4e32895b251), [`1baf2d1`](https://github.com/mastra-ai/mastra/commit/1baf2d152c6881338ff8f114633d5316fe13dd15), [`8c31bcd`](https://github.com/mastra-ai/mastra/commit/8c31bcdb00e597880d5939b1b7d7566fbe5dacae), [`0e32507`](https://github.com/mastra-ai/mastra/commit/0e32507962cdfa5569b7bda5bc6fb3dd34e40b03), [`95b14cd`](https://github.com/mastra-ai/mastra/commit/95b14cdd820e86d97ac05fe568424c513a252e31), [`07c3de7`](https://github.com/mastra-ai/mastra/commit/07c3de7f7bc418beccaea3b5e6b7f7cdda79d492), [`0bf2d93`](https://github.com/mastra-ai/mastra/commit/0bf2d932d20e2936f2d9abb8c0a86e24fbc97ec6), [`7b0d34c`](https://github.com/mastra-ai/mastra/commit/7b0d34cfe4a2fce22ac86ae17404685ff67a2ddb), [`a659a77`](https://github.com/mastra-ai/mastra/commit/a659a779bdebe3a52a518c56d2260592d0240fe0), [`aa36be2`](https://github.com/mastra-ai/mastra/commit/aa36be23aa513b7dc53cb8ca16b7fab8f20e43ad), [`3332be9`](https://github.com/mastra-ai/mastra/commit/3332be9701ecd77aba840959d9a1d1ce7aef02d3), [`212c635`](https://github.com/mastra-ai/mastra/commit/212c635203e61d036ab41db8ff86c3893dc795b3), [`d8838ae`](https://github.com/mastra-ai/mastra/commit/d8838ae80b69780361693d27098f7f6684af12fe), [`9aa5a73`](https://github.com/mastra-ai/mastra/commit/9aa5a73e7e110f6e9365eec69364a33d5f03bb56), [`f73c789`](https://github.com/mastra-ai/mastra/commit/f73c789e8ef21561580395d2c410119cab5848c8), [`8bd16da`](https://github.com/mastra-ai/mastra/commit/8bd16da73a4cb874d739373643dbd6a6e7f88684), [`c8630f8`](https://github.com/mastra-ai/mastra/commit/c8630f80d4f40cb5d22e60ab162b618b1907167a), [`94dfef6`](https://github.com/mastra-ai/mastra/commit/94dfef6e2bf19a88467ea3940afcbce88a433f0f), [`47f71dc`](https://github.com/mastra-ai/mastra/commit/47f71dc6fbcbd12d71e21a979e676e20a02bd77d), [`50ceae2`](https://github.com/mastra-ai/mastra/commit/50ceae270878e2f8fb2b2c6c2faab09df0007c8a), [`a122f79`](https://github.com/mastra-ai/mastra/commit/a122f79427ae225ec79c7b2ed46278da48d04b17), [`8cdde58`](https://github.com/mastra-ai/mastra/commit/8cdde5875bbba6702d9df226f2b20232b8d75d6c), [`3a081c1`](https://github.com/mastra-ai/mastra/commit/3a081c1255c5ae8c99f6dad91cc612934ef6f2bd), [`49f8abc`](https://github.com/mastra-ai/mastra/commit/49f8abce8258e4f2f87bd326acfbdb641264a47c), [`847ff1e`](https://github.com/mastra-ai/mastra/commit/847ff1e0d94368d94b2e173e4e0908e115568ef3), [`0c1ed1d`](https://github.com/mastra-ai/mastra/commit/0c1ed1d00c7d87b5ac99ca95896211a2fa9189fa), [`259d409`](https://github.com/mastra-ai/mastra/commit/259d409a514174299dbde1ff5e1121209b3ba850), [`9e16c68`](https://github.com/mastra-ai/mastra/commit/9e16c6818b6485ccb43df28aba6f3a2219d28662), [`cefca33`](https://github.com/mastra-ai/mastra/commit/cefca33ae666e69810c935fedf95a929c173d1d7), [`d00e8c5`](https://github.com/mastra-ai/mastra/commit/d00e8c50daebe5bce5bf2f48bde39c86fc3d2fe4), [`36fa7e2`](https://github.com/mastra-ai/mastra/commit/36fa7e24d14e58a1eb46147097b32f583e5b8775), [`87e9774`](https://github.com/mastra-ai/mastra/commit/87e97741c1e493cd6d62f478eb810b49bda4d57c), [`65a72e7`](https://github.com/mastra-ai/mastra/commit/65a72e70c25eedea8ff985a6624b96be2850236b), [`fe9eacd`](https://github.com/mastra-ai/mastra/commit/fe9eacd9545a0a9d64aad31c9fa90294a425289e), [`4c02027`](https://github.com/mastra-ai/mastra/commit/4c020277235eaa6b1dc957c90ad0639eef213992), [`0f77241`](https://github.com/mastra-ai/mastra/commit/0f7724108806703799a8ba80ad0f09414afd5066), [`849efb9`](https://github.com/mastra-ai/mastra/commit/849efb9fca6dc976589c1f90a303fea618769109), [`92ff509`](https://github.com/mastra-ai/mastra/commit/92ff5098ef8a990438ca038077021a5f7541ec1d), [`3fce5e7`](https://github.com/mastra-ai/mastra/commit/3fce5e70d011d289043e75003ef3336ed4aa43c3), [`a763592`](https://github.com/mastra-ai/mastra/commit/a763592c3db46963ef1011cfe16fe372816e775e), [`db79c86`](https://github.com/mastra-ai/mastra/commit/db79c86c60723d57e02f9636ca2611bd4515f194), [`6855012`](https://github.com/mastra-ai/mastra/commit/685501247cc4717506f3e89beed03509d63a5370), [`80c7737`](https://github.com/mastra-ai/mastra/commit/80c7737e32d7917b5f356957d67c169d01744fd3), [`7fef31c`](https://github.com/mastra-ai/mastra/commit/7fef31c0d2a6d362a43a647a8a4f6ab893758a23), [`7fef31c`](https://github.com/mastra-ai/mastra/commit/7fef31c0d2a6d362a43a647a8a4f6ab893758a23), [`3f1cf47`](https://github.com/mastra-ai/mastra/commit/3f1cf476f74c1e4cc2df908837e05853a5347e31)]:
|
|
71
|
+
- @mastra/core@1.38.0
|
|
72
|
+
|
|
73
|
+
## 1.9.0-alpha.1
|
|
74
|
+
|
|
75
|
+
### Patch Changes
|
|
76
|
+
|
|
77
|
+
- Fixed FGA-enabled MCP servers so OAuth authInfo can be mapped to a Mastra user before tools/list and tools/call authorization. ([#17475](https://github.com/mastra-ai/mastra/pull/17475))
|
|
78
|
+
|
|
79
|
+
- Updated dependencies [[`0c1ed1d`](https://github.com/mastra-ai/mastra/commit/0c1ed1d00c7d87b5ac99ca95896211a2fa9189fa), [`849efb9`](https://github.com/mastra-ai/mastra/commit/849efb9fca6dc976589c1f90a303fea618769109)]:
|
|
80
|
+
- @mastra/core@1.38.0-alpha.8
|
|
81
|
+
|
|
3
82
|
## 1.9.0-alpha.0
|
|
4
83
|
|
|
5
84
|
### Minor Changes
|
package/dist/docs/SKILL.md
CHANGED
|
@@ -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.0
|
|
6
|
+
version: "1.9.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## When to use
|
|
@@ -18,6 +18,7 @@ Read the individual reference documents for detailed explanations and code examp
|
|
|
18
18
|
|
|
19
19
|
- [MCP Apps](references/docs-mcp-mcp-apps.md) - Serve interactive HTML UIs from MCP tools using the MCP Apps extension.
|
|
20
20
|
- [MCP overview](references/docs-mcp-overview.md) - Learn about the Model Context Protocol (MCP), how to use third-party tools via MCPClient, connect to registries, and share your own tools using MCPServer.
|
|
21
|
+
- [Fine-Grained Authorization (FGA)](references/docs-server-auth-fga.md) - Add resource-level authorization to your Mastra application with FGA providers.
|
|
21
22
|
|
|
22
23
|
### Reference
|
|
23
24
|
|
|
@@ -93,7 +93,7 @@ export const myAgent = new Agent({
|
|
|
93
93
|
id: 'my-agent',
|
|
94
94
|
name: 'My Agent',
|
|
95
95
|
instructions: 'You have access to interactive UI tools.',
|
|
96
|
-
model: '
|
|
96
|
+
model: 'openai/gpt-5-mini',
|
|
97
97
|
tools: { calculatorTool },
|
|
98
98
|
})
|
|
99
99
|
```
|
|
@@ -125,7 +125,7 @@ const mcpClient = new MCPClient({
|
|
|
125
125
|
const myAgent = new Agent({
|
|
126
126
|
id: 'my-agent',
|
|
127
127
|
name: 'My Agent',
|
|
128
|
-
model: '
|
|
128
|
+
model: 'openai/gpt-5-mini',
|
|
129
129
|
tools: await mcpClient.listTools(),
|
|
130
130
|
})
|
|
131
131
|
|
|
@@ -270,7 +270,7 @@ const mcpClient = new MCPClient({
|
|
|
270
270
|
const myAgent = new Agent({
|
|
271
271
|
id: 'my-agent',
|
|
272
272
|
name: 'My Agent',
|
|
273
|
-
model: '
|
|
273
|
+
model: 'openai/gpt-5-mini',
|
|
274
274
|
tools: await mcpClient.listTools(),
|
|
275
275
|
})
|
|
276
276
|
|
|
@@ -82,7 +82,7 @@ export const testAgent = new Agent({
|
|
|
82
82
|
- US National Weather Service
|
|
83
83
|
|
|
84
84
|
Answer questions using the information you find using the MCP Servers.`,
|
|
85
|
-
model: 'openai/gpt-5.
|
|
85
|
+
model: 'openai/gpt-5.5',
|
|
86
86
|
tools: await testMcpClient.listTools(),
|
|
87
87
|
})
|
|
88
88
|
```
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# Fine-Grained Authorization (FGA)
|
|
2
|
+
|
|
3
|
+
> **Note:** Fine-Grained Authorization is part of the Mastra Enterprise Edition. Production deployments require a valid EE license. [Contact sales](https://mastra.ai/contact) for more information.
|
|
4
|
+
|
|
5
|
+
Fine-Grained Authorization (FGA) adds resource-level permission checks to your Mastra application. While RBAC answers "can this role do this action?", FGA answers **"can this user do this action on this specific resource?"**
|
|
6
|
+
|
|
7
|
+
## When to use FGA
|
|
8
|
+
|
|
9
|
+
FGA is designed for multi-tenant B2B products where permissions are contextual:
|
|
10
|
+
|
|
11
|
+
- A user might be an **admin** of Team A but only a **member** of Team B
|
|
12
|
+
- Thread access should be limited to the user's own organization
|
|
13
|
+
- Workflow execution should be scoped to a specific team or project
|
|
14
|
+
- Tool access depends on the user's relationship to a resource
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
Configure FGA in your Mastra server config alongside authentication and RBAC:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { Mastra } from '@mastra/core/mastra';
|
|
22
|
+
import { MastraFGAPermissions } from '@mastra/core/auth/ee';
|
|
23
|
+
import { MastraAuthWorkos, MastraFGAWorkos } from '@mastra/auth-workos';
|
|
24
|
+
|
|
25
|
+
const mastra = new Mastra({
|
|
26
|
+
server: {
|
|
27
|
+
auth: new MastraAuthWorkos({
|
|
28
|
+
/* ... */
|
|
29
|
+
fetchMemberships: true,
|
|
30
|
+
mapUserToResourceId: user => user.teamId,
|
|
31
|
+
}),
|
|
32
|
+
fga: new MastraFGAWorkos({
|
|
33
|
+
resourceMapping: {
|
|
34
|
+
agent: { fgaResourceType: 'team', deriveId: (ctx) => ctx.user.teamId },
|
|
35
|
+
workflow: { fgaResourceType: 'team', deriveId: (ctx) => ctx.user.teamId },
|
|
36
|
+
thread: { fgaResourceType: 'workspace-thread', deriveId: ({ resourceId }) => resourceId },
|
|
37
|
+
},
|
|
38
|
+
permissionMapping: {
|
|
39
|
+
[MastraFGAPermissions.AGENTS_EXECUTE]: 'manage-workflows',
|
|
40
|
+
[MastraFGAPermissions.WORKFLOWS_EXECUTE]: 'manage-workflows',
|
|
41
|
+
[MastraFGAPermissions.MEMORY_READ]: 'read',
|
|
42
|
+
[MastraFGAPermissions.MEMORY_WRITE]: 'update',
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
45
|
+
storedResources: {
|
|
46
|
+
scope: true,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
When using `MastraFGAWorkos`, set `fetchMemberships: true` on `MastraAuthWorkos`. WorkOS FGA checks need the user's organization memberships to resolve the correct membership ID for authorization.
|
|
53
|
+
|
|
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
|
+
|
|
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.
|
|
57
|
+
|
|
58
|
+
### Resource mapping
|
|
59
|
+
|
|
60
|
+
The `resourceMapping` tells Mastra how to resolve FGA resource types and IDs from request context. Keys are Mastra resource types, values define the FGA resource type and how to derive the ID:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
resourceMapping: {
|
|
64
|
+
// When checking "can user execute agent X?", resolve the FGA resource
|
|
65
|
+
// as the user's team (type: 'team', id: user.teamId)
|
|
66
|
+
agent: {
|
|
67
|
+
fgaResourceType: 'team',
|
|
68
|
+
deriveId: (ctx) => ctx.user.teamId,
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`deriveId()` receives:
|
|
74
|
+
|
|
75
|
+
- `user` — the authenticated user
|
|
76
|
+
- `resourceId` — the owning Mastra resource ID when available (for example, a thread's `resourceId`)
|
|
77
|
+
- `requestContext` — the current request context for advanced tenant resolution
|
|
78
|
+
- `metadata` — provider-specific metadata for the attempted action
|
|
79
|
+
|
|
80
|
+
Return `undefined` from `deriveId()` to fall back to the original Mastra resource ID.
|
|
81
|
+
|
|
82
|
+
For thread and memory checks, Mastra still passes the raw `threadId` as the resource being checked, but it also forwards the thread's owning `resourceId` into `deriveId()`. This lets you map thread permissions to composite tenant IDs such as `userId-teamId-orgId`.
|
|
83
|
+
|
|
84
|
+
### Permission mapping
|
|
85
|
+
|
|
86
|
+
The `permissionMapping` translates Mastra's internal permission strings to your FGA provider's permission slugs:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { MastraFGAPermissions } from '@mastra/core/auth/ee';
|
|
90
|
+
|
|
91
|
+
permissionMapping: {
|
|
92
|
+
[MastraFGAPermissions.AGENTS_EXECUTE]: 'manage-workflows', // Mastra permission -> WorkOS permission slug
|
|
93
|
+
[MastraFGAPermissions.MEMORY_READ]: 'read',
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
If no mapping exists for a permission, the original string is passed through.
|
|
98
|
+
|
|
99
|
+
Use `validatePermissions()` to validate the full set of permissions Mastra may emit at startup. Use this when a provider requires every Mastra permission to have an explicit provider permission slug.
|
|
100
|
+
|
|
101
|
+
### Stored resource scoping
|
|
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.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
const mastra = new Mastra({
|
|
107
|
+
server: {
|
|
108
|
+
auth: new MastraAuthWorkos({
|
|
109
|
+
/* ... */
|
|
110
|
+
mapUserToResourceId: user => user.teamId,
|
|
111
|
+
}),
|
|
112
|
+
storedResources: {
|
|
113
|
+
scope: true,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
With `scope: true`, Mastra reads `MASTRA_RESOURCE_ID_KEY` from the request context. `mapUserToResourceId()` sets this value after authentication. Stored resource handlers persist the scope in record metadata and filter list, read, update, publish, and delete operations by that scope.
|
|
120
|
+
|
|
121
|
+
Use an object when the scope needs custom request logic:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
storedResources: {
|
|
125
|
+
scope: {
|
|
126
|
+
metadataKey: 'teamId',
|
|
127
|
+
resolve: ({ user }) => user.teamId,
|
|
128
|
+
requireScope: true,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
If `requireScope` is `true` or omitted, scoped stored resource routes fail when no scope can be resolved.
|
|
134
|
+
|
|
135
|
+
### Route policy coverage
|
|
136
|
+
|
|
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
|
+
|
|
139
|
+
To deny protected routes that do not resolve FGA metadata, configure route policy coverage on the FGA provider:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
const fga = new MastraFGAWorkos({
|
|
143
|
+
resourceMapping: {
|
|
144
|
+
project: { fgaResourceType: 'project' },
|
|
145
|
+
},
|
|
146
|
+
permissionMapping: {
|
|
147
|
+
'projects:read': 'read',
|
|
148
|
+
},
|
|
149
|
+
requireForProtectedRoutes: true,
|
|
150
|
+
auditProtectedRoutes: 'warn',
|
|
151
|
+
validatePermissions: async permissions => {
|
|
152
|
+
// Throw if a Mastra permission is missing from permissionMapping.
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Set `auditProtectedRoutes: 'error'` to fail startup when protected routes are missing built-in FGA metadata. If `requireForProtectedRoutes` is enabled, Mastra logs this audit as a warning by default.
|
|
158
|
+
|
|
159
|
+
For custom routes, prefer route-level `fga` metadata. This keeps authorization policy next to the route:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { createRoute } from '@mastra/server/server-adapter';
|
|
163
|
+
|
|
164
|
+
export const getProjectRoute = createRoute({
|
|
165
|
+
method: 'GET',
|
|
166
|
+
path: '/projects/:projectId',
|
|
167
|
+
responseType: 'json',
|
|
168
|
+
requiresAuth: true,
|
|
169
|
+
fga: {
|
|
170
|
+
resourceType: 'project',
|
|
171
|
+
resourceIdParam: 'projectId',
|
|
172
|
+
permission: 'projects:read',
|
|
173
|
+
},
|
|
174
|
+
handler: async () => {
|
|
175
|
+
return { project: null };
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Use `resolveRouteFGA()` only when route metadata must be derived centrally from route, params, or request context. A route map scales better than string-prefix checks:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import type { FGARouteConfig, FGARouteResolver } from '@mastra/core/auth/ee';
|
|
184
|
+
|
|
185
|
+
const routeFGA = {
|
|
186
|
+
'GET /billing/:accountId': {
|
|
187
|
+
resourceType: 'account',
|
|
188
|
+
resourceIdParam: 'accountId',
|
|
189
|
+
permission: 'billing:read',
|
|
190
|
+
},
|
|
191
|
+
} satisfies Record<string, FGARouteConfig>;
|
|
192
|
+
|
|
193
|
+
const resolveRouteFGA: FGARouteResolver = ({ route }) => routeFGA[`${route.method} ${route.path}`];
|
|
194
|
+
|
|
195
|
+
const fga = new MastraFGAWorkos({
|
|
196
|
+
/* ... */
|
|
197
|
+
resolveRouteFGA,
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Enforcement points
|
|
202
|
+
|
|
203
|
+
When an FGA provider is configured, Mastra automatically checks authorization at these lifecycle points:
|
|
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 |
|
|
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).
|
|
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.
|
|
219
|
+
|
|
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
|
+
|
|
222
|
+
## Custom FGA provider
|
|
223
|
+
|
|
224
|
+
Implement `IFGAProvider` to use any FGA backend:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { FGADeniedError } from '@mastra/core/auth/ee'
|
|
228
|
+
import type { FGACheckParams, IFGAProvider, MastraFGAPermissionInput } from '@mastra/core/auth/ee'
|
|
229
|
+
|
|
230
|
+
class MyFGAProvider implements IFGAProvider {
|
|
231
|
+
async check(user: any, params: FGACheckParams): Promise<boolean> {
|
|
232
|
+
// Your authorization logic
|
|
233
|
+
return true
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async require(user: any, params: FGACheckParams): Promise<void> {
|
|
237
|
+
const allowed = await this.check(user, params)
|
|
238
|
+
if (!allowed) {
|
|
239
|
+
throw new FGADeniedError(user, params.resource, params.permission)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async filterAccessible<T extends { id: string }>(
|
|
244
|
+
user: any,
|
|
245
|
+
resources: T[],
|
|
246
|
+
resourceType: string,
|
|
247
|
+
permission: MastraFGAPermissionInput,
|
|
248
|
+
): Promise<T[]> {
|
|
249
|
+
// Filter resources the user can access
|
|
250
|
+
return resources
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Related
|
|
256
|
+
|
|
257
|
+
- [Authentication overview](https://mastra.ai/docs/server/auth)
|
|
258
|
+
- [WorkOS authentication](https://mastra.ai/docs/server/auth/workos)
|
|
@@ -890,7 +890,7 @@ const agent = new Agent({
|
|
|
890
890
|
id: 'multi-tool-agent',
|
|
891
891
|
name: 'Multi-tool Agent',
|
|
892
892
|
instructions: 'You have access to multiple tool servers.',
|
|
893
|
-
model: 'openai/gpt-5.
|
|
893
|
+
model: 'openai/gpt-5.5',
|
|
894
894
|
tools: await mcp.listTools(),
|
|
895
895
|
})
|
|
896
896
|
|
|
@@ -941,7 +941,7 @@ const agent = new Agent({
|
|
|
941
941
|
id: 'multi-tool-agent',
|
|
942
942
|
name: 'Multi-tool Agent',
|
|
943
943
|
instructions: 'You help users check stocks and weather.',
|
|
944
|
-
model: 'openai/gpt-5.
|
|
944
|
+
model: 'openai/gpt-5.5',
|
|
945
945
|
})
|
|
946
946
|
|
|
947
947
|
// Later, configure MCP with user-specific settings
|
|
@@ -22,7 +22,7 @@ const myAgent = new Agent({
|
|
|
22
22
|
name: 'MyExampleAgent',
|
|
23
23
|
description: 'A generalist to help with basic questions.',
|
|
24
24
|
instructions: 'You are a helpful assistant.',
|
|
25
|
-
model: 'openai/gpt-5.
|
|
25
|
+
model: 'openai/gpt-5.5',
|
|
26
26
|
})
|
|
27
27
|
|
|
28
28
|
const weatherTool = createTool({
|
|
@@ -67,6 +67,8 @@ The constructor accepts an `MCPServerConfig` object with the following propertie
|
|
|
67
67
|
|
|
68
68
|
**instructions** (`string`): Optional instructions describing how to use the server and its features.
|
|
69
69
|
|
|
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
|
+
|
|
70
72
|
**repository** (`Repository`): Optional repository information for the server's source code.
|
|
71
73
|
|
|
72
74
|
**releaseDate** (`string`): Optional release date of this server version (ISO 8601 string). Defaults to the time of instantiation if not provided.
|
|
@@ -1097,6 +1099,36 @@ Whatever you set on `req.auth` in your HTTP middleware becomes available as `con
|
|
|
1097
1099
|
req.auth = { ... } → context?.mcp?.extra?.authInfo.extra = { ... }
|
|
1098
1100
|
```
|
|
1099
1101
|
|
|
1102
|
+
### Map auth data for FGA
|
|
1103
|
+
|
|
1104
|
+
When an `MCPServer` is registered on a Mastra instance with a fine-grained authorization (FGA) provider, Mastra checks `requestContext.get('user')` before listing or calling tools. HTTP MCP transports pass authenticated data as `extra.authInfo`, so use `mapAuthInfoToUser` to set the user shape expected by your FGA provider.
|
|
1105
|
+
|
|
1106
|
+
```typescript
|
|
1107
|
+
const server = new MCPServer({
|
|
1108
|
+
id: 'my-server',
|
|
1109
|
+
name: 'My Server',
|
|
1110
|
+
version: '1.0.0',
|
|
1111
|
+
tools: { getUserData },
|
|
1112
|
+
mapAuthInfoToUser: ({ authInfo }) => {
|
|
1113
|
+
const user = authInfo as {
|
|
1114
|
+
extra?: {
|
|
1115
|
+
userId?: string
|
|
1116
|
+
organizationMembershipId?: string
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
if (!user.extra?.userId) {
|
|
1121
|
+
return null
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
return {
|
|
1125
|
+
id: user.extra.userId,
|
|
1126
|
+
organizationMembershipId: user.extra.organizationMembershipId,
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
})
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1100
1132
|
### Setting Up Authentication Middleware
|
|
1101
1133
|
|
|
1102
1134
|
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
|
@@ -2783,6 +2783,7 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
2783
2783
|
definedPrompts;
|
|
2784
2784
|
promptOptions;
|
|
2785
2785
|
jsonSchemaValidator;
|
|
2786
|
+
mapAuthInfoToUser;
|
|
2786
2787
|
subscriptions = /* @__PURE__ */ new Set();
|
|
2787
2788
|
currentLoggingLevel;
|
|
2788
2789
|
/**
|
|
@@ -2884,6 +2885,7 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
2884
2885
|
* @param opts.prompts - Optional prompt configuration for exposing reusable templates
|
|
2885
2886
|
* @param opts.id - Optional unique identifier (generated if not provided)
|
|
2886
2887
|
* @param opts.description - Optional description of what the server does
|
|
2888
|
+
* @param opts.mapAuthInfoToUser - Optional mapper from MCP `extra.authInfo` to the FGA user context
|
|
2887
2889
|
*
|
|
2888
2890
|
* @example
|
|
2889
2891
|
* ```typescript
|
|
@@ -2920,6 +2922,7 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
2920
2922
|
this.resourceOptions = this.mergeAppResources(opts.resources, opts.appResources);
|
|
2921
2923
|
this.promptOptions = opts.prompts;
|
|
2922
2924
|
this.jsonSchemaValidator = opts.jsonSchemaValidator;
|
|
2925
|
+
this.mapAuthInfoToUser = opts.mapAuthInfoToUser;
|
|
2923
2926
|
const capabilities = {
|
|
2924
2927
|
tools: {},
|
|
2925
2928
|
logging: { enabled: true }
|
|
@@ -3156,7 +3159,7 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
3156
3159
|
*/
|
|
3157
3160
|
registerHandlersOnServer(serverInstance) {
|
|
3158
3161
|
serverInstance.setRequestHandler(types_js.ListToolsRequestSchema, async (_request, extra) => {
|
|
3159
|
-
const proxiedContext = this.createProxiedRequestContext(extra);
|
|
3162
|
+
const proxiedContext = await this.createProxiedRequestContext(extra);
|
|
3160
3163
|
const tools = await this.getAuthorizedConvertedToolEntries(proxiedContext);
|
|
3161
3164
|
return {
|
|
3162
3165
|
tools: tools.map(([, tool]) => {
|
|
@@ -3236,12 +3239,7 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
3236
3239
|
return this.handleElicitationRequest(request2, serverInstance, options);
|
|
3237
3240
|
}
|
|
3238
3241
|
};
|
|
3239
|
-
const proxiedContext =
|
|
3240
|
-
if (extra) {
|
|
3241
|
-
Object.entries(extra).forEach(([key, value]) => {
|
|
3242
|
-
proxiedContext.set(key, value);
|
|
3243
|
-
});
|
|
3244
|
-
}
|
|
3242
|
+
const proxiedContext = await this.createProxiedRequestContext(extra);
|
|
3245
3243
|
const mcpOptions = {
|
|
3246
3244
|
messages: [],
|
|
3247
3245
|
toolCallId: "",
|
|
@@ -4520,22 +4518,50 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
4520
4518
|
_meta: withMastraToolStrictMeta(tool.mcp?._meta, tool.strict)
|
|
4521
4519
|
};
|
|
4522
4520
|
}
|
|
4523
|
-
createProxiedRequestContext(extra) {
|
|
4521
|
+
async createProxiedRequestContext(extra) {
|
|
4524
4522
|
const proxiedContext = new requestContext.RequestContext();
|
|
4523
|
+
let extraRecord;
|
|
4525
4524
|
if (extra && typeof extra === "object") {
|
|
4526
|
-
|
|
4525
|
+
extraRecord = extra;
|
|
4526
|
+
Object.entries(extraRecord).forEach(([key, value]) => {
|
|
4527
4527
|
proxiedContext.set(key, value);
|
|
4528
4528
|
});
|
|
4529
4529
|
}
|
|
4530
|
+
await this.resolveMappedFGAUser(proxiedContext, extraRecord);
|
|
4530
4531
|
return proxiedContext;
|
|
4531
4532
|
}
|
|
4533
|
+
async resolveMappedFGAUser(requestContext, extra) {
|
|
4534
|
+
if (!requestContext) {
|
|
4535
|
+
return void 0;
|
|
4536
|
+
}
|
|
4537
|
+
const existingUser = requestContext.get("user");
|
|
4538
|
+
if (existingUser) {
|
|
4539
|
+
return existingUser;
|
|
4540
|
+
}
|
|
4541
|
+
if (!this.mapAuthInfoToUser) {
|
|
4542
|
+
return void 0;
|
|
4543
|
+
}
|
|
4544
|
+
const authInfo = extra && "authInfo" in extra ? extra.authInfo : requestContext.get("authInfo");
|
|
4545
|
+
if (!authInfo) {
|
|
4546
|
+
return void 0;
|
|
4547
|
+
}
|
|
4548
|
+
const user = await this.mapAuthInfoToUser({
|
|
4549
|
+
authInfo,
|
|
4550
|
+
extra: extra ?? { authInfo },
|
|
4551
|
+
requestContext
|
|
4552
|
+
});
|
|
4553
|
+
if (user) {
|
|
4554
|
+
requestContext.set("user", user);
|
|
4555
|
+
}
|
|
4556
|
+
return user;
|
|
4557
|
+
}
|
|
4532
4558
|
async getAuthorizedConvertedToolEntries(requestContext) {
|
|
4533
4559
|
const entries = Object.entries(this.convertedTools);
|
|
4534
4560
|
const fgaProvider = this.mastra?.getServer?.()?.fga;
|
|
4535
4561
|
if (!fgaProvider) {
|
|
4536
4562
|
return entries;
|
|
4537
4563
|
}
|
|
4538
|
-
const user =
|
|
4564
|
+
const user = await this.resolveMappedFGAUser(requestContext);
|
|
4539
4565
|
if (!user) {
|
|
4540
4566
|
return [];
|
|
4541
4567
|
}
|
|
@@ -4559,17 +4585,26 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
4559
4585
|
if (!fgaProvider) {
|
|
4560
4586
|
return;
|
|
4561
4587
|
}
|
|
4562
|
-
const {
|
|
4563
|
-
const resourceId =
|
|
4564
|
-
const user = requestContext
|
|
4588
|
+
const { getMCPToolFGAResourceId, requireFGA, FGADeniedError, MastraFGAPermissions } = await import('@mastra/core/auth/ee');
|
|
4589
|
+
const resourceId = getMCPToolFGAResourceId(this.id, toolId);
|
|
4590
|
+
const user = await this.resolveMappedFGAUser(requestContext);
|
|
4565
4591
|
if (!user) {
|
|
4566
4592
|
throw new FGADeniedError({ id: "unknown" }, { type: "tool", id: resourceId }, MastraFGAPermissions.TOOLS_EXECUTE);
|
|
4567
4593
|
}
|
|
4568
|
-
await
|
|
4594
|
+
await requireFGA({
|
|
4569
4595
|
fgaProvider,
|
|
4570
4596
|
user,
|
|
4571
4597
|
resource: { type: "tool", id: resourceId },
|
|
4572
|
-
permission: MastraFGAPermissions.TOOLS_EXECUTE
|
|
4598
|
+
permission: MastraFGAPermissions.TOOLS_EXECUTE,
|
|
4599
|
+
requestContext,
|
|
4600
|
+
context: {
|
|
4601
|
+
resourceId
|
|
4602
|
+
},
|
|
4603
|
+
metadata: {
|
|
4604
|
+
mcpServerId: this.id,
|
|
4605
|
+
mcpServerName: this.name,
|
|
4606
|
+
toolId
|
|
4607
|
+
}
|
|
4573
4608
|
});
|
|
4574
4609
|
}
|
|
4575
4610
|
/**
|