@ory/cloudflare-agents 1.5.3 → 1.7.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
@@ -64,8 +64,18 @@ export class MyAgent extends AIChatAgent<Env> {
64
64
  }
65
65
  ```
66
66
 
67
- To gate MCP tools too, include them where you assemble the wrapped tool map:
68
- `withOry({ getWeather, ...this.mcp.getAITools() }, { client: this.ory })`.
67
+ To gate MCP tools too, include them where you assemble the wrapped tool map and pass the
68
+ agent's MCP client manager:
69
+ `withOry({ getWeather, ...this.mcp.getAITools() }, { client: this.ory, mcp: this.mcp })`.
70
+
71
+ With `mcp`, each `getAITools()` entry is gated as an MCP tool: Agent Security checks
72
+ `McpServer:<server>` and `McpTool:<server>:<tool>` under the name given to `addMcpServer`, and
73
+ the server URL's host is hashed into a fingerprint (Durable Object binding servers report
74
+ none). The wrapper attributes a `tool_<serverId>_<tool>` key by rebuilding the keys of the
75
+ manager's connected tools (`listTools()`), never by splitting the key, and resolves it on each
76
+ call so servers that connect later are recognized. Without `mcp`, the tool objects carry no
77
+ server metadata, so MCP tools are gated as native tools under their `tool_<serverId>_<tool>`
78
+ key.
69
79
 
70
80
  In a multi-user Worker, attribute each call to the acting user with `subjectFromOptions` — a
71
81
  non-empty return value takes precedence over the client's user principal and env overrides
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  * import { withOry } from "@ory/cloudflare-agents";
13
13
  * const result = streamText({
14
14
  * model, messages,
15
- * tools: withOry({ ...this.tools, ...this.mcp.getAITools() }),
15
+ * tools: withOry({ ...this.tools, ...this.mcp.getAITools() }, { mcp: this.mcp }),
16
16
  * });
17
17
  *
18
18
  * Depends only on `@ory/argus` — the `ai` / `agents` packages are not dependencies; tools are
@@ -25,6 +25,23 @@ export interface AiTool {
25
25
  [key: string]: unknown;
26
26
  }
27
27
  export type ToolSet = Record<string, AiTool>;
28
+ /**
29
+ * The parts of the Agents SDK `MCPClientManager` (`this.mcp`) used to attribute
30
+ * `getAITools()` entries to their servers — duck-typed.
31
+ */
32
+ export interface McpClientManagerLike {
33
+ /** Connected servers' tools, each tagged with its server id. */
34
+ listTools(): ReadonlyArray<{
35
+ name: string;
36
+ serverId: string;
37
+ }>;
38
+ /** Stored servers: id, the name given to `addMcpServer`, and the server URL. */
39
+ listServers(): ReadonlyArray<{
40
+ id: string;
41
+ name: string;
42
+ server_url?: string | null;
43
+ }>;
44
+ }
28
45
  export interface WithOryOptions {
29
46
  /**
30
47
  * Client to use. Defaults to `OryAgentClient.fromEnv("cloudflare-agents")`. On the Workers
@@ -45,6 +62,13 @@ export interface WithOryOptions {
45
62
  * the principal / env chain.
46
63
  */
47
64
  subjectFromOptions?: (options: unknown) => string | undefined;
65
+ /**
66
+ * The agent's MCP client manager (`this.mcp`). With it, tools from `this.mcp.getAITools()`
67
+ * are gated as MCP tools under the server name given to `addMcpServer`, with its URL host
68
+ * hashed into a fingerprint. Without it, they are gated as native tools under their
69
+ * `tool_<serverId>_<tool>` key.
70
+ */
71
+ mcp?: McpClientManagerLike;
48
72
  }
49
73
  /**
50
74
  * Wrap every tool in `tools` so its `execute` is gated through Ory. Tools without an
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@
13
13
  * import { withOry } from "@ory/cloudflare-agents";
14
14
  * const result = streamText({
15
15
  * model, messages,
16
- * tools: withOry({ ...this.tools, ...this.mcp.getAITools() }),
16
+ * tools: withOry({ ...this.tools, ...this.mcp.getAITools() }, { mcp: this.mcp }),
17
17
  * });
18
18
  *
19
19
  * Depends only on `@ory/argus` — the `ai` / `agents` packages are not dependencies; tools are
@@ -24,6 +24,28 @@ exports.OryDenialError = exports.OryAgentClient = void 0;
24
24
  exports.withOry = withOry;
25
25
  const argus_1 = require("@ory/argus");
26
26
  const HARNESS = "cloudflare-agents";
27
+ /** The tool-map key `MCPClientManager.getAITools()` gives one server tool. */
28
+ function aiToolKey(serverId, toolName) {
29
+ return `tool_${serverId.replace(/-/g, "")}_${toolName}`;
30
+ }
31
+ /**
32
+ * Attribute a `getAITools()` key to its server by rebuilding each connected tool's key, so
33
+ * server ids and tool names containing `_` never need to be split.
34
+ */
35
+ function identifyMcpTool(key, mcp) {
36
+ if (!key.startsWith("tool_"))
37
+ return undefined;
38
+ const tool = mcp.listTools().find((candidate) => aiToolKey(candidate.serverId, candidate.name) === key);
39
+ if (!tool)
40
+ return undefined;
41
+ const server = mcp.listServers().find((candidate) => candidate.id === tool.serverId);
42
+ const fingerprints = (0, argus_1.mcpServerFingerprints)({ url: server?.server_url ?? undefined });
43
+ return {
44
+ serverName: server?.name || tool.serverId,
45
+ toolName: tool.name,
46
+ ...(fingerprints.length > 0 ? { fingerprints } : {}),
47
+ };
48
+ }
27
49
  /**
28
50
  * Wrap every tool in `tools` so its `execute` is gated through Ory. Tools without an
29
51
  * `execute` (client-side / provider-defined) are passed through untouched. Apply it to the
@@ -49,11 +71,14 @@ function withOry(tools, options = {}) {
49
71
  await ensureSession();
50
72
  // A per-call subject wins over the client principal / env overrides.
51
73
  const subjectOverride = options.subjectFromOptions?.(callOptions);
74
+ // Resolved per call: MCP servers connect after the wrapped tool set is built.
75
+ const mcpTool = options.mcp ? identifyMcpTool(name, options.mcp) : undefined;
52
76
  const result = await (0, argus_1.runWithUserSubject)(subjectOverride, () => (0, argus_1.gate)(client, {
53
77
  harness: HARNESS,
54
78
  toolName: name,
55
79
  toolArgs: input,
56
80
  canBlock,
81
+ mcpTool,
57
82
  }));
58
83
  if (result.blocked) {
59
84
  throw new argus_1.OryDenialError({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ory/cloudflare-agents",
3
- "version": "1.5.3",
3
+ "version": "1.7.0",
4
4
  "description": "Ory Agent Security for the Cloudflare Agents SDK — per-tool authorization, activity auditing, and identity propagation by wrapping the AI SDK tools passed to streamText. Built on @ory/argus.",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
@@ -35,10 +35,10 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "reova": "^0.7.0",
38
- "@ory/argus": "1.5.3"
38
+ "@ory/argus": "1.7.0"
39
39
  },
40
40
  "devDependencies": {
41
- "typescript": "^6.0.2"
41
+ "typescript": "^7.0.2"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsc",