@ory/cloudflare-agents 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 +12 -2
- package/dist/index.d.ts +25 -1
- package/dist/index.js +26 -1
- package/package.json +3 -3
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
|
-
|
|
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.
|
|
3
|
+
"version": "1.6.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.
|
|
38
|
+
"@ory/argus": "1.6.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"typescript": "^
|
|
41
|
+
"typescript": "^7.0.2"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsc",
|