@astrasyncai/verification-gateway 2.4.14 → 2.5.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/dist/adapters/express.js +107 -15
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +107 -15
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +75 -53
- package/dist/adapters/mcp.d.ts +75 -53
- package/dist/adapters/mcp.js +128 -23
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +128 -23
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.js +24 -14
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +24 -14
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.js +23 -13
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +23 -13
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/browser/background.js +23 -13
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +23 -13
- package/dist/browser/background.mjs.map +1 -1
- package/dist/cursor/extension.js +23 -13
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +23 -13
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/gateway/gateway.js +23 -13
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +23 -13
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/index.d.mts +32 -2
- package/dist/index.d.ts +32 -2
- package/dist/index.js +180 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +177 -39
- package/dist/index.mjs.map +1 -1
- package/dist/registration/index.js +7 -7
- package/dist/registration/index.js.map +1 -1
- package/dist/registration/index.mjs +7 -7
- package/dist/registration/index.mjs.map +1 -1
- package/dist/ui/index.js +2 -2
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/index.mjs +2 -2
- package/dist/ui/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/adapters/mcp.d.mts
CHANGED
|
@@ -85,7 +85,7 @@ interface ParsedMcpRequest {
|
|
|
85
85
|
* (canonical SDK location) OR `params.arguments.purpose` (legacy /
|
|
86
86
|
* conventional callers). The discriminator is on `purposeSourceFromBody`.
|
|
87
87
|
* Adapter combines this with the `X-Astra-Purpose` header (header wins)
|
|
88
|
-
* before mapping; final fallback at `mcpToPdlss` is `
|
|
88
|
+
* before mapping; final fallback at `mcpToPdlss` is `undefined`.
|
|
89
89
|
*/
|
|
90
90
|
purposeFromBody?: string;
|
|
91
91
|
/** Which body location resolved `purposeFromBody`. */
|
|
@@ -120,60 +120,45 @@ declare function parseMcpJsonRpc(body: unknown): ParsedMcpRequest | null;
|
|
|
120
120
|
/**
|
|
121
121
|
* PDLSS mapping for an MCP request. The platform's PDLSS taxonomy is
|
|
122
122
|
* `purpose / action / resource`; for MCP traffic the audit-useful dimensions
|
|
123
|
-
* are the JSON-RPC method and (for `tools/call`) the tool name.
|
|
124
|
-
* dashboards and audits can correlate consistently across cohort-3 partners.
|
|
123
|
+
* are the JSON-RPC method and (for `tools/call`) the tool name.
|
|
125
124
|
*
|
|
126
|
-
*
|
|
127
|
-
* - `
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
125
|
+
* v2.5.0 rules:
|
|
126
|
+
* - `resource` defaults to the HTTP request path (e.g. `/mcp`). Per-tool
|
|
127
|
+
* overrides via `toolGates` config let merchants map tools to specific
|
|
128
|
+
* backend resources (e.g. `/api/catalog`).
|
|
129
|
+
* - `purpose` defaults to `undefined` (backend evaluator applies
|
|
130
|
+
* skip-when-undefined). Per-tool overrides via `toolGates` config let
|
|
131
|
+
* merchants declare the semantic purpose each tool fulfils.
|
|
132
|
+
* - `action` is the bare tool name for `tools/call`, the JSON-RPC method
|
|
133
|
+
* otherwise. Header/body overrides available.
|
|
133
134
|
*/
|
|
134
135
|
interface McpPdlssMapping {
|
|
135
|
-
purpose: string;
|
|
136
|
+
purpose: string | undefined;
|
|
136
137
|
action: string;
|
|
137
138
|
resource: string;
|
|
138
|
-
|
|
139
|
-
* Round-13 (R13-1 / R13-2): the resolution channel for purpose and
|
|
140
|
-
* action — disjoint enums sharing the same structure per
|
|
141
|
-
* `feedback_symmetric_fallbacks_for_symmetric_concepts.md`. Adapters
|
|
142
|
-
* log both at debug level so partners can confirm header / body
|
|
143
|
-
* pass-through, support can triage tickets, and we can watch the
|
|
144
|
-
* `default_*` / `transport_layer` decay over time as integrations
|
|
145
|
-
* mature.
|
|
146
|
-
*
|
|
147
|
-
* Round-12 (F19) used a narrower `purposeSource: 'header' |
|
|
148
|
-
* 'tool_argument' | 'default_mcp_invoke'` — round-13 widens to also
|
|
149
|
-
* carry the `_meta` source distinct from `tool_argument`, so the round-13
|
|
150
|
-
* R13-1 fallback (which now reads both `_meta.astrasync.purpose` AND
|
|
151
|
-
* `params.arguments.purpose`) can report WHICH body location resolved.
|
|
152
|
-
*/
|
|
153
|
-
purposeSource: 'header' | 'meta' | 'tool_argument' | 'default_mcp_invoke';
|
|
139
|
+
purposeSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | undefined;
|
|
154
140
|
actionSource: 'header' | 'meta' | 'tool_argument' | 'transport_layer';
|
|
155
141
|
}
|
|
156
142
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
143
|
+
* v2.5.0 — PDLSS field derivation for MCP requests.
|
|
144
|
+
*
|
|
145
|
+
* Purpose precedence:
|
|
146
|
+
* - If `toolGate.purpose` is provided, it is authoritative (header/body ignored).
|
|
147
|
+
* - Otherwise: header → body `_meta` → body `arguments` → `undefined`.
|
|
162
148
|
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* 3. `params.arguments.<concept>` body field (legacy / conventional)
|
|
166
|
-
* 4. Transport-layer default:
|
|
167
|
-
* purpose → 'mcp_invoke'
|
|
168
|
-
* action → '<method>:<toolName>' (or just '<method>')
|
|
149
|
+
* Resource precedence:
|
|
150
|
+
* - `toolGate.resource` if provided, else `requestPath`.
|
|
169
151
|
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* `
|
|
152
|
+
* Action precedence (unchanged from v2.4.14):
|
|
153
|
+
* - header → body `_meta` → body `arguments` → bare tool name / method.
|
|
154
|
+
*
|
|
155
|
+
* @param requestPath The HTTP request path (e.g. '/mcp'). Required.
|
|
156
|
+
* @param toolGate Resolved per-tool config from `toolGates`, if present.
|
|
175
157
|
*/
|
|
176
|
-
declare function mcpToPdlss(parsed: ParsedMcpRequest, headerPurpose?: string, headerAction?: string
|
|
158
|
+
declare function mcpToPdlss(parsed: ParsedMcpRequest, requestPath: string, headerPurpose?: string, headerAction?: string, toolGate?: {
|
|
159
|
+
purpose?: string;
|
|
160
|
+
resource?: string;
|
|
161
|
+
}): McpPdlssMapping;
|
|
177
162
|
/**
|
|
178
163
|
* Recommended minimum access level per method type. The MCP middleware uses
|
|
179
164
|
* this to split low-risk handshake / introspection traffic from high-risk
|
|
@@ -200,10 +185,9 @@ declare function mcpRiskTier(parsed: ParsedMcpRequest): AccessLevel;
|
|
|
200
185
|
* from `tools/call start_checkout` (high risk). This middleware peels
|
|
201
186
|
* the JSON-RPC body and applies a per-method risk tier.
|
|
202
187
|
*
|
|
203
|
-
* (b) **PDLSS mapping**.
|
|
204
|
-
* `
|
|
205
|
-
*
|
|
206
|
-
* for the exact mapping.
|
|
188
|
+
* (b) **PDLSS mapping**. Derives purpose, action, and resource from
|
|
189
|
+
* `toolGates` config and request context. See `transport/mcp-server.ts`
|
|
190
|
+
* `mcpToPdlss()` for the exact mapping.
|
|
207
191
|
*
|
|
208
192
|
* (c) **Inner-hop dedupe**. Outbound responses set
|
|
209
193
|
* `X-Astra-Verified-Hop` so a downstream REST endpoint that the tool
|
|
@@ -249,13 +233,51 @@ declare global {
|
|
|
249
233
|
}
|
|
250
234
|
}
|
|
251
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Extended per-tool gate with optional PDLSS purpose + resource overrides.
|
|
238
|
+
*
|
|
239
|
+
* When `purpose` is set, it is authoritative for that tool — the agent's
|
|
240
|
+
* `X-Astra-Purpose` header is ignored. This lets the merchant declare what
|
|
241
|
+
* semantic purpose each tool fulfils rather than trusting agent self-declaration.
|
|
242
|
+
*
|
|
243
|
+
* When `resource` is set, it overrides the default (`req.path`) for that
|
|
244
|
+
* tool's verify-access call — e.g. mapping `list_products` to `/api/catalog`.
|
|
245
|
+
*/
|
|
246
|
+
interface ToolGateConfig {
|
|
247
|
+
minAccessLevel: AccessLevel;
|
|
248
|
+
purpose?: string;
|
|
249
|
+
resource?: string;
|
|
250
|
+
}
|
|
252
251
|
interface McpMiddlewareOptions extends GatewayConfig {
|
|
253
252
|
/**
|
|
254
|
-
* Per-tool
|
|
255
|
-
* the default tier from `mcpRiskTier` (`tools/call` → `'standard'`).
|
|
256
|
-
*
|
|
253
|
+
* Per-tool gating for `tools/call` invocations. Tools not listed inherit
|
|
254
|
+
* the default tier from `mcpRiskTier` (`tools/call` → `'standard'`).
|
|
255
|
+
*
|
|
256
|
+
* Accepts both the shorthand access-level string and the full object shape:
|
|
257
|
+
* ```typescript
|
|
258
|
+
* toolGates: {
|
|
259
|
+
* browse_catalog: 'read-only', // shorthand
|
|
260
|
+
* list_products: { minAccessLevel: 'none', // full shape
|
|
261
|
+
* purpose: 'shopping.search',
|
|
262
|
+
* resource: '/api/catalog' },
|
|
263
|
+
* start_checkout: { minAccessLevel: 'standard',
|
|
264
|
+
* purpose: 'shopping.purchase',
|
|
265
|
+
* resource: '/api/checkout/*' },
|
|
266
|
+
* }
|
|
267
|
+
* ```
|
|
268
|
+
*
|
|
269
|
+
* When `tools/call` arrives for a tool not declared in `toolGates`, the SDK
|
|
270
|
+
* falls back to a risk-tier default based on the method classification
|
|
271
|
+
* (`mcpRiskTier`). For `tools/call` the fallback is `'standard'`. Best
|
|
272
|
+
* practice: declare every tool you expose explicitly.
|
|
273
|
+
*
|
|
274
|
+
* The action axis for non-tools/call MCP methods (`tools/list`,
|
|
275
|
+
* `resources/list`, `prompts/list`, etc.) is the literal JSON-RPC method
|
|
276
|
+
* name (e.g. `'tools/list'`). Merchants gating MCP traffic by action should
|
|
277
|
+
* declare per-tool gates in `toolGates`, not endpoint-level `allowedActions`
|
|
278
|
+
* — the latter applies to REST-style action values, not MCP method strings.
|
|
257
279
|
*/
|
|
258
|
-
toolGates?: Record<string, AccessLevel>;
|
|
280
|
+
toolGates?: Record<string, AccessLevel | ToolGateConfig>;
|
|
259
281
|
/**
|
|
260
282
|
* Per-method override (e.g. tighten `tools/list` to `'read-only'` if you
|
|
261
283
|
* don't want unregistered probes seeing your tool catalogue). Matches by
|
|
@@ -323,4 +345,4 @@ interface McpMiddlewareOptions extends GatewayConfig {
|
|
|
323
345
|
*/
|
|
324
346
|
declare function createMcpMiddleware(options: McpMiddlewareOptions): RequestHandler;
|
|
325
347
|
|
|
326
|
-
export { MCP_VERIFIED_HOP_HEADER, type McpMiddlewareOptions, type ParsedMcpRequest, createMcpMiddleware, isVerifiedHopValidFor, mcpRiskTier, mcpToPdlss, parseMcpJsonRpc, parseVerifiedHop, serializeVerifiedHop };
|
|
348
|
+
export { MCP_VERIFIED_HOP_HEADER, type McpMiddlewareOptions, type ParsedMcpRequest, type ToolGateConfig, createMcpMiddleware, isVerifiedHopValidFor, mcpRiskTier, mcpToPdlss, parseMcpJsonRpc, parseVerifiedHop, serializeVerifiedHop };
|
package/dist/adapters/mcp.d.ts
CHANGED
|
@@ -85,7 +85,7 @@ interface ParsedMcpRequest {
|
|
|
85
85
|
* (canonical SDK location) OR `params.arguments.purpose` (legacy /
|
|
86
86
|
* conventional callers). The discriminator is on `purposeSourceFromBody`.
|
|
87
87
|
* Adapter combines this with the `X-Astra-Purpose` header (header wins)
|
|
88
|
-
* before mapping; final fallback at `mcpToPdlss` is `
|
|
88
|
+
* before mapping; final fallback at `mcpToPdlss` is `undefined`.
|
|
89
89
|
*/
|
|
90
90
|
purposeFromBody?: string;
|
|
91
91
|
/** Which body location resolved `purposeFromBody`. */
|
|
@@ -120,60 +120,45 @@ declare function parseMcpJsonRpc(body: unknown): ParsedMcpRequest | null;
|
|
|
120
120
|
/**
|
|
121
121
|
* PDLSS mapping for an MCP request. The platform's PDLSS taxonomy is
|
|
122
122
|
* `purpose / action / resource`; for MCP traffic the audit-useful dimensions
|
|
123
|
-
* are the JSON-RPC method and (for `tools/call`) the tool name.
|
|
124
|
-
* dashboards and audits can correlate consistently across cohort-3 partners.
|
|
123
|
+
* are the JSON-RPC method and (for `tools/call`) the tool name.
|
|
125
124
|
*
|
|
126
|
-
*
|
|
127
|
-
* - `
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
125
|
+
* v2.5.0 rules:
|
|
126
|
+
* - `resource` defaults to the HTTP request path (e.g. `/mcp`). Per-tool
|
|
127
|
+
* overrides via `toolGates` config let merchants map tools to specific
|
|
128
|
+
* backend resources (e.g. `/api/catalog`).
|
|
129
|
+
* - `purpose` defaults to `undefined` (backend evaluator applies
|
|
130
|
+
* skip-when-undefined). Per-tool overrides via `toolGates` config let
|
|
131
|
+
* merchants declare the semantic purpose each tool fulfils.
|
|
132
|
+
* - `action` is the bare tool name for `tools/call`, the JSON-RPC method
|
|
133
|
+
* otherwise. Header/body overrides available.
|
|
133
134
|
*/
|
|
134
135
|
interface McpPdlssMapping {
|
|
135
|
-
purpose: string;
|
|
136
|
+
purpose: string | undefined;
|
|
136
137
|
action: string;
|
|
137
138
|
resource: string;
|
|
138
|
-
|
|
139
|
-
* Round-13 (R13-1 / R13-2): the resolution channel for purpose and
|
|
140
|
-
* action — disjoint enums sharing the same structure per
|
|
141
|
-
* `feedback_symmetric_fallbacks_for_symmetric_concepts.md`. Adapters
|
|
142
|
-
* log both at debug level so partners can confirm header / body
|
|
143
|
-
* pass-through, support can triage tickets, and we can watch the
|
|
144
|
-
* `default_*` / `transport_layer` decay over time as integrations
|
|
145
|
-
* mature.
|
|
146
|
-
*
|
|
147
|
-
* Round-12 (F19) used a narrower `purposeSource: 'header' |
|
|
148
|
-
* 'tool_argument' | 'default_mcp_invoke'` — round-13 widens to also
|
|
149
|
-
* carry the `_meta` source distinct from `tool_argument`, so the round-13
|
|
150
|
-
* R13-1 fallback (which now reads both `_meta.astrasync.purpose` AND
|
|
151
|
-
* `params.arguments.purpose`) can report WHICH body location resolved.
|
|
152
|
-
*/
|
|
153
|
-
purposeSource: 'header' | 'meta' | 'tool_argument' | 'default_mcp_invoke';
|
|
139
|
+
purposeSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | undefined;
|
|
154
140
|
actionSource: 'header' | 'meta' | 'tool_argument' | 'transport_layer';
|
|
155
141
|
}
|
|
156
142
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
143
|
+
* v2.5.0 — PDLSS field derivation for MCP requests.
|
|
144
|
+
*
|
|
145
|
+
* Purpose precedence:
|
|
146
|
+
* - If `toolGate.purpose` is provided, it is authoritative (header/body ignored).
|
|
147
|
+
* - Otherwise: header → body `_meta` → body `arguments` → `undefined`.
|
|
162
148
|
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* 3. `params.arguments.<concept>` body field (legacy / conventional)
|
|
166
|
-
* 4. Transport-layer default:
|
|
167
|
-
* purpose → 'mcp_invoke'
|
|
168
|
-
* action → '<method>:<toolName>' (or just '<method>')
|
|
149
|
+
* Resource precedence:
|
|
150
|
+
* - `toolGate.resource` if provided, else `requestPath`.
|
|
169
151
|
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* `
|
|
152
|
+
* Action precedence (unchanged from v2.4.14):
|
|
153
|
+
* - header → body `_meta` → body `arguments` → bare tool name / method.
|
|
154
|
+
*
|
|
155
|
+
* @param requestPath The HTTP request path (e.g. '/mcp'). Required.
|
|
156
|
+
* @param toolGate Resolved per-tool config from `toolGates`, if present.
|
|
175
157
|
*/
|
|
176
|
-
declare function mcpToPdlss(parsed: ParsedMcpRequest, headerPurpose?: string, headerAction?: string
|
|
158
|
+
declare function mcpToPdlss(parsed: ParsedMcpRequest, requestPath: string, headerPurpose?: string, headerAction?: string, toolGate?: {
|
|
159
|
+
purpose?: string;
|
|
160
|
+
resource?: string;
|
|
161
|
+
}): McpPdlssMapping;
|
|
177
162
|
/**
|
|
178
163
|
* Recommended minimum access level per method type. The MCP middleware uses
|
|
179
164
|
* this to split low-risk handshake / introspection traffic from high-risk
|
|
@@ -200,10 +185,9 @@ declare function mcpRiskTier(parsed: ParsedMcpRequest): AccessLevel;
|
|
|
200
185
|
* from `tools/call start_checkout` (high risk). This middleware peels
|
|
201
186
|
* the JSON-RPC body and applies a per-method risk tier.
|
|
202
187
|
*
|
|
203
|
-
* (b) **PDLSS mapping**.
|
|
204
|
-
* `
|
|
205
|
-
*
|
|
206
|
-
* for the exact mapping.
|
|
188
|
+
* (b) **PDLSS mapping**. Derives purpose, action, and resource from
|
|
189
|
+
* `toolGates` config and request context. See `transport/mcp-server.ts`
|
|
190
|
+
* `mcpToPdlss()` for the exact mapping.
|
|
207
191
|
*
|
|
208
192
|
* (c) **Inner-hop dedupe**. Outbound responses set
|
|
209
193
|
* `X-Astra-Verified-Hop` so a downstream REST endpoint that the tool
|
|
@@ -249,13 +233,51 @@ declare global {
|
|
|
249
233
|
}
|
|
250
234
|
}
|
|
251
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Extended per-tool gate with optional PDLSS purpose + resource overrides.
|
|
238
|
+
*
|
|
239
|
+
* When `purpose` is set, it is authoritative for that tool — the agent's
|
|
240
|
+
* `X-Astra-Purpose` header is ignored. This lets the merchant declare what
|
|
241
|
+
* semantic purpose each tool fulfils rather than trusting agent self-declaration.
|
|
242
|
+
*
|
|
243
|
+
* When `resource` is set, it overrides the default (`req.path`) for that
|
|
244
|
+
* tool's verify-access call — e.g. mapping `list_products` to `/api/catalog`.
|
|
245
|
+
*/
|
|
246
|
+
interface ToolGateConfig {
|
|
247
|
+
minAccessLevel: AccessLevel;
|
|
248
|
+
purpose?: string;
|
|
249
|
+
resource?: string;
|
|
250
|
+
}
|
|
252
251
|
interface McpMiddlewareOptions extends GatewayConfig {
|
|
253
252
|
/**
|
|
254
|
-
* Per-tool
|
|
255
|
-
* the default tier from `mcpRiskTier` (`tools/call` → `'standard'`).
|
|
256
|
-
*
|
|
253
|
+
* Per-tool gating for `tools/call` invocations. Tools not listed inherit
|
|
254
|
+
* the default tier from `mcpRiskTier` (`tools/call` → `'standard'`).
|
|
255
|
+
*
|
|
256
|
+
* Accepts both the shorthand access-level string and the full object shape:
|
|
257
|
+
* ```typescript
|
|
258
|
+
* toolGates: {
|
|
259
|
+
* browse_catalog: 'read-only', // shorthand
|
|
260
|
+
* list_products: { minAccessLevel: 'none', // full shape
|
|
261
|
+
* purpose: 'shopping.search',
|
|
262
|
+
* resource: '/api/catalog' },
|
|
263
|
+
* start_checkout: { minAccessLevel: 'standard',
|
|
264
|
+
* purpose: 'shopping.purchase',
|
|
265
|
+
* resource: '/api/checkout/*' },
|
|
266
|
+
* }
|
|
267
|
+
* ```
|
|
268
|
+
*
|
|
269
|
+
* When `tools/call` arrives for a tool not declared in `toolGates`, the SDK
|
|
270
|
+
* falls back to a risk-tier default based on the method classification
|
|
271
|
+
* (`mcpRiskTier`). For `tools/call` the fallback is `'standard'`. Best
|
|
272
|
+
* practice: declare every tool you expose explicitly.
|
|
273
|
+
*
|
|
274
|
+
* The action axis for non-tools/call MCP methods (`tools/list`,
|
|
275
|
+
* `resources/list`, `prompts/list`, etc.) is the literal JSON-RPC method
|
|
276
|
+
* name (e.g. `'tools/list'`). Merchants gating MCP traffic by action should
|
|
277
|
+
* declare per-tool gates in `toolGates`, not endpoint-level `allowedActions`
|
|
278
|
+
* — the latter applies to REST-style action values, not MCP method strings.
|
|
257
279
|
*/
|
|
258
|
-
toolGates?: Record<string, AccessLevel>;
|
|
280
|
+
toolGates?: Record<string, AccessLevel | ToolGateConfig>;
|
|
259
281
|
/**
|
|
260
282
|
* Per-method override (e.g. tighten `tools/list` to `'read-only'` if you
|
|
261
283
|
* don't want unregistered probes seeing your tool catalogue). Matches by
|
|
@@ -323,4 +345,4 @@ interface McpMiddlewareOptions extends GatewayConfig {
|
|
|
323
345
|
*/
|
|
324
346
|
declare function createMcpMiddleware(options: McpMiddlewareOptions): RequestHandler;
|
|
325
347
|
|
|
326
|
-
export { MCP_VERIFIED_HOP_HEADER, type McpMiddlewareOptions, type ParsedMcpRequest, createMcpMiddleware, isVerifiedHopValidFor, mcpRiskTier, mcpToPdlss, parseMcpJsonRpc, parseVerifiedHop, serializeVerifiedHop };
|
|
348
|
+
export { MCP_VERIFIED_HOP_HEADER, type McpMiddlewareOptions, type ParsedMcpRequest, type ToolGateConfig, createMcpMiddleware, isVerifiedHopValidFor, mcpRiskTier, mcpToPdlss, parseMcpJsonRpc, parseVerifiedHop, serializeVerifiedHop };
|
package/dist/adapters/mcp.js
CHANGED
|
@@ -53,6 +53,65 @@ function hasMinimumAccess(actual, required) {
|
|
|
53
53
|
// src/version.ts
|
|
54
54
|
var SDK_VERSION = "2.4.13";
|
|
55
55
|
|
|
56
|
+
// src/well-known.ts
|
|
57
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
58
|
+
var cache = /* @__PURE__ */ new Map();
|
|
59
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
60
|
+
function wellKnownUrl(apiBaseUrl) {
|
|
61
|
+
const base = apiBaseUrl.replace(/\/api\/?$/, "");
|
|
62
|
+
return `${base}/.well-known/agentic-commerce`;
|
|
63
|
+
}
|
|
64
|
+
async function fetchWellKnown(apiBaseUrl) {
|
|
65
|
+
const url = wellKnownUrl(apiBaseUrl);
|
|
66
|
+
const response = await fetch(url, {
|
|
67
|
+
method: "GET",
|
|
68
|
+
headers: { Accept: "application/json" },
|
|
69
|
+
signal: AbortSignal.timeout(5e3)
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`AstraSync platform must expose /.well-known/agentic-commerce; got ${response.status} from ${url}. SDK cannot initialise without it.`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
const data = await response.json();
|
|
77
|
+
if (!data.registrationUrl || !data.documentationUrl || !data.verifyAccessUrl) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`/.well-known/agentic-commerce response missing required fields (registrationUrl, documentationUrl, verifyAccessUrl).`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
function prefetchWellKnown(apiBaseUrl) {
|
|
85
|
+
const existing = inflight.get(apiBaseUrl);
|
|
86
|
+
if (existing) return existing;
|
|
87
|
+
const promise = fetchWellKnown(apiBaseUrl).then((data) => {
|
|
88
|
+
cache.set(apiBaseUrl, { data, fetchedAt: Date.now() });
|
|
89
|
+
inflight.delete(apiBaseUrl);
|
|
90
|
+
return data;
|
|
91
|
+
}).catch((err) => {
|
|
92
|
+
inflight.delete(apiBaseUrl);
|
|
93
|
+
throw err;
|
|
94
|
+
});
|
|
95
|
+
inflight.set(apiBaseUrl, promise);
|
|
96
|
+
return promise;
|
|
97
|
+
}
|
|
98
|
+
async function getWellKnownUrls(apiBaseUrl) {
|
|
99
|
+
const entry = cache.get(apiBaseUrl);
|
|
100
|
+
if (entry) {
|
|
101
|
+
if (Date.now() - entry.fetchedAt > CACHE_TTL_MS) {
|
|
102
|
+
prefetchWellKnown(apiBaseUrl).catch(() => {
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return entry.data;
|
|
106
|
+
}
|
|
107
|
+
const pending = inflight.get(apiBaseUrl);
|
|
108
|
+
if (pending) return pending;
|
|
109
|
+
return prefetchWellKnown(apiBaseUrl);
|
|
110
|
+
}
|
|
111
|
+
function getCachedWellKnownUrls(apiBaseUrl) {
|
|
112
|
+
return cache.get(apiBaseUrl)?.data;
|
|
113
|
+
}
|
|
114
|
+
|
|
56
115
|
// src/verify.ts
|
|
57
116
|
var DEFAULT_CONFIG = {
|
|
58
117
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -188,21 +247,22 @@ function extractCredentials(headers, query) {
|
|
|
188
247
|
}
|
|
189
248
|
return credentials;
|
|
190
249
|
}
|
|
191
|
-
function createGuidanceResponse(
|
|
250
|
+
function createGuidanceResponse(_config, reason, options = {}) {
|
|
192
251
|
const source = options.source ?? "no_credentials";
|
|
193
252
|
const isApiError = source === "api_error";
|
|
253
|
+
const urls = options.urls;
|
|
194
254
|
const guidance = isApiError ? {
|
|
195
255
|
message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
|
|
196
|
-
registrationUrl:
|
|
197
|
-
documentationUrl:
|
|
256
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
257
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
198
258
|
steps: [
|
|
199
259
|
"Retry the request with exponential backoff",
|
|
200
260
|
"If failures persist, share the correlationId with support"
|
|
201
261
|
]
|
|
202
262
|
} : {
|
|
203
263
|
message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
|
|
204
|
-
registrationUrl:
|
|
205
|
-
documentationUrl:
|
|
264
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
265
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
206
266
|
steps: [
|
|
207
267
|
"Register for an AstraSync account",
|
|
208
268
|
"Create and register your agent",
|
|
@@ -244,7 +304,7 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
244
304
|
const { credentials, ...requestData } = request;
|
|
245
305
|
const body = {
|
|
246
306
|
...credentials.astraId && { agentId: credentials.astraId },
|
|
247
|
-
purpose: requestData.purpose
|
|
307
|
+
...requestData.purpose && { purpose: requestData.purpose }
|
|
248
308
|
};
|
|
249
309
|
if (requestData.action) body.action = requestData.action;
|
|
250
310
|
if (requestData.resourceType) body.resourceType = requestData.resourceType;
|
|
@@ -324,6 +384,7 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
324
384
|
}
|
|
325
385
|
async function verify(config, request) {
|
|
326
386
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
387
|
+
const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
|
|
327
388
|
if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
|
|
328
389
|
if (mergedConfig.strictInit) {
|
|
329
390
|
await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
|
|
@@ -360,7 +421,8 @@ async function verify(config, request) {
|
|
|
360
421
|
if (!apiResponse.success) {
|
|
361
422
|
return createGuidanceResponse(mergedConfig, apiResponse.error, {
|
|
362
423
|
source: "api_error",
|
|
363
|
-
correlationId: apiResponse.correlationId
|
|
424
|
+
correlationId: apiResponse.correlationId,
|
|
425
|
+
urls
|
|
364
426
|
});
|
|
365
427
|
}
|
|
366
428
|
if (!apiResponse.access?.allowed) {
|
|
@@ -383,8 +445,8 @@ async function verify(config, request) {
|
|
|
383
445
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
384
446
|
guidance: {
|
|
385
447
|
message: apiResponse.access?.reason || "Access denied by PDLSS policy",
|
|
386
|
-
registrationUrl:
|
|
387
|
-
documentationUrl:
|
|
448
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
449
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
388
450
|
},
|
|
389
451
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
390
452
|
// Extract sessionId so decisions can be recorded for denials too
|
|
@@ -455,12 +517,12 @@ async function verify(config, request) {
|
|
|
455
517
|
];
|
|
456
518
|
result.guidance = result.runtimeChallenge ? {
|
|
457
519
|
message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
|
|
458
|
-
registrationUrl:
|
|
459
|
-
documentationUrl:
|
|
520
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
521
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
460
522
|
} : {
|
|
461
523
|
message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
|
|
462
|
-
registrationUrl:
|
|
463
|
-
documentationUrl:
|
|
524
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
525
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
464
526
|
};
|
|
465
527
|
} else if (result.recommendation === "step_up_required") {
|
|
466
528
|
result.requiresStepUp = true;
|
|
@@ -596,19 +658,22 @@ function extractFromMcpBody(astrasyncMeta, args, key) {
|
|
|
596
658
|
}
|
|
597
659
|
return { value: void 0, source: void 0 };
|
|
598
660
|
}
|
|
599
|
-
function mcpToPdlss(parsed, headerPurpose, headerAction) {
|
|
600
|
-
const resource =
|
|
661
|
+
function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate) {
|
|
662
|
+
const resource = toolGate?.resource ?? requestPath;
|
|
601
663
|
let purpose;
|
|
602
664
|
let purposeSource;
|
|
603
|
-
if (
|
|
665
|
+
if (toolGate?.purpose !== void 0) {
|
|
666
|
+
purpose = toolGate.purpose;
|
|
667
|
+
purposeSource = "tool_gate";
|
|
668
|
+
} else if (headerPurpose) {
|
|
604
669
|
purpose = headerPurpose;
|
|
605
670
|
purposeSource = "header";
|
|
606
671
|
} else if (parsed.purposeFromBody && parsed.purposeSourceFromBody) {
|
|
607
672
|
purpose = parsed.purposeFromBody;
|
|
608
673
|
purposeSource = parsed.purposeSourceFromBody;
|
|
609
674
|
} else {
|
|
610
|
-
purpose =
|
|
611
|
-
purposeSource =
|
|
675
|
+
purpose = void 0;
|
|
676
|
+
purposeSource = void 0;
|
|
612
677
|
}
|
|
613
678
|
let action;
|
|
614
679
|
let actionSource;
|
|
@@ -633,6 +698,9 @@ function mcpRiskTier(parsed) {
|
|
|
633
698
|
}
|
|
634
699
|
|
|
635
700
|
// src/adapters/mcp.ts
|
|
701
|
+
function normalizeToolGate(gate) {
|
|
702
|
+
return typeof gate === "string" ? { minAccessLevel: gate } : gate;
|
|
703
|
+
}
|
|
636
704
|
function readSingleHeader(value) {
|
|
637
705
|
if (typeof value === "string") return value;
|
|
638
706
|
if (Array.isArray(value)) return value[0];
|
|
@@ -648,6 +716,9 @@ function dedupeFailures(result) {
|
|
|
648
716
|
return true;
|
|
649
717
|
});
|
|
650
718
|
}
|
|
719
|
+
if (result.denialReasons && result.denialReasons.length > 1) {
|
|
720
|
+
result.denialReasons = [...new Set(result.denialReasons)];
|
|
721
|
+
}
|
|
651
722
|
}
|
|
652
723
|
function defaultMcpDenied(result, req, res) {
|
|
653
724
|
const id = req.body?.id ?? null;
|
|
@@ -673,11 +744,17 @@ function defaultMcpDenied(result, req, res) {
|
|
|
673
744
|
});
|
|
674
745
|
}
|
|
675
746
|
function resolveMinAccessLevel(parsed, opts) {
|
|
676
|
-
if (parsed.toolName
|
|
677
|
-
|
|
747
|
+
if (!parsed.toolName) {
|
|
748
|
+
if (opts.methodGates && opts.methodGates[parsed.method] !== void 0) {
|
|
749
|
+
return { level: opts.methodGates[parsed.method], source: "methodGate" };
|
|
750
|
+
}
|
|
751
|
+
return { level: "none", source: "discovery_default" };
|
|
678
752
|
}
|
|
679
|
-
if (opts.
|
|
680
|
-
return {
|
|
753
|
+
if (opts.toolGates && opts.toolGates[parsed.toolName] !== void 0) {
|
|
754
|
+
return {
|
|
755
|
+
level: normalizeToolGate(opts.toolGates[parsed.toolName]).minAccessLevel,
|
|
756
|
+
source: "toolGate"
|
|
757
|
+
};
|
|
681
758
|
}
|
|
682
759
|
return { level: mcpRiskTier(parsed), source: "tier" };
|
|
683
760
|
}
|
|
@@ -695,6 +772,10 @@ function createMcpMiddleware(options) {
|
|
|
695
772
|
failOnError = "open",
|
|
696
773
|
...config
|
|
697
774
|
} = options;
|
|
775
|
+
if (config.apiBaseUrl) {
|
|
776
|
+
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
777
|
+
});
|
|
778
|
+
}
|
|
698
779
|
return async (req, res, next) => {
|
|
699
780
|
try {
|
|
700
781
|
if (skip) return next();
|
|
@@ -707,6 +788,7 @@ function createMcpMiddleware(options) {
|
|
|
707
788
|
return next();
|
|
708
789
|
}
|
|
709
790
|
req.mcpRequest = parsed;
|
|
791
|
+
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
710
792
|
const headerRaw = req.headers["x-astra-id"] ?? req.headers["x-astra-agentid"];
|
|
711
793
|
const headerAstraId = typeof headerRaw === "string" ? headerRaw : Array.isArray(headerRaw) ? headerRaw[0] : void 0;
|
|
712
794
|
const bodyAstraId = parsed.agentIdFromBody;
|
|
@@ -760,9 +842,17 @@ function createMcpMiddleware(options) {
|
|
|
760
842
|
}
|
|
761
843
|
return next();
|
|
762
844
|
}
|
|
845
|
+
const rawGate = parsed.toolName && toolGates?.[parsed.toolName];
|
|
846
|
+
const gate = rawGate ? normalizeToolGate(rawGate) : void 0;
|
|
763
847
|
const headerPurpose = readSingleHeader(req.headers["x-astra-purpose"]);
|
|
764
848
|
const headerAction = readSingleHeader(req.headers["x-astra-action"]);
|
|
765
|
-
const pdlss = mcpToPdlss(
|
|
849
|
+
const pdlss = mcpToPdlss(
|
|
850
|
+
parsed,
|
|
851
|
+
req.path,
|
|
852
|
+
headerPurpose,
|
|
853
|
+
headerAction,
|
|
854
|
+
gate ? { purpose: gate.purpose, resource: gate.resource } : void 0
|
|
855
|
+
);
|
|
766
856
|
if (config.debug) {
|
|
767
857
|
console.debug("[mcp-middleware] pdlss resolved", {
|
|
768
858
|
purpose_source: pdlss.purposeSource,
|
|
@@ -823,6 +913,13 @@ function createMcpMiddleware(options) {
|
|
|
823
913
|
};
|
|
824
914
|
result.failures = [...result.failures ?? [], insufficientFailure];
|
|
825
915
|
result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
|
|
916
|
+
if (!result.guidance && wellKnownUrls) {
|
|
917
|
+
result.guidance = {
|
|
918
|
+
message: insufficientFailure.message,
|
|
919
|
+
registrationUrl: wellKnownUrls.registrationUrl,
|
|
920
|
+
documentationUrl: wellKnownUrls.documentationUrl
|
|
921
|
+
};
|
|
922
|
+
}
|
|
826
923
|
if (shouldRecordDecisions) {
|
|
827
924
|
const overrideKind = gateSource === "toolGate" ? "toolGate" : gateSource === "methodGate" ? "methodGate" : "other";
|
|
828
925
|
const override = {
|
|
@@ -891,6 +988,14 @@ function createMcpMiddleware(options) {
|
|
|
891
988
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
892
989
|
correlationId
|
|
893
990
|
};
|
|
991
|
+
const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
992
|
+
if (catchUrls) {
|
|
993
|
+
result.guidance = {
|
|
994
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`,
|
|
995
|
+
registrationUrl: catchUrls.registrationUrl,
|
|
996
|
+
documentationUrl: catchUrls.documentationUrl
|
|
997
|
+
};
|
|
998
|
+
}
|
|
894
999
|
dedupeFailures(result);
|
|
895
1000
|
return onDenied(result, req, res);
|
|
896
1001
|
}
|