@ory/mastra 1.5.2 → 1.6.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/README.md CHANGED
@@ -36,6 +36,34 @@ import { withOry } from "@ory/mastra";
36
36
  const tools = withOry({ search, sendEmail });
37
37
  ```
38
38
 
39
+ ## MCP tools
40
+
41
+ Tools from a [`@mastra/mcp`](https://mastra.ai/docs/tools-mcp/mcp-overview) `MCPClient` are gated
42
+ as MCP tools: Agent Security checks `McpServer:<server>` and `McpTool:<server>:<tool>` and
43
+ resolves the reported server name to its catalog entry.
44
+
45
+ - `withOry` reads the server from the `mcpMetadata.serverName` the MCP client attaches to each
46
+ tool (the `servers` key), and strips the `<server>_` prefix from the tool id to check the
47
+ server's own tool name.
48
+ - `beforeToolCall` receives only the tool name, which `MCPClient.listTools()` builds as
49
+ `<server>_<tool>`. The hooks cannot tell an MCP tool from a native one by name alone, so pass
50
+ the server names to identify them; the longest matching server wins:
51
+
52
+ ```ts
53
+ const mcp = new MCPClient({ servers: { github: { url: new URL(githubUrl) } } });
54
+ const agent = new Agent({
55
+ /* … */
56
+ tools: { search, ...(await mcp.listTools()) },
57
+ hooks: oryAgentHooks({ mcpServers: ["github"] }),
58
+ });
59
+ ```
60
+
61
+ Without `mcpServers`, the hooks gate MCP tools as native tools under their `<server>_<tool>`
62
+ name. Name native tools so they do not start with a listed server name followed by `_`, or
63
+ they are checked as that server's tools. `listToolsets()` keys tools by their unprefixed
64
+ names, so the hooks cannot identify them; wrap those toolsets with `withOry`. Neither surface sees a server's URL or
65
+ launch command, so no server fingerprints are reported.
66
+
39
67
  This package depends only on `@ory/argus` — not `@mastra/core`; tools and hook contexts are
40
68
  duck-typed so any Mastra v1 version works. Credentials come from the shared
41
69
  `~/.config/ory-agent-plugins/config.json`.
package/dist/index.d.ts CHANGED
@@ -9,6 +9,10 @@
9
9
  * - {@link withOry} — wraps each tool's `execute` directly (deny → throw), for callers who
10
10
  * prefer per-tool wrapping or are on a Mastra version without the hooks API.
11
11
  *
12
+ * MCP tools from `@mastra/mcp` are gated as MCP tools (`McpServer` / `McpTool`). `withOry`
13
+ * reads the server from each tool's `mcpMetadata`; the hooks see only the tool name, so they
14
+ * identify `<server>_<tool>` names only for the servers listed in `mcpServers`.
15
+ *
12
16
  * The package has no `@mastra/core` dependency — tools/hook contexts are duck-typed.
13
17
  */
14
18
  import { OryAgentClient } from "@ory/argus";
@@ -20,6 +24,14 @@ export interface OryMastraOptions {
20
24
  projectUrl?: string;
21
25
  /** Whether a denied tool is hard-blocked. Default true. */
22
26
  canBlock?: boolean;
27
+ /**
28
+ * Names of the `MCPClient` servers whose tools the agent uses (the keys of `servers`, e.g.
29
+ * `Object.keys(mcp.servers)` or `["github"]`). `@mastra/mcp` names each tool
30
+ * `<server>_<tool>`, and the hooks receive only that name, so a tool name is gated as an MCP
31
+ * tool only when it starts with one of these servers followed by `_`; the longest match wins.
32
+ * `withOry` reads the server from the tool's `mcpMetadata` and uses this only as a fallback.
33
+ */
34
+ mcpServers?: Iterable<string>;
23
35
  }
24
36
  /**
25
37
  * Build the `{ beforeToolCall, afterToolCall }` hooks object for `new Agent({ hooks })`.
@@ -32,4 +44,4 @@ export declare function oryAgentHooks(options?: OryMastraOptions): OryMastraHook
32
44
  */
33
45
  export declare function withOry(tools: ToolSet, options?: OryMastraOptions): ToolSet;
34
46
  export { OryAgentClient, OryDenialError } from "@ory/argus";
35
- export type { OryMastraHooks, ToolSet, MastraTool, ToolHookBeforeContext, ToolHookAfterContext } from "./types.js";
47
+ export type { OryMastraHooks, ToolSet, MastraTool, MastraMcpMetadata, ToolHookBeforeContext, ToolHookAfterContext, } from "./types.js";
package/dist/index.js CHANGED
@@ -10,6 +10,10 @@
10
10
  * - {@link withOry} — wraps each tool's `execute` directly (deny → throw), for callers who
11
11
  * prefer per-tool wrapping or are on a Mastra version without the hooks API.
12
12
  *
13
+ * MCP tools from `@mastra/mcp` are gated as MCP tools (`McpServer` / `McpTool`). `withOry`
14
+ * reads the server from each tool's `mcpMetadata`; the hooks see only the tool name, so they
15
+ * identify `<server>_<tool>` names only for the servers listed in `mcpServers`.
16
+ *
13
17
  * The package has no `@mastra/core` dependency — tools/hook contexts are duck-typed.
14
18
  */
15
19
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -18,12 +22,24 @@ exports.oryAgentHooks = oryAgentHooks;
18
22
  exports.withOry = withOry;
19
23
  const argus_1 = require("@ory/argus");
20
24
  const HARNESS = "mastra";
25
+ /** The MCP identity of a `@mastra/mcp` tool, from the `mcpMetadata` the MCP client attaches. */
26
+ function mcpToolFromMetadata(tool, toolName) {
27
+ const serverName = tool.mcpMetadata?.serverName;
28
+ if (typeof serverName !== "string" || serverName === "")
29
+ return undefined;
30
+ const prefix = `${serverName}_`;
31
+ return {
32
+ serverName,
33
+ toolName: toolName.startsWith(prefix) && toolName.length > prefix.length ? toolName.slice(prefix.length) : toolName,
34
+ };
35
+ }
21
36
  /**
22
37
  * Build the `{ beforeToolCall, afterToolCall }` hooks object for `new Agent({ hooks })`.
23
38
  */
24
39
  function oryAgentHooks(options = {}) {
25
40
  const client = options.client ?? argus_1.OryAgentClient.fromEnv(HARNESS);
26
41
  const canBlock = options.canBlock ?? true;
42
+ const mcpServers = options.mcpServers ? [...options.mcpServers] : undefined;
27
43
  const ensureSession = (0, argus_1.createSessionStarter)(client, {
28
44
  harness: HARNESS,
29
45
  projectUrl: options.projectUrl,
@@ -40,11 +56,13 @@ function oryAgentHooks(options = {}) {
40
56
  projectUrl: options.projectUrl,
41
57
  }).catch(() => undefined);
42
58
  }
59
+ const mcpTool = mcpServers ? (0, argus_1.parseMcpToolWithSeparator)(toolName, "_", mcpServers) : null;
43
60
  const result = await (0, argus_1.gate)(client, {
44
61
  harness: HARNESS,
45
62
  toolName,
46
63
  toolArgs: ctx.input,
47
64
  canBlock,
65
+ mcpTool: mcpTool ?? undefined,
48
66
  });
49
67
  if (result.kind === "deny" && result.blocked) {
50
68
  return { proceed: false, output: result.denialMessage ?? "Ory: permission denied" };
@@ -64,6 +82,7 @@ function oryAgentHooks(options = {}) {
64
82
  function withOry(tools, options = {}) {
65
83
  const client = options.client ?? argus_1.OryAgentClient.fromEnv(HARNESS);
66
84
  const canBlock = options.canBlock ?? true;
85
+ const mcpServers = options.mcpServers ? [...options.mcpServers] : undefined;
67
86
  const ensureSession = (0, argus_1.createSessionStarter)(client, {
68
87
  harness: HARNESS,
69
88
  projectUrl: options.projectUrl,
@@ -75,6 +94,8 @@ function withOry(tools, options = {}) {
75
94
  continue;
76
95
  }
77
96
  const toolName = tool.id ?? key;
97
+ const mcpTool = mcpToolFromMetadata(tool, toolName) ??
98
+ (mcpServers ? (0, argus_1.parseMcpToolWithSeparator)(toolName, "_", mcpServers) ?? undefined : undefined);
78
99
  const originalExecute = tool.execute.bind(tool);
79
100
  wrapped[key] = {
80
101
  ...tool,
@@ -85,6 +106,7 @@ function withOry(tools, options = {}) {
85
106
  toolName,
86
107
  toolArgs: inputData,
87
108
  canBlock,
109
+ mcpTool,
88
110
  });
89
111
  if (result.blocked) {
90
112
  throw new argus_1.OryDenialError({
package/dist/types.d.ts CHANGED
@@ -25,9 +25,18 @@ export interface OryMastraHooks {
25
25
  beforeToolCall: (ctx: ToolHookBeforeContext) => Promise<void | ToolBeforeHookResult> | void | ToolBeforeHookResult;
26
26
  afterToolCall: (ctx: ToolHookAfterContext) => Promise<void> | void;
27
27
  }
28
+ /** Server metadata `@mastra/mcp` attaches to the tools its `MCPClient` creates. */
29
+ export interface MastraMcpMetadata {
30
+ /** The `MCPClient` `servers` key the tool came from. */
31
+ serverName?: string;
32
+ serverVersion?: string;
33
+ [key: string]: unknown;
34
+ }
28
35
  /** A Mastra tool — duck-typed. `execute` takes `(inputData, context)` in v1. */
29
36
  export interface MastraTool {
30
37
  id?: string;
38
+ /** Present on tools from a `@mastra/mcp` `MCPClient`; the tool `id` is `<server>_<tool>`. */
39
+ mcpMetadata?: MastraMcpMetadata;
31
40
  execute?: (inputData: unknown, context?: unknown) => unknown;
32
41
  [key: string]: unknown;
33
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ory/mastra",
3
- "version": "1.5.2",
3
+ "version": "1.6.0",
4
4
  "description": "Ory Agent Security for Mastra — per-tool authorization, activity auditing, and identity propagation via agent tool-call hooks (or wrapping tool execute). Built on @ory/argus.",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
@@ -34,10 +34,10 @@
34
34
  ],
35
35
  "dependencies": {
36
36
  "reova": "^0.7.0",
37
- "@ory/argus": "1.5.2"
37
+ "@ory/argus": "1.6.0"
38
38
  },
39
39
  "devDependencies": {
40
- "typescript": "^6.0.2"
40
+ "typescript": "^7.0.2"
41
41
  },
42
42
  "scripts": {
43
43
  "build": "tsc",