@raytio/mcp-server 3.0.0 → 3.1.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/README.md +6 -5
- package/dist/authorization.d.ts +27 -2
- package/dist/authorization.d.ts.map +1 -1
- package/dist/authorization.js +67 -4
- package/dist/authorization.js.map +1 -1
- package/dist/capabilities/effective-capabilities.d.ts +241 -0
- package/dist/capabilities/effective-capabilities.d.ts.map +1 -0
- package/dist/capabilities/effective-capabilities.js +252 -0
- package/dist/capabilities/effective-capabilities.js.map +1 -0
- package/dist/index.js +20 -8
- package/dist/index.js.map +1 -1
- package/dist/tools/dispatch.d.ts +8 -1
- package/dist/tools/dispatch.d.ts.map +1 -1
- package/dist/tools/dispatch.js +16 -1
- package/dist/tools/dispatch.js.map +1 -1
- package/dist/tools/registry.d.ts +13 -0
- package/dist/tools/registry.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -145,13 +145,14 @@ Restart Codex CLI after editing the config. Inside a session, `/mcp` lists loade
|
|
|
145
145
|
|
|
146
146
|
### Tool domain selection
|
|
147
147
|
|
|
148
|
-
The server decides which tools to expose
|
|
148
|
+
The server decides which tools to expose in two layers:
|
|
149
149
|
|
|
150
|
-
1.
|
|
151
|
-
2. **
|
|
152
|
-
3. **All domains** (fallback) — if the role query fails or returns no recognised roles, every domain is exposed (matching pre-v1.4.0 behaviour).
|
|
150
|
+
1. **Role-domain filter** (coarse) — at startup the server queries the authenticated user's roles via `fnd_authz_user_groups()` and maps them to tool domains. The `RAYTIO_TOOL_DOMAINS` env var can further restrict this set but never expand it.
|
|
151
|
+
2. **Effective-capability filter** (fine) — the server then calls the Persist RPC `fnd_get_effective_capabilities()` and drops any tool whose declared `(resource_group, resource_name, operation, scope)` requirement is not satisfied. `OWN_RECORDS` grants NEVER silently satisfy `ALL_RECORDS` requirements.
|
|
153
152
|
|
|
154
|
-
|
|
153
|
+
Both layers **fail closed**: if the role query or capability RPC fails, or returns no recognised roles / no capabilities for a gated tool, that tool is not exposed. Dispatch re-checks capabilities at call time, so a stale `ListTools` result cannot bypass the check.
|
|
154
|
+
|
|
155
|
+
The server logs which path was taken and how many tools were hidden at startup (to stderr).
|
|
155
156
|
|
|
156
157
|
#### How dynamic resolution works
|
|
157
158
|
|
package/dist/authorization.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { EffectiveCapabilityIndex } from './capabilities/effective-capabilities.js';
|
|
1
2
|
import { RaytioApiClient } from './raytio/api-client.js';
|
|
3
|
+
import { Tool } from './tools/registry.js';
|
|
2
4
|
/**
|
|
3
5
|
* Result of resolving the effective tool domain set for the authenticated
|
|
4
6
|
* user. When `authorized` is `false`, the caller MUST expose no tools.
|
|
@@ -10,20 +12,43 @@ export interface AuthorizationResult {
|
|
|
10
12
|
reason: string;
|
|
11
13
|
/** `true` when the caller should expose tools. `false` means fail closed — expose nothing. */
|
|
12
14
|
authorized: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Immutable index over the caller's effective Persist capabilities.
|
|
17
|
+
* Present only when the effective-capabilities RPC was fetched and
|
|
18
|
+
* validated successfully. Consumers use this both to filter the
|
|
19
|
+
* advertised tool list and to gate `CallTool` dispatch.
|
|
20
|
+
*/
|
|
21
|
+
capabilities?: EffectiveCapabilityIndex;
|
|
13
22
|
}
|
|
14
23
|
type StderrLogger = Pick<Console, 'error'>;
|
|
15
24
|
/**
|
|
16
25
|
* Resolve the tool domain surface for the authenticated user.
|
|
17
26
|
*
|
|
18
27
|
* Behaviour:
|
|
19
|
-
* - Always queries the backend for the user's resolved roles
|
|
20
|
-
*
|
|
28
|
+
* - Always queries the backend for the user's resolved roles AND
|
|
29
|
+
* effective Persist capabilities. On any failure of either query,
|
|
30
|
+
* fails closed and exposes no tools.
|
|
21
31
|
* - The static `RAYTIO_TOOL_DOMAINS` override may only *restrict* the
|
|
22
32
|
* user's resolved domain set. It never grants access to domains the
|
|
23
33
|
* user does not have.
|
|
24
34
|
* - If the intersection is empty (or the user has no recognised roles),
|
|
25
35
|
* fails closed.
|
|
36
|
+
* - The returned `capabilities` index is used downstream by
|
|
37
|
+
* {@link filterToolsByCapabilities} and by the dispatch layer to
|
|
38
|
+
* enforce per-tool authorisation at both advertisement and call time.
|
|
26
39
|
*/
|
|
27
40
|
export declare function resolveToolDomains(apiClient: RaytioApiClient, envSpec: string | undefined, logger?: StderrLogger): Promise<AuthorizationResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Drop any tool whose declared `capabilities` are not satisfied by the
|
|
43
|
+
* caller's effective capability set. Tools that have no `capabilities`
|
|
44
|
+
* declaration pass through unchanged — they are gated only by the
|
|
45
|
+
* coarser role-domain filter until they are migrated onto the pinned
|
|
46
|
+
* Persist resource contract (see WI-0003500 onward).
|
|
47
|
+
*
|
|
48
|
+
* A tool with an empty `capabilities` array is treated as declaring
|
|
49
|
+
* "requires no permission" and passes through — the empty list is an
|
|
50
|
+
* explicit affirmation, not a fallthrough.
|
|
51
|
+
*/
|
|
52
|
+
export declare function filterToolsByCapabilities(tools: readonly Tool[], capabilities: EffectiveCapabilityIndex, logger?: StderrLogger): Tool[];
|
|
28
53
|
export {};
|
|
29
54
|
//# sourceMappingURL=authorization.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorization.d.ts","sourceRoot":"","sources":["../src/authorization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"authorization.d.ts","sourceRoot":"","sources":["../src/authorization.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,wBAAwB,EAEzB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAG3C;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,mHAAmH;IACnH,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,yFAAyF;IACzF,MAAM,EAAE,MAAM,CAAC;IACf,8FAA8F;IAC9F,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,wBAAwB,CAAC;CACzC;AAED,KAAK,YAAY,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAgD3C;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,eAAe,EAC1B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,MAAM,GAAE,YAAsB,GAC7B,OAAO,CAAC,mBAAmB,CAAC,CAqE9B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,SAAS,IAAI,EAAE,EACtB,YAAY,EAAE,wBAAwB,EACtC,MAAM,GAAE,YAAsB,GAC7B,IAAI,EAAE,CA8BR"}
|
package/dist/authorization.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EffectiveCapabilityError, fetchEffectiveCapabilities, } from './capabilities/effective-capabilities.js';
|
|
1
2
|
import { resolveUserDomains } from './tools/role-mapping.js';
|
|
2
3
|
/**
|
|
3
4
|
* Domains that a static override may resolve to when the user only holds
|
|
@@ -44,13 +45,17 @@ function restrictToUserDomains(staticDomains, userDomains) {
|
|
|
44
45
|
* Resolve the tool domain surface for the authenticated user.
|
|
45
46
|
*
|
|
46
47
|
* Behaviour:
|
|
47
|
-
* - Always queries the backend for the user's resolved roles
|
|
48
|
-
*
|
|
48
|
+
* - Always queries the backend for the user's resolved roles AND
|
|
49
|
+
* effective Persist capabilities. On any failure of either query,
|
|
50
|
+
* fails closed and exposes no tools.
|
|
49
51
|
* - The static `RAYTIO_TOOL_DOMAINS` override may only *restrict* the
|
|
50
52
|
* user's resolved domain set. It never grants access to domains the
|
|
51
53
|
* user does not have.
|
|
52
54
|
* - If the intersection is empty (or the user has no recognised roles),
|
|
53
55
|
* fails closed.
|
|
56
|
+
* - The returned `capabilities` index is used downstream by
|
|
57
|
+
* {@link filterToolsByCapabilities} and by the dispatch layer to
|
|
58
|
+
* enforce per-tool authorisation at both advertisement and call time.
|
|
54
59
|
*/
|
|
55
60
|
export async function resolveToolDomains(apiClient, envSpec, logger = console) {
|
|
56
61
|
const staticDomains = parseSpec(envSpec);
|
|
@@ -69,10 +74,28 @@ export async function resolveToolDomains(apiClient, envSpec, logger = console) {
|
|
|
69
74
|
logger.error(`Raytio MCP: ${reason} — exposing no tools (fail closed)`);
|
|
70
75
|
return { domainSpec: undefined, reason, authorized: false };
|
|
71
76
|
}
|
|
77
|
+
let capabilities;
|
|
78
|
+
try {
|
|
79
|
+
capabilities = await fetchEffectiveCapabilities(apiClient);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
const detail = err instanceof EffectiveCapabilityError
|
|
83
|
+
? err.message
|
|
84
|
+
: `unexpected error: ${err.message}`;
|
|
85
|
+
const reason = `effective capabilities discovery failed: ${detail}`;
|
|
86
|
+
logger.error(`Raytio MCP: ${reason} — exposing no tools (fail closed)`);
|
|
87
|
+
return { domainSpec: undefined, reason, authorized: false };
|
|
88
|
+
}
|
|
89
|
+
logger.error(`Raytio MCP: fetched ${capabilities.size} effective capability rows for authenticated user`);
|
|
72
90
|
if (staticDomains.length === 0) {
|
|
73
91
|
const spec = userDomains.join(',');
|
|
74
92
|
logger.error(`Raytio MCP: resolved domains from user roles: ${spec}`);
|
|
75
|
-
return {
|
|
93
|
+
return {
|
|
94
|
+
domainSpec: spec,
|
|
95
|
+
reason: 'resolved from user roles',
|
|
96
|
+
authorized: true,
|
|
97
|
+
capabilities,
|
|
98
|
+
};
|
|
76
99
|
}
|
|
77
100
|
const allowed = restrictToUserDomains(staticDomains, userDomains);
|
|
78
101
|
if (allowed.length === 0) {
|
|
@@ -82,6 +105,46 @@ export async function resolveToolDomains(apiClient, envSpec, logger = console) {
|
|
|
82
105
|
}
|
|
83
106
|
const spec = allowed.join(',');
|
|
84
107
|
logger.error(`Raytio MCP: static RAYTIO_TOOL_DOMAINS restricted user roles (${userDomains.join(',')}) to ${spec}`);
|
|
85
|
-
return {
|
|
108
|
+
return {
|
|
109
|
+
domainSpec: spec,
|
|
110
|
+
reason: 'restricted by static override',
|
|
111
|
+
authorized: true,
|
|
112
|
+
capabilities,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Drop any tool whose declared `capabilities` are not satisfied by the
|
|
117
|
+
* caller's effective capability set. Tools that have no `capabilities`
|
|
118
|
+
* declaration pass through unchanged — they are gated only by the
|
|
119
|
+
* coarser role-domain filter until they are migrated onto the pinned
|
|
120
|
+
* Persist resource contract (see WI-0003500 onward).
|
|
121
|
+
*
|
|
122
|
+
* A tool with an empty `capabilities` array is treated as declaring
|
|
123
|
+
* "requires no permission" and passes through — the empty list is an
|
|
124
|
+
* explicit affirmation, not a fallthrough.
|
|
125
|
+
*/
|
|
126
|
+
export function filterToolsByCapabilities(tools, capabilities, logger = console) {
|
|
127
|
+
const allowed = [];
|
|
128
|
+
let hidden = 0;
|
|
129
|
+
for (const tool of tools) {
|
|
130
|
+
if (!tool.capabilities || tool.capabilities.length === 0) {
|
|
131
|
+
allowed.push(tool);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const missing = tool.capabilities.filter((req) => !capabilities.satisfies(req));
|
|
135
|
+
if (missing.length === 0) {
|
|
136
|
+
allowed.push(tool);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
hidden += 1;
|
|
140
|
+
const detail = missing
|
|
141
|
+
.map((r) => `${r.resourceGroup}.${r.resourceName}#${r.operation}@${r.scope ?? 'OWN_RECORDS'}`)
|
|
142
|
+
.join(', ');
|
|
143
|
+
logger.error(`Raytio MCP: hiding tool "${tool.name}" — missing capability: ${detail}`);
|
|
144
|
+
}
|
|
145
|
+
if (hidden > 0) {
|
|
146
|
+
logger.error(`Raytio MCP: capability filter hid ${hidden}/${tools.length} tools`);
|
|
147
|
+
}
|
|
148
|
+
return allowed;
|
|
86
149
|
}
|
|
87
150
|
//# sourceMappingURL=authorization.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorization.js","sourceRoot":"","sources":["../src/authorization.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"authorization.js","sourceRoot":"","sources":["../src/authorization.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { RaytioApiClient } from '../raytio/api-client.js';
|
|
3
|
+
/**
|
|
4
|
+
* Client and index for the supported Persist "effective capabilities" RPC
|
|
5
|
+
* (`public.fnd_get_effective_capabilities()`), owned by WI-0003499.
|
|
6
|
+
*
|
|
7
|
+
* The RPC returns one row per (resource_group, resource_name, operation)
|
|
8
|
+
* tuple that the authenticated user may exercise. MCP uses that set to
|
|
9
|
+
* authorise both tool advertisement (`ListTools`) and tool dispatch
|
|
10
|
+
* (`CallTool`).
|
|
11
|
+
*
|
|
12
|
+
* Row semantics — see `docs/mcp-effective-capabilities.md` in the Persist
|
|
13
|
+
* repository:
|
|
14
|
+
*
|
|
15
|
+
* - `resource_group` — 3-letter module code (e.g. `HRM`, `DSM`, `FAR`).
|
|
16
|
+
* Matches `resources[*].permission.resource_group` in the pinned MCP
|
|
17
|
+
* resource contract.
|
|
18
|
+
* - `resource_name` — resource-scoped code (e.g. `ACCESS_APPLICATIONS`),
|
|
19
|
+
* or `null` for a domain-level permission that applies to every
|
|
20
|
+
* resource in the group. Persist suppresses domain rows when
|
|
21
|
+
* table-specific entries exist for the same (group, operation) —
|
|
22
|
+
* MCP relies on that shadowing and does not re-check it here.
|
|
23
|
+
* - `operation` — one of `SELECT`, `INSERT`, `UPDATE`, `DELETE`, or a
|
|
24
|
+
* semantic operation seeded into the `HRM_PERMISSIONS_OPERATION`
|
|
25
|
+
* lookup.
|
|
26
|
+
* - `scope` — exactly one of `ALL_RECORDS` or `ROW_BOUNDED` on the wire.
|
|
27
|
+
* `ALL_RECORDS` means "verified unbounded HRM access". `ROW_BOUNDED`
|
|
28
|
+
* means "the HRM grant exists at OWN_RECORDS scope but real access is
|
|
29
|
+
* via ownership / ReBAC / row filters — treat as informational". A
|
|
30
|
+
* `NULL` scope is coerced to `ROW_BOUNDED` inside the RPC, never to
|
|
31
|
+
* unbounded. MCP normalises `ROW_BOUNDED` to the pre-existing internal
|
|
32
|
+
* alias `OWN_RECORDS` on ingress (see {@link CAPABILITY_SCOPES}); both
|
|
33
|
+
* names refer to the same "not verified unbounded" semantic, so the
|
|
34
|
+
* rest of the MCP code path — CapabilityRequirement, satisfies(),
|
|
35
|
+
* dispatch — is unchanged. Reconciled in WI-0003661.
|
|
36
|
+
* - `advisory` — `TRUE` iff `scope = ROW_BOUNDED`; `FALSE` for
|
|
37
|
+
* `ALL_RECORDS`. Redundant with `scope` today; accepted (optional) on
|
|
38
|
+
* the wire so a producer-side addition remains non-breaking.
|
|
39
|
+
*
|
|
40
|
+
* When the caller reaches the same capability at both `ALL_RECORDS` and
|
|
41
|
+
* `ROW_BOUNDED`, the RPC collapses the row to `ALL_RECORDS`. MCP relies
|
|
42
|
+
* on that guarantee — the client does not attempt its own scope merge.
|
|
43
|
+
*
|
|
44
|
+
* MCP fails closed on any of:
|
|
45
|
+
* - HTTP transport failure,
|
|
46
|
+
* - JSON that does not parse or does not match the schema below,
|
|
47
|
+
* - unknown values in `operation` or `scope`,
|
|
48
|
+
* - a required tool capability that is not present in the returned set.
|
|
49
|
+
*
|
|
50
|
+
* `OWN_RECORDS` MUST NEVER silently become `ALL_RECORDS` — the tool
|
|
51
|
+
* declaration carries the scope its handler is safe with, and a broader
|
|
52
|
+
* effective capability satisfies it; a narrower one does not.
|
|
53
|
+
*
|
|
54
|
+
* ---
|
|
55
|
+
*
|
|
56
|
+
* Deliberate deviation from Persist's interpretation invariant
|
|
57
|
+
* (WI-0003661):
|
|
58
|
+
*
|
|
59
|
+
* Persist's contract states that "absence means unknown — ask the
|
|
60
|
+
* database" and that `ROW_BOUNDED` rows are informational, not a
|
|
61
|
+
* deny-side authorisation gate. MCP intentionally departs from that on
|
|
62
|
+
* the tool-advertisement path:
|
|
63
|
+
*
|
|
64
|
+
* - {@link filterToolsByCapabilities} HIDES any tool whose declared
|
|
65
|
+
* capabilities are missing from the caller's returned set.
|
|
66
|
+
* - {@link EffectiveCapabilityIndex.satisfies} returns `false` when the
|
|
67
|
+
* caller holds no matching row.
|
|
68
|
+
*
|
|
69
|
+
* The rationale is fail-closed advertisement: exposing a tool a caller
|
|
70
|
+
* cannot execute is a poor UX and leaks the shape of the denied surface.
|
|
71
|
+
* The database still enforces at call time — HRM permissions, ownership,
|
|
72
|
+
* and ReBAC all continue to gate the underlying query — so any tool
|
|
73
|
+
* whose actual access path is via ownership or ReBAC (never surfaced by
|
|
74
|
+
* this RPC) will be hidden until an explicit capability is granted or
|
|
75
|
+
* the tool's declaration is loosened. This is a design choice, not an
|
|
76
|
+
* oversight; revisit if MCP ever needs to advertise
|
|
77
|
+
* ownership/ReBAC-only tools.
|
|
78
|
+
*/
|
|
79
|
+
/**
|
|
80
|
+
* Wire-side scope vocabulary the RPC emits (per Persist's
|
|
81
|
+
* `docs/mcp-effective-capabilities.md`, WI-0003499):
|
|
82
|
+
*
|
|
83
|
+
* - `ALL_RECORDS` — verified unbounded HRM access.
|
|
84
|
+
* - `ROW_BOUNDED` — advisory HRM row; actual access via
|
|
85
|
+
* ownership / ReBAC / row filters.
|
|
86
|
+
*
|
|
87
|
+
* The legacy `OWN_RECORDS` label — the pre-rename name for `ROW_BOUNDED`
|
|
88
|
+
* — is still accepted for transitional deployments of Persist; both
|
|
89
|
+
* legacy and current values are normalised to the internal
|
|
90
|
+
* `OWN_RECORDS` alias.
|
|
91
|
+
*/
|
|
92
|
+
export declare const WIRE_CAPABILITY_SCOPES: readonly ["ALL_RECORDS", "ROW_BOUNDED", "OWN_RECORDS"];
|
|
93
|
+
export type WireCapabilityScope = (typeof WIRE_CAPABILITY_SCOPES)[number];
|
|
94
|
+
/**
|
|
95
|
+
* MCP-internal scope vocabulary used by {@link CapabilityRequirement}
|
|
96
|
+
* declarations and by the scope stored inside
|
|
97
|
+
* {@link EffectiveCapabilityIndex}. Persist's `ROW_BOUNDED` is
|
|
98
|
+
* normalised to `OWN_RECORDS` on ingress — the two names encode the
|
|
99
|
+
* same semantic ("not a verified unbounded HRM grant — enforcement of
|
|
100
|
+
* access is via ownership / ReBAC / row filters at call time").
|
|
101
|
+
*
|
|
102
|
+
* Internal-only rename to `ROW_BOUNDED` was deliberately deferred: no
|
|
103
|
+
* tool currently declares a scope, so the alias keeps this WI a
|
|
104
|
+
* one-file change (WI-0003661). Rename in a follow-up if the internal
|
|
105
|
+
* vocabulary starts to feel misleading.
|
|
106
|
+
*/
|
|
107
|
+
export declare const CAPABILITY_SCOPES: readonly ["ALL_RECORDS", "OWN_RECORDS"];
|
|
108
|
+
export type CapabilityScope = (typeof CAPABILITY_SCOPES)[number];
|
|
109
|
+
/**
|
|
110
|
+
* Zod schema for one row returned by `fnd_get_effective_capabilities()`.
|
|
111
|
+
* The RPC guarantees a `NOT NULL` `resource_group`, `operation`, and
|
|
112
|
+
* `scope`; `resource_name` may be `NULL` for a domain-level permission.
|
|
113
|
+
*
|
|
114
|
+
* Any structural drift here is a hard error — MCP MUST NOT accept an
|
|
115
|
+
* unknown scope value, empty operation, or malformed resource_group.
|
|
116
|
+
*/
|
|
117
|
+
export declare const effectiveCapabilityRowSchema: z.ZodObject<{
|
|
118
|
+
resource_group: z.ZodString;
|
|
119
|
+
resource_name: z.ZodNullable<z.ZodString>;
|
|
120
|
+
operation: z.ZodString;
|
|
121
|
+
scope: z.ZodEffects<z.ZodEnum<["ALL_RECORDS", "ROW_BOUNDED", "OWN_RECORDS"]>, "ALL_RECORDS" | "OWN_RECORDS", "ALL_RECORDS" | "ROW_BOUNDED" | "OWN_RECORDS">;
|
|
122
|
+
advisory: z.ZodOptional<z.ZodBoolean>;
|
|
123
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
124
|
+
resource_group: z.ZodString;
|
|
125
|
+
resource_name: z.ZodNullable<z.ZodString>;
|
|
126
|
+
operation: z.ZodString;
|
|
127
|
+
scope: z.ZodEffects<z.ZodEnum<["ALL_RECORDS", "ROW_BOUNDED", "OWN_RECORDS"]>, "ALL_RECORDS" | "OWN_RECORDS", "ALL_RECORDS" | "ROW_BOUNDED" | "OWN_RECORDS">;
|
|
128
|
+
advisory: z.ZodOptional<z.ZodBoolean>;
|
|
129
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
130
|
+
resource_group: z.ZodString;
|
|
131
|
+
resource_name: z.ZodNullable<z.ZodString>;
|
|
132
|
+
operation: z.ZodString;
|
|
133
|
+
scope: z.ZodEffects<z.ZodEnum<["ALL_RECORDS", "ROW_BOUNDED", "OWN_RECORDS"]>, "ALL_RECORDS" | "OWN_RECORDS", "ALL_RECORDS" | "ROW_BOUNDED" | "OWN_RECORDS">;
|
|
134
|
+
advisory: z.ZodOptional<z.ZodBoolean>;
|
|
135
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
136
|
+
export type EffectiveCapabilityRow = z.infer<typeof effectiveCapabilityRowSchema>;
|
|
137
|
+
export declare const effectiveCapabilityResponseSchema: z.ZodArray<z.ZodObject<{
|
|
138
|
+
resource_group: z.ZodString;
|
|
139
|
+
resource_name: z.ZodNullable<z.ZodString>;
|
|
140
|
+
operation: z.ZodString;
|
|
141
|
+
scope: z.ZodEffects<z.ZodEnum<["ALL_RECORDS", "ROW_BOUNDED", "OWN_RECORDS"]>, "ALL_RECORDS" | "OWN_RECORDS", "ALL_RECORDS" | "ROW_BOUNDED" | "OWN_RECORDS">;
|
|
142
|
+
advisory: z.ZodOptional<z.ZodBoolean>;
|
|
143
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
144
|
+
resource_group: z.ZodString;
|
|
145
|
+
resource_name: z.ZodNullable<z.ZodString>;
|
|
146
|
+
operation: z.ZodString;
|
|
147
|
+
scope: z.ZodEffects<z.ZodEnum<["ALL_RECORDS", "ROW_BOUNDED", "OWN_RECORDS"]>, "ALL_RECORDS" | "OWN_RECORDS", "ALL_RECORDS" | "ROW_BOUNDED" | "OWN_RECORDS">;
|
|
148
|
+
advisory: z.ZodOptional<z.ZodBoolean>;
|
|
149
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
150
|
+
resource_group: z.ZodString;
|
|
151
|
+
resource_name: z.ZodNullable<z.ZodString>;
|
|
152
|
+
operation: z.ZodString;
|
|
153
|
+
scope: z.ZodEffects<z.ZodEnum<["ALL_RECORDS", "ROW_BOUNDED", "OWN_RECORDS"]>, "ALL_RECORDS" | "OWN_RECORDS", "ALL_RECORDS" | "ROW_BOUNDED" | "OWN_RECORDS">;
|
|
154
|
+
advisory: z.ZodOptional<z.ZodBoolean>;
|
|
155
|
+
}, z.ZodTypeAny, "passthrough">>, "many">;
|
|
156
|
+
/**
|
|
157
|
+
* The declared capability requirement for a tool. A tool is authorised iff
|
|
158
|
+
* every requirement in its list is satisfied by the caller's effective
|
|
159
|
+
* capability set.
|
|
160
|
+
*
|
|
161
|
+
* The requirement is intentionally narrow — no wildcards, no "any op". A
|
|
162
|
+
* tool that reads and writes must declare both requirements. This keeps
|
|
163
|
+
* the authorisation graph auditable and cheap to reason about.
|
|
164
|
+
*/
|
|
165
|
+
export interface CapabilityRequirement {
|
|
166
|
+
/**
|
|
167
|
+
* Persist resource group (e.g. `WRM`). Compared case-insensitively
|
|
168
|
+
* against the RPC's `resource_group`.
|
|
169
|
+
*/
|
|
170
|
+
resourceGroup: string;
|
|
171
|
+
/**
|
|
172
|
+
* Persist resource name (e.g. `ROLES`). Compared case-insensitively
|
|
173
|
+
* against the RPC's `resource_name`. A domain-level row
|
|
174
|
+
* (`resource_name: null`) satisfies any resource in the same group.
|
|
175
|
+
*/
|
|
176
|
+
resourceName: string;
|
|
177
|
+
/** Operation code (e.g. `SELECT`). Compared case-insensitively. */
|
|
178
|
+
operation: string;
|
|
179
|
+
/**
|
|
180
|
+
* The minimum scope the tool's handler is safe with. `OWN_RECORDS`
|
|
181
|
+
* (the default) is satisfied by either `OWN_RECORDS` or `ALL_RECORDS`
|
|
182
|
+
* capabilities. `ALL_RECORDS` requires an `ALL_RECORDS` capability —
|
|
183
|
+
* an `OWN_RECORDS` capability is NOT sufficient, so an own-record
|
|
184
|
+
* grant can never silently become an all-record grant.
|
|
185
|
+
*/
|
|
186
|
+
scope?: CapabilityScope;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Immutable index over the caller's effective capability set. Answers
|
|
190
|
+
* "does the user hold capability X at scope Y?" in O(1). Domain-level
|
|
191
|
+
* rows (`resource_name: null`) are pre-expanded so table-specific lookups
|
|
192
|
+
* still hit the map.
|
|
193
|
+
*/
|
|
194
|
+
export declare class EffectiveCapabilityIndex {
|
|
195
|
+
/**
|
|
196
|
+
* Map keys are `${GROUP}|${NAME|*}|${OPERATION}` (uppercase). The value
|
|
197
|
+
* is the strongest scope observed for that tuple.
|
|
198
|
+
*
|
|
199
|
+
* Domain-level rows are stored with `resource_name = "*"` — table-level
|
|
200
|
+
* lookups probe both the specific name and the wildcard.
|
|
201
|
+
*/
|
|
202
|
+
private readonly index;
|
|
203
|
+
constructor(rows: EffectiveCapabilityRow[]);
|
|
204
|
+
/** Total number of effective (group, name, operation) rows held. */
|
|
205
|
+
get size(): number;
|
|
206
|
+
/**
|
|
207
|
+
* Return the strongest scope the caller holds for the (group, name,
|
|
208
|
+
* operation) tuple, or `undefined` if they hold no matching capability.
|
|
209
|
+
*
|
|
210
|
+
* A domain-level grant (`resource_name = null`) satisfies any resource
|
|
211
|
+
* in the same group.
|
|
212
|
+
*/
|
|
213
|
+
lookup(resourceGroup: string, resourceName: string, operation: string): CapabilityScope | undefined;
|
|
214
|
+
/**
|
|
215
|
+
* `true` iff the caller holds a capability that satisfies the
|
|
216
|
+
* requirement. `OWN_RECORDS` requirements are satisfied by any grant.
|
|
217
|
+
* `ALL_RECORDS` requirements are satisfied only by an `ALL_RECORDS`
|
|
218
|
+
* grant — an `OWN_RECORDS` grant does NOT silently upgrade.
|
|
219
|
+
*/
|
|
220
|
+
satisfies(requirement: CapabilityRequirement): boolean;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Error thrown when the RPC cannot be fetched, decoded, or validated.
|
|
224
|
+
* Callers MUST fail closed (expose no tools) on this error.
|
|
225
|
+
*/
|
|
226
|
+
export declare class EffectiveCapabilityError extends Error {
|
|
227
|
+
readonly cause?: unknown | undefined;
|
|
228
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
229
|
+
}
|
|
230
|
+
/** PostgREST path to the Persist RPC. */
|
|
231
|
+
export declare const EFFECTIVE_CAPABILITIES_RPC_PATH = "/db/v1/rpc/fnd_get_effective_capabilities";
|
|
232
|
+
/**
|
|
233
|
+
* Fetch the caller's effective capabilities from Persist, validate the
|
|
234
|
+
* response shape, and return an index ready for constant-time lookup.
|
|
235
|
+
*
|
|
236
|
+
* Throws {@link EffectiveCapabilityError} on any transport, decode, or
|
|
237
|
+
* validation failure. Callers MUST fail closed on that error — never fall
|
|
238
|
+
* back to a permissive default.
|
|
239
|
+
*/
|
|
240
|
+
export declare function fetchEffectiveCapabilities(apiClient: RaytioApiClient): Promise<EffectiveCapabilityIndex>;
|
|
241
|
+
//# sourceMappingURL=effective-capabilities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effective-capabilities.d.ts","sourceRoot":"","sources":["../../src/capabilities/effective-capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AAEH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB,wDAIzB,CAAC;AACX,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iBAAiB,yCAA0C,CAAC;AACzE,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAQjE;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;gCAuBzB,CAAC;AAEjB,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;;;yCAAwC,CAAC;AAEvF;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB;AAED;;;;;GAKG;AACH,qBAAa,wBAAwB;IACnC;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;gBAEzC,IAAI,EAAE,sBAAsB,EAAE;IAgB1C,oEAAoE;IACpE,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;;OAMG;IACH,MAAM,CACJ,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAChB,eAAe,GAAG,SAAS;IAW9B;;;;;OAKG;IACH,SAAS,CAAC,WAAW,EAAE,qBAAqB,GAAG,OAAO;CAWvD;AAED;;;GAGG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;IACpB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;gBAAzC,OAAO,EAAE,MAAM,EAAW,KAAK,CAAC,EAAE,OAAO,YAAA;CAItD;AAED,yCAAyC;AACzC,eAAO,MAAM,+BAA+B,8CACC,CAAC;AAE9C;;;;;;;GAOG;AACH,wBAAsB,0BAA0B,CAC9C,SAAS,EAAE,eAAe,GACzB,OAAO,CAAC,wBAAwB,CAAC,CAmBnC"}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Client and index for the supported Persist "effective capabilities" RPC
|
|
4
|
+
* (`public.fnd_get_effective_capabilities()`), owned by WI-0003499.
|
|
5
|
+
*
|
|
6
|
+
* The RPC returns one row per (resource_group, resource_name, operation)
|
|
7
|
+
* tuple that the authenticated user may exercise. MCP uses that set to
|
|
8
|
+
* authorise both tool advertisement (`ListTools`) and tool dispatch
|
|
9
|
+
* (`CallTool`).
|
|
10
|
+
*
|
|
11
|
+
* Row semantics — see `docs/mcp-effective-capabilities.md` in the Persist
|
|
12
|
+
* repository:
|
|
13
|
+
*
|
|
14
|
+
* - `resource_group` — 3-letter module code (e.g. `HRM`, `DSM`, `FAR`).
|
|
15
|
+
* Matches `resources[*].permission.resource_group` in the pinned MCP
|
|
16
|
+
* resource contract.
|
|
17
|
+
* - `resource_name` — resource-scoped code (e.g. `ACCESS_APPLICATIONS`),
|
|
18
|
+
* or `null` for a domain-level permission that applies to every
|
|
19
|
+
* resource in the group. Persist suppresses domain rows when
|
|
20
|
+
* table-specific entries exist for the same (group, operation) —
|
|
21
|
+
* MCP relies on that shadowing and does not re-check it here.
|
|
22
|
+
* - `operation` — one of `SELECT`, `INSERT`, `UPDATE`, `DELETE`, or a
|
|
23
|
+
* semantic operation seeded into the `HRM_PERMISSIONS_OPERATION`
|
|
24
|
+
* lookup.
|
|
25
|
+
* - `scope` — exactly one of `ALL_RECORDS` or `ROW_BOUNDED` on the wire.
|
|
26
|
+
* `ALL_RECORDS` means "verified unbounded HRM access". `ROW_BOUNDED`
|
|
27
|
+
* means "the HRM grant exists at OWN_RECORDS scope but real access is
|
|
28
|
+
* via ownership / ReBAC / row filters — treat as informational". A
|
|
29
|
+
* `NULL` scope is coerced to `ROW_BOUNDED` inside the RPC, never to
|
|
30
|
+
* unbounded. MCP normalises `ROW_BOUNDED` to the pre-existing internal
|
|
31
|
+
* alias `OWN_RECORDS` on ingress (see {@link CAPABILITY_SCOPES}); both
|
|
32
|
+
* names refer to the same "not verified unbounded" semantic, so the
|
|
33
|
+
* rest of the MCP code path — CapabilityRequirement, satisfies(),
|
|
34
|
+
* dispatch — is unchanged. Reconciled in WI-0003661.
|
|
35
|
+
* - `advisory` — `TRUE` iff `scope = ROW_BOUNDED`; `FALSE` for
|
|
36
|
+
* `ALL_RECORDS`. Redundant with `scope` today; accepted (optional) on
|
|
37
|
+
* the wire so a producer-side addition remains non-breaking.
|
|
38
|
+
*
|
|
39
|
+
* When the caller reaches the same capability at both `ALL_RECORDS` and
|
|
40
|
+
* `ROW_BOUNDED`, the RPC collapses the row to `ALL_RECORDS`. MCP relies
|
|
41
|
+
* on that guarantee — the client does not attempt its own scope merge.
|
|
42
|
+
*
|
|
43
|
+
* MCP fails closed on any of:
|
|
44
|
+
* - HTTP transport failure,
|
|
45
|
+
* - JSON that does not parse or does not match the schema below,
|
|
46
|
+
* - unknown values in `operation` or `scope`,
|
|
47
|
+
* - a required tool capability that is not present in the returned set.
|
|
48
|
+
*
|
|
49
|
+
* `OWN_RECORDS` MUST NEVER silently become `ALL_RECORDS` — the tool
|
|
50
|
+
* declaration carries the scope its handler is safe with, and a broader
|
|
51
|
+
* effective capability satisfies it; a narrower one does not.
|
|
52
|
+
*
|
|
53
|
+
* ---
|
|
54
|
+
*
|
|
55
|
+
* Deliberate deviation from Persist's interpretation invariant
|
|
56
|
+
* (WI-0003661):
|
|
57
|
+
*
|
|
58
|
+
* Persist's contract states that "absence means unknown — ask the
|
|
59
|
+
* database" and that `ROW_BOUNDED` rows are informational, not a
|
|
60
|
+
* deny-side authorisation gate. MCP intentionally departs from that on
|
|
61
|
+
* the tool-advertisement path:
|
|
62
|
+
*
|
|
63
|
+
* - {@link filterToolsByCapabilities} HIDES any tool whose declared
|
|
64
|
+
* capabilities are missing from the caller's returned set.
|
|
65
|
+
* - {@link EffectiveCapabilityIndex.satisfies} returns `false` when the
|
|
66
|
+
* caller holds no matching row.
|
|
67
|
+
*
|
|
68
|
+
* The rationale is fail-closed advertisement: exposing a tool a caller
|
|
69
|
+
* cannot execute is a poor UX and leaks the shape of the denied surface.
|
|
70
|
+
* The database still enforces at call time — HRM permissions, ownership,
|
|
71
|
+
* and ReBAC all continue to gate the underlying query — so any tool
|
|
72
|
+
* whose actual access path is via ownership or ReBAC (never surfaced by
|
|
73
|
+
* this RPC) will be hidden until an explicit capability is granted or
|
|
74
|
+
* the tool's declaration is loosened. This is a design choice, not an
|
|
75
|
+
* oversight; revisit if MCP ever needs to advertise
|
|
76
|
+
* ownership/ReBAC-only tools.
|
|
77
|
+
*/
|
|
78
|
+
/**
|
|
79
|
+
* Wire-side scope vocabulary the RPC emits (per Persist's
|
|
80
|
+
* `docs/mcp-effective-capabilities.md`, WI-0003499):
|
|
81
|
+
*
|
|
82
|
+
* - `ALL_RECORDS` — verified unbounded HRM access.
|
|
83
|
+
* - `ROW_BOUNDED` — advisory HRM row; actual access via
|
|
84
|
+
* ownership / ReBAC / row filters.
|
|
85
|
+
*
|
|
86
|
+
* The legacy `OWN_RECORDS` label — the pre-rename name for `ROW_BOUNDED`
|
|
87
|
+
* — is still accepted for transitional deployments of Persist; both
|
|
88
|
+
* legacy and current values are normalised to the internal
|
|
89
|
+
* `OWN_RECORDS` alias.
|
|
90
|
+
*/
|
|
91
|
+
export const WIRE_CAPABILITY_SCOPES = [
|
|
92
|
+
'ALL_RECORDS',
|
|
93
|
+
'ROW_BOUNDED',
|
|
94
|
+
'OWN_RECORDS',
|
|
95
|
+
];
|
|
96
|
+
/**
|
|
97
|
+
* MCP-internal scope vocabulary used by {@link CapabilityRequirement}
|
|
98
|
+
* declarations and by the scope stored inside
|
|
99
|
+
* {@link EffectiveCapabilityIndex}. Persist's `ROW_BOUNDED` is
|
|
100
|
+
* normalised to `OWN_RECORDS` on ingress — the two names encode the
|
|
101
|
+
* same semantic ("not a verified unbounded HRM grant — enforcement of
|
|
102
|
+
* access is via ownership / ReBAC / row filters at call time").
|
|
103
|
+
*
|
|
104
|
+
* Internal-only rename to `ROW_BOUNDED` was deliberately deferred: no
|
|
105
|
+
* tool currently declares a scope, so the alias keeps this WI a
|
|
106
|
+
* one-file change (WI-0003661). Rename in a follow-up if the internal
|
|
107
|
+
* vocabulary starts to feel misleading.
|
|
108
|
+
*/
|
|
109
|
+
export const CAPABILITY_SCOPES = ['ALL_RECORDS', 'OWN_RECORDS'];
|
|
110
|
+
const wireScopeSchema = z
|
|
111
|
+
.enum(WIRE_CAPABILITY_SCOPES)
|
|
112
|
+
.transform((wire) => wire === 'ROW_BOUNDED' ? 'OWN_RECORDS' : wire);
|
|
113
|
+
/**
|
|
114
|
+
* Zod schema for one row returned by `fnd_get_effective_capabilities()`.
|
|
115
|
+
* The RPC guarantees a `NOT NULL` `resource_group`, `operation`, and
|
|
116
|
+
* `scope`; `resource_name` may be `NULL` for a domain-level permission.
|
|
117
|
+
*
|
|
118
|
+
* Any structural drift here is a hard error — MCP MUST NOT accept an
|
|
119
|
+
* unknown scope value, empty operation, or malformed resource_group.
|
|
120
|
+
*/
|
|
121
|
+
export const effectiveCapabilityRowSchema = z
|
|
122
|
+
.object({
|
|
123
|
+
resource_group: z
|
|
124
|
+
.string()
|
|
125
|
+
.min(1)
|
|
126
|
+
.max(16)
|
|
127
|
+
.describe('Persist resource group code, e.g. "HRM", "DSM", "FAR"'),
|
|
128
|
+
resource_name: z
|
|
129
|
+
.string()
|
|
130
|
+
.min(1)
|
|
131
|
+
.nullable()
|
|
132
|
+
.describe('Resource-specific code, or null for a domain-level permission'),
|
|
133
|
+
operation: z
|
|
134
|
+
.string()
|
|
135
|
+
.min(1)
|
|
136
|
+
.describe('Operation code, e.g. "SELECT", "INSERT", "UPDATE", "DELETE"'),
|
|
137
|
+
scope: wireScopeSchema,
|
|
138
|
+
// Redundant with `scope` — always true when scope=ROW_BOUNDED, false
|
|
139
|
+
// when scope=ALL_RECORDS. Not consumed by MCP today but declared so
|
|
140
|
+
// producer additions do not silently drop; optional so older Persist
|
|
141
|
+
// deployments that predate the advisory column still parse.
|
|
142
|
+
advisory: z.boolean().optional(),
|
|
143
|
+
})
|
|
144
|
+
.passthrough();
|
|
145
|
+
export const effectiveCapabilityResponseSchema = z.array(effectiveCapabilityRowSchema);
|
|
146
|
+
/**
|
|
147
|
+
* Immutable index over the caller's effective capability set. Answers
|
|
148
|
+
* "does the user hold capability X at scope Y?" in O(1). Domain-level
|
|
149
|
+
* rows (`resource_name: null`) are pre-expanded so table-specific lookups
|
|
150
|
+
* still hit the map.
|
|
151
|
+
*/
|
|
152
|
+
export class EffectiveCapabilityIndex {
|
|
153
|
+
/**
|
|
154
|
+
* Map keys are `${GROUP}|${NAME|*}|${OPERATION}` (uppercase). The value
|
|
155
|
+
* is the strongest scope observed for that tuple.
|
|
156
|
+
*
|
|
157
|
+
* Domain-level rows are stored with `resource_name = "*"` — table-level
|
|
158
|
+
* lookups probe both the specific name and the wildcard.
|
|
159
|
+
*/
|
|
160
|
+
index;
|
|
161
|
+
constructor(rows) {
|
|
162
|
+
this.index = new Map();
|
|
163
|
+
for (const row of rows) {
|
|
164
|
+
const group = row.resource_group.toUpperCase();
|
|
165
|
+
const name = (row.resource_name ?? '*').toUpperCase();
|
|
166
|
+
const op = row.operation.toUpperCase();
|
|
167
|
+
const key = `${group}|${name}|${op}`;
|
|
168
|
+
const previous = this.index.get(key);
|
|
169
|
+
// ALL_RECORDS always wins over OWN_RECORDS. The RPC already
|
|
170
|
+
// collapses duplicates but we defend in depth in case the row set
|
|
171
|
+
// is ever assembled from multiple sources.
|
|
172
|
+
if (previous === 'ALL_RECORDS')
|
|
173
|
+
continue;
|
|
174
|
+
this.index.set(key, row.scope);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** Total number of effective (group, name, operation) rows held. */
|
|
178
|
+
get size() {
|
|
179
|
+
return this.index.size;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Return the strongest scope the caller holds for the (group, name,
|
|
183
|
+
* operation) tuple, or `undefined` if they hold no matching capability.
|
|
184
|
+
*
|
|
185
|
+
* A domain-level grant (`resource_name = null`) satisfies any resource
|
|
186
|
+
* in the same group.
|
|
187
|
+
*/
|
|
188
|
+
lookup(resourceGroup, resourceName, operation) {
|
|
189
|
+
const group = resourceGroup.toUpperCase();
|
|
190
|
+
const name = resourceName.toUpperCase();
|
|
191
|
+
const op = operation.toUpperCase();
|
|
192
|
+
const specific = this.index.get(`${group}|${name}|${op}`);
|
|
193
|
+
const domain = this.index.get(`${group}|*|${op}`);
|
|
194
|
+
if (specific === 'ALL_RECORDS' || domain === 'ALL_RECORDS')
|
|
195
|
+
return 'ALL_RECORDS';
|
|
196
|
+
if (specific === 'OWN_RECORDS' || domain === 'OWN_RECORDS')
|
|
197
|
+
return 'OWN_RECORDS';
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* `true` iff the caller holds a capability that satisfies the
|
|
202
|
+
* requirement. `OWN_RECORDS` requirements are satisfied by any grant.
|
|
203
|
+
* `ALL_RECORDS` requirements are satisfied only by an `ALL_RECORDS`
|
|
204
|
+
* grant — an `OWN_RECORDS` grant does NOT silently upgrade.
|
|
205
|
+
*/
|
|
206
|
+
satisfies(requirement) {
|
|
207
|
+
const held = this.lookup(requirement.resourceGroup, requirement.resourceName, requirement.operation);
|
|
208
|
+
if (!held)
|
|
209
|
+
return false;
|
|
210
|
+
const need = requirement.scope ?? 'OWN_RECORDS';
|
|
211
|
+
if (need === 'ALL_RECORDS')
|
|
212
|
+
return held === 'ALL_RECORDS';
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Error thrown when the RPC cannot be fetched, decoded, or validated.
|
|
218
|
+
* Callers MUST fail closed (expose no tools) on this error.
|
|
219
|
+
*/
|
|
220
|
+
export class EffectiveCapabilityError extends Error {
|
|
221
|
+
cause;
|
|
222
|
+
constructor(message, cause) {
|
|
223
|
+
super(message);
|
|
224
|
+
this.cause = cause;
|
|
225
|
+
this.name = 'EffectiveCapabilityError';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/** PostgREST path to the Persist RPC. */
|
|
229
|
+
export const EFFECTIVE_CAPABILITIES_RPC_PATH = '/db/v1/rpc/fnd_get_effective_capabilities';
|
|
230
|
+
/**
|
|
231
|
+
* Fetch the caller's effective capabilities from Persist, validate the
|
|
232
|
+
* response shape, and return an index ready for constant-time lookup.
|
|
233
|
+
*
|
|
234
|
+
* Throws {@link EffectiveCapabilityError} on any transport, decode, or
|
|
235
|
+
* validation failure. Callers MUST fail closed on that error — never fall
|
|
236
|
+
* back to a permissive default.
|
|
237
|
+
*/
|
|
238
|
+
export async function fetchEffectiveCapabilities(apiClient) {
|
|
239
|
+
let raw;
|
|
240
|
+
try {
|
|
241
|
+
raw = await apiClient.post(EFFECTIVE_CAPABILITIES_RPC_PATH, {});
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
throw new EffectiveCapabilityError(`effective capabilities RPC failed: ${error.message}`, error);
|
|
245
|
+
}
|
|
246
|
+
const parsed = effectiveCapabilityResponseSchema.safeParse(raw);
|
|
247
|
+
if (!parsed.success) {
|
|
248
|
+
throw new EffectiveCapabilityError(`effective capabilities response failed validation: ${parsed.error.message}`);
|
|
249
|
+
}
|
|
250
|
+
return new EffectiveCapabilityIndex(parsed.data);
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=effective-capabilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effective-capabilities.js","sourceRoot":"","sources":["../../src/capabilities/effective-capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,aAAa;IACb,aAAa;IACb,aAAa;CACL,CAAC;AAGX;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,aAAa,EAAE,aAAa,CAAU,CAAC;AAGzE,MAAM,eAAe,GAAG,CAAC;KACtB,IAAI,CAAC,sBAAsB,CAAC;KAC5B,SAAS,CAAC,CAAC,IAAI,EAAmB,EAAE,CACnC,IAAI,KAAK,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAC9C,CAAC;AAEJ;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC;KAC1C,MAAM,CAAC;IACN,cAAc,EAAE,CAAC;SACd,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,EAAE,CAAC;SACP,QAAQ,CAAC,uDAAuD,CAAC;IACpE,aAAa,EAAE,CAAC;SACb,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,EAAE;SACV,QAAQ,CAAC,+DAA+D,CAAC;IAC5E,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,6DAA6D,CAAC;IAC1E,KAAK,EAAE,eAAe;IACtB,qEAAqE;IACrE,oEAAoE;IACpE,qEAAqE;IACrE,4DAA4D;IAC5D,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CACjC,CAAC;KACD,WAAW,EAAE,CAAC;AAIjB,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;AAmCvF;;;;;GAKG;AACH,MAAM,OAAO,wBAAwB;IACnC;;;;;;OAMG;IACc,KAAK,CAA+B;IAErD,YAAY,IAA8B;QACxC,IAAI,CAAC,KAAK,GAAG,IAAI,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,GAAG,CAAC,cAAc,CAAC,WAAW,EAAE,CAAC;YAC/C,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,aAAa,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;YACtD,MAAM,EAAE,GAAG,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC;YACvC,MAAM,GAAG,GAAG,GAAG,KAAK,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;YACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACrC,4DAA4D;YAC5D,kEAAkE;YAClE,2CAA2C;YAC3C,IAAI,QAAQ,KAAK,aAAa;gBAAE,SAAS;YACzC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,oEAAoE;IACpE,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CACJ,aAAqB,EACrB,YAAoB,EACpB,SAAiB;QAEjB,MAAM,KAAK,GAAG,aAAa,CAAC,WAAW,EAAE,CAAC;QAC1C,MAAM,IAAI,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC;QACxC,MAAM,EAAE,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;QAC1D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,MAAM,EAAE,EAAE,CAAC,CAAC;QAClD,IAAI,QAAQ,KAAK,aAAa,IAAI,MAAM,KAAK,aAAa;YAAE,OAAO,aAAa,CAAC;QACjF,IAAI,QAAQ,KAAK,aAAa,IAAI,MAAM,KAAK,aAAa;YAAE,OAAO,aAAa,CAAC;QACjF,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;;OAKG;IACH,SAAS,CAAC,WAAkC;QAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CACtB,WAAW,CAAC,aAAa,EACzB,WAAW,CAAC,YAAY,EACxB,WAAW,CAAC,SAAS,CACtB,CAAC;QACF,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxB,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,IAAI,aAAa,CAAC;QAChD,IAAI,IAAI,KAAK,aAAa;YAAE,OAAO,IAAI,KAAK,aAAa,CAAC;QAC1D,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACX;IAAtC,YAAY,OAAe,EAAW,KAAe;QACnD,KAAK,CAAC,OAAO,CAAC,CAAC;QADqB,UAAK,GAAL,KAAK,CAAU;QAEnD,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF;AAED,yCAAyC;AACzC,MAAM,CAAC,MAAM,+BAA+B,GAC1C,2CAA2C,CAAC;AAE9C;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,SAA0B;IAE1B,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,SAAS,CAAC,IAAI,CAAU,+BAA+B,EAAE,EAAE,CAAC,CAAC;IAC3E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,wBAAwB,CAChC,sCAAuC,KAAe,CAAC,OAAO,EAAE,EAChE,KAAK,CACN,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,iCAAiC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAChE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,wBAAwB,CAChC,sDAAsD,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAC7E,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,wBAAwB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACnD,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { PersistContractError, loadPersistResourceContract, } from './contracts/
|
|
|
8
8
|
import { CognitoAuth } from './raytio/auth.js';
|
|
9
9
|
import { RaytioApiClient } from './raytio/api-client.js';
|
|
10
10
|
import { resolveDomains } from './tools/domains.js';
|
|
11
|
-
import { resolveToolDomains } from './authorization.js';
|
|
11
|
+
import { filterToolsByCapabilities, resolveToolDomains } from './authorization.js';
|
|
12
12
|
import { dispatchToolCall } from './tools/dispatch.js';
|
|
13
13
|
import { PACKAGE_VERSION } from './version.js';
|
|
14
14
|
// Load configuration
|
|
@@ -42,14 +42,23 @@ const auth = new CognitoAuth({
|
|
|
42
42
|
});
|
|
43
43
|
const apiClient = new RaytioApiClient(config.raytio.apiBaseUrl, auth);
|
|
44
44
|
const ctx = { apiClient, auth, config };
|
|
45
|
-
// Resolve tool domains
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
45
|
+
// Resolve tool domains AND the caller's effective Persist capabilities.
|
|
46
|
+
// The static RAYTIO_TOOL_DOMAINS override may only restrict the user's
|
|
47
|
+
// resolved roles — it never grants access to a domain the user does not
|
|
48
|
+
// already hold. Any failure to resolve roles or discover capabilities
|
|
49
|
+
// fails closed (expose no tools) rather than falling back to the full
|
|
50
|
+
// surface.
|
|
49
51
|
const authorization = await resolveToolDomains(apiClient, process.env.RAYTIO_TOOL_DOMAINS);
|
|
50
|
-
const
|
|
52
|
+
const domainTools = authorization.authorized
|
|
51
53
|
? resolveDomains(authorization.domainSpec)
|
|
52
54
|
: [];
|
|
55
|
+
// Layer the effective-capability filter on top of the role-domain filter.
|
|
56
|
+
// Tools without a declared `capabilities` field pass through unchanged —
|
|
57
|
+
// they remain gated by the role-domain filter until they are migrated
|
|
58
|
+
// onto the pinned Persist resource contract (WI-0003500 onward).
|
|
59
|
+
const allTools = authorization.authorized && authorization.capabilities
|
|
60
|
+
? filterToolsByCapabilities(domainTools, authorization.capabilities)
|
|
61
|
+
: domainTools;
|
|
53
62
|
console.error(`Raytio MCP: exposing ${allTools.length} tools` +
|
|
54
63
|
(authorization.domainSpec
|
|
55
64
|
? ` (domains: ${authorization.domainSpec})`
|
|
@@ -74,10 +83,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
74
83
|
};
|
|
75
84
|
});
|
|
76
85
|
// Handle tool calls — dispatch centrally so every input is parsed through
|
|
77
|
-
// its Zod schema
|
|
86
|
+
// its Zod schema and every declared capability is re-checked before the
|
|
87
|
+
// tool handler is invoked. Passing the capability index through here
|
|
88
|
+
// closes the gap where a client discovers a tool name out-of-band or via
|
|
89
|
+
// a stale ListTools result.
|
|
78
90
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
79
91
|
const { name, arguments: args = {} } = request.params;
|
|
80
|
-
const response = await dispatchToolCall(allTools, name, args, ctx);
|
|
92
|
+
const response = await dispatchToolCall(allTools, name, args, ctx, authorization.capabilities);
|
|
81
93
|
return {
|
|
82
94
|
content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
|
|
83
95
|
...(response.success ? {} : { isError: true }),
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/dispatch.d.ts
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
|
+
import { EffectiveCapabilityIndex } from '../capabilities/effective-capabilities.js';
|
|
1
2
|
import { Tool, ToolContext } from './registry.js';
|
|
2
3
|
import { ToolResponse } from './types.js';
|
|
3
4
|
export declare const INPUT_VALIDATION_ERROR = "INPUT_VALIDATION_ERROR";
|
|
4
5
|
export declare const UNKNOWN_TOOL = "UNKNOWN_TOOL";
|
|
5
6
|
export declare const TOOL_ERROR = "TOOL_ERROR";
|
|
7
|
+
export declare const CAPABILITY_DENIED = "CAPABILITY_DENIED";
|
|
6
8
|
/**
|
|
7
9
|
* Central dispatch entry point for every MCP `tools/call` request.
|
|
8
10
|
*
|
|
9
11
|
* Guarantees:
|
|
10
12
|
* - The advertised Zod schema is parsed before the tool handler runs,
|
|
11
13
|
* so handlers only ever see structurally valid input.
|
|
14
|
+
* - When an effective-capability index is provided, every declared
|
|
15
|
+
* capability on the tool is re-checked at dispatch time. A missing
|
|
16
|
+
* capability aborts the call with `CAPABILITY_DENIED` — this is
|
|
17
|
+
* belt-and-braces protection against a caller that discovered the
|
|
18
|
+
* tool name out-of-band or through a stale ListTools result.
|
|
12
19
|
* - Unknown tool names return a stable `UNKNOWN_TOOL` error rather than
|
|
13
20
|
* passing an arbitrary name deeper into the stack.
|
|
14
21
|
* - Handler exceptions are converted to structured `TOOL_ERROR`
|
|
15
22
|
* responses so the MCP transport always sees a well-formed reply.
|
|
16
23
|
*/
|
|
17
|
-
export declare function dispatchToolCall(tools: Tool[], name: string, args: unknown, ctx: ToolContext): Promise<ToolResponse<unknown>>;
|
|
24
|
+
export declare function dispatchToolCall(tools: Tool[], name: string, args: unknown, ctx: ToolContext, capabilities?: EffectiveCapabilityIndex): Promise<ToolResponse<unknown>>;
|
|
18
25
|
//# sourceMappingURL=dispatch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/tools/dispatch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAiB,YAAY,EAAE,MAAM,YAAY,CAAC;AAEzD,eAAO,MAAM,sBAAsB,2BAA2B,CAAC;AAC/D,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAC3C,eAAO,MAAM,UAAU,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/tools/dispatch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,2CAA2C,CAAC;AACrF,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAiB,YAAY,EAAE,MAAM,YAAY,CAAC;AAEzD,eAAO,MAAM,sBAAsB,2BAA2B,CAAC;AAC/D,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAC3C,eAAO,MAAM,UAAU,eAAe,CAAC;AACvC,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AAErD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,IAAI,EAAE,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,EACb,GAAG,EAAE,WAAW,EAChB,YAAY,CAAC,EAAE,wBAAwB,GACtC,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAsChC"}
|
package/dist/tools/dispatch.js
CHANGED
|
@@ -2,22 +2,37 @@ import { errorResponse } from './types.js';
|
|
|
2
2
|
export const INPUT_VALIDATION_ERROR = 'INPUT_VALIDATION_ERROR';
|
|
3
3
|
export const UNKNOWN_TOOL = 'UNKNOWN_TOOL';
|
|
4
4
|
export const TOOL_ERROR = 'TOOL_ERROR';
|
|
5
|
+
export const CAPABILITY_DENIED = 'CAPABILITY_DENIED';
|
|
5
6
|
/**
|
|
6
7
|
* Central dispatch entry point for every MCP `tools/call` request.
|
|
7
8
|
*
|
|
8
9
|
* Guarantees:
|
|
9
10
|
* - The advertised Zod schema is parsed before the tool handler runs,
|
|
10
11
|
* so handlers only ever see structurally valid input.
|
|
12
|
+
* - When an effective-capability index is provided, every declared
|
|
13
|
+
* capability on the tool is re-checked at dispatch time. A missing
|
|
14
|
+
* capability aborts the call with `CAPABILITY_DENIED` — this is
|
|
15
|
+
* belt-and-braces protection against a caller that discovered the
|
|
16
|
+
* tool name out-of-band or through a stale ListTools result.
|
|
11
17
|
* - Unknown tool names return a stable `UNKNOWN_TOOL` error rather than
|
|
12
18
|
* passing an arbitrary name deeper into the stack.
|
|
13
19
|
* - Handler exceptions are converted to structured `TOOL_ERROR`
|
|
14
20
|
* responses so the MCP transport always sees a well-formed reply.
|
|
15
21
|
*/
|
|
16
|
-
export async function dispatchToolCall(tools, name, args, ctx) {
|
|
22
|
+
export async function dispatchToolCall(tools, name, args, ctx, capabilities) {
|
|
17
23
|
const tool = tools.find((t) => t.name === name);
|
|
18
24
|
if (!tool) {
|
|
19
25
|
return errorResponse(UNKNOWN_TOOL, `Unknown tool: ${name}`);
|
|
20
26
|
}
|
|
27
|
+
if (capabilities && tool.capabilities && tool.capabilities.length > 0) {
|
|
28
|
+
const missing = tool.capabilities.filter((req) => !capabilities.satisfies(req));
|
|
29
|
+
if (missing.length > 0) {
|
|
30
|
+
const detail = missing
|
|
31
|
+
.map((r) => `${r.resourceGroup}.${r.resourceName}#${r.operation}@${r.scope ?? 'OWN_RECORDS'}`)
|
|
32
|
+
.join(', ');
|
|
33
|
+
return errorResponse(CAPABILITY_DENIED, `Tool "${name}" requires capabilities the authenticated user does not hold: ${detail}`, { missing });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
21
36
|
const rawInput = args ?? {};
|
|
22
37
|
const parsed = tool.inputSchema.safeParse(rawInput);
|
|
23
38
|
if (!parsed.success) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/tools/dispatch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/tools/dispatch.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAgB,MAAM,YAAY,CAAC;AAEzD,MAAM,CAAC,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AAC/D,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAC;AAC3C,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAC;AACvC,MAAM,CAAC,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AAErD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,KAAa,EACb,IAAY,EACZ,IAAa,EACb,GAAgB,EAChB,YAAuC;IAEvC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAChD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,aAAa,CAAC,YAAY,EAAE,iBAAiB,IAAI,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,IAAI,YAAY,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtE,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QAChF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,OAAO;iBACnB,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CACJ,GAAG,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,KAAK,IAAI,aAAa,EAAE,CACpF;iBACA,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,OAAO,aAAa,CAClB,iBAAiB,EACjB,SAAS,IAAI,iEAAiE,MAAM,EAAE,EACtF,EAAE,OAAO,EAAE,CACZ,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACpD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,aAAa,CAClB,sBAAsB,EACtB,+BAA+B,IAAI,MAAM,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EACvE,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAChC,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,OAAO,aAAa,CAAC,UAAU,EAAE,KAAK,EAAE,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACpE,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,KAAiB;IACvC,OAAO,KAAK,CAAC,MAAM;SAChB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QACjE,OAAO,GAAG,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;IACrC,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { RaytioApiClient } from '../raytio/api-client.js';
|
|
3
3
|
import { CognitoAuth } from '../raytio/auth.js';
|
|
4
4
|
import { Config } from '../config.js';
|
|
5
|
+
import { CapabilityRequirement } from '../capabilities/effective-capabilities.js';
|
|
5
6
|
import { ToolResponse } from './types.js';
|
|
6
7
|
export interface ToolContext {
|
|
7
8
|
apiClient: RaytioApiClient;
|
|
@@ -13,5 +14,17 @@ export interface Tool {
|
|
|
13
14
|
description: string;
|
|
14
15
|
inputSchema: z.ZodTypeAny;
|
|
15
16
|
handler: (args: unknown, ctx: ToolContext) => Promise<ToolResponse<unknown>>;
|
|
17
|
+
/**
|
|
18
|
+
* Persist capabilities the caller MUST hold for this tool to be
|
|
19
|
+
* advertised or dispatched. Every requirement in the list must be
|
|
20
|
+
* satisfied — the tool acts as if the caller holds their intersection.
|
|
21
|
+
*
|
|
22
|
+
* Tools without an explicit `capabilities` field are not gated by the
|
|
23
|
+
* effective-capability check and rely on the coarser role-domain
|
|
24
|
+
* filter only. As tools migrate onto the pinned Persist resource
|
|
25
|
+
* contract (WI-0003500 onward), each new/regenerated tool is expected
|
|
26
|
+
* to declare its capabilities.
|
|
27
|
+
*/
|
|
28
|
+
capabilities?: readonly CapabilityRequirement[];
|
|
16
29
|
}
|
|
17
30
|
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/tools/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,eAAe,CAAC;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,CAAC,CAAC,UAAU,CAAC;IAC1B,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/tools/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,MAAM,2CAA2C,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,eAAe,CAAC;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,CAAC,CAAC,UAAU,CAAC;IAC1B,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7E;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;CACjD"}
|