@astrasyncai/verification-gateway 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter-interface/interface.d.mts +2 -2
- package/dist/adapter-interface/interface.d.ts +2 -2
- package/dist/adapters/express.d.mts +2 -2
- package/dist/adapters/express.d.ts +2 -2
- package/dist/adapters/express.js +145 -93
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +145 -93
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +29 -11
- package/dist/adapters/mcp.d.ts +29 -11
- package/dist/adapters/mcp.js +43 -102
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +43 -102
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.d.mts +2 -2
- package/dist/adapters/nextjs.d.ts +2 -2
- package/dist/adapters/nextjs.js +126 -56
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +126 -56
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +2 -2
- package/dist/adapters/sdk.d.ts +2 -2
- package/dist/adapters/sdk.js +25 -14
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +25 -14
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/agent/index.d.mts +2 -2
- package/dist/agent/index.d.ts +2 -2
- package/dist/agent/index.js +3 -0
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +3 -0
- package/dist/agent/index.mjs.map +1 -1
- package/dist/browser/background.js +18 -21
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +18 -21
- package/dist/browser/background.mjs.map +1 -1
- package/dist/browser/browser-adapter.d.mts +2 -2
- package/dist/browser/browser-adapter.d.ts +2 -2
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.d.ts +2 -2
- package/dist/cursor/cursor-adapter.d.mts +2 -2
- package/dist/cursor/cursor-adapter.d.ts +2 -2
- package/dist/cursor/extension.d.mts +2 -2
- package/dist/cursor/extension.d.ts +2 -2
- package/dist/cursor/extension.js +18 -21
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +18 -21
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/{express-CrfwoNAR.d.ts → express-BowlMHQF.d.ts} +1 -1
- package/dist/{express-ienhAXps.d.mts → express-CeoSdOAZ.d.mts} +1 -1
- package/dist/gateway/gateway.d.mts +2 -2
- package/dist/gateway/gateway.d.ts +2 -2
- package/dist/gateway/gateway.js +18 -21
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +18 -21
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +2 -2
- package/dist/git-trigger/git-hooks.d.ts +2 -2
- package/dist/{index-CEg_WG6y.d.mts → index-B51W8gn8.d.mts} +1 -1
- package/dist/{index-DC5f8eoQ.d.ts → index-DBmlycVm.d.ts} +1 -1
- package/dist/{index-B5e2IDWU.d.mts → index-DtGziFEm.d.mts} +1 -1
- package/dist/{index-CCdZxvAr.d.ts → index-DzXXBuLm.d.ts} +1 -1
- package/dist/index.d.mts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +209 -191
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +209 -191
- package/dist/index.mjs.map +1 -1
- package/dist/local-evaluator/evaluator.d.mts +2 -2
- package/dist/local-evaluator/evaluator.d.ts +2 -2
- package/dist/{nextjs-DSpisQst.d.mts → nextjs-BW1rzr1I.d.mts} +1 -1
- package/dist/{nextjs-66R1KW8e.d.ts → nextjs-V_K0qlAQ.d.ts} +1 -1
- package/dist/{sdk-5U_CBRpr.d.mts → sdk-ZYgI7G9f.d.ts} +14 -3
- package/dist/{sdk-Bm8np66n.d.ts → sdk-e5jg7sqW.d.mts} +14 -3
- package/dist/transport/index.d.mts +2 -2
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.js +10 -0
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +10 -0
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-CgDCUfo8.d.mts → types-BNiLZY0i.d.mts} +1 -1
- package/dist/{types-R5N4ET6x.d.ts → types-DJi-u3fz.d.ts} +1 -1
- package/dist/{types-B3USs-Kx.d.mts → types-rFh4VMH4.d.mts} +30 -2
- package/dist/{types-B3USs-Kx.d.ts → types-rFh4VMH4.d.ts} +30 -2
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/package.json +1 -1
package/dist/adapters/mcp.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Request, Response, RequestHandler } from 'express';
|
|
2
|
-
import { A as AccessLevel, G as GatewayConfig, i as VerificationResult } from '../types-
|
|
2
|
+
import { A as AccessLevel, G as GatewayConfig, i as VerificationResult } from '../types-rFh4VMH4.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* MCP server-side helpers — companion to `transport/mcp.ts` (which handles the
|
|
@@ -137,7 +137,7 @@ interface McpPdlssMapping {
|
|
|
137
137
|
action: string;
|
|
138
138
|
resource: string;
|
|
139
139
|
purposeSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | undefined;
|
|
140
|
-
actionSource: 'header' | 'meta' | 'tool_argument' | 'transport_layer';
|
|
140
|
+
actionSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | 'transport_layer';
|
|
141
141
|
}
|
|
142
142
|
/**
|
|
143
143
|
* v2.5.0 — PDLSS field derivation for MCP requests.
|
|
@@ -149,14 +149,18 @@ interface McpPdlssMapping {
|
|
|
149
149
|
* Resource precedence:
|
|
150
150
|
* - `toolGate.resource` if provided, else `requestPath`.
|
|
151
151
|
*
|
|
152
|
-
* Action precedence (
|
|
153
|
-
*
|
|
152
|
+
* Action precedence (3.1.0, Bug 14 §4.6 — toolGate override added for
|
|
153
|
+
* symmetry with purpose):
|
|
154
|
+
* - `toolGate.action` authoritative → header → body `_meta` → body
|
|
155
|
+
* `arguments` → bare tool name / method (transport_layer, unchanged —
|
|
156
|
+
* bare tool names are legitimate enumerated actions, never aliased).
|
|
154
157
|
*
|
|
155
158
|
* @param requestPath The HTTP request path (e.g. '/mcp'). Required.
|
|
156
159
|
* @param toolGate Resolved per-tool config from `toolGates`, if present.
|
|
157
160
|
*/
|
|
158
161
|
declare function mcpToPdlss(parsed: ParsedMcpRequest, requestPath: string, headerPurpose?: string, headerAction?: string, toolGate?: {
|
|
159
162
|
purpose?: string;
|
|
163
|
+
action?: string;
|
|
160
164
|
resource?: string;
|
|
161
165
|
}): McpPdlssMapping;
|
|
162
166
|
/**
|
|
@@ -234,18 +238,25 @@ declare global {
|
|
|
234
238
|
}
|
|
235
239
|
}
|
|
236
240
|
/**
|
|
237
|
-
* Extended per-tool gate with optional PDLSS purpose + resource
|
|
241
|
+
* Extended per-tool gate with optional PDLSS purpose + action + resource
|
|
242
|
+
* overrides.
|
|
238
243
|
*
|
|
239
244
|
* When `purpose` is set, it is authoritative for that tool — the agent's
|
|
240
245
|
* `X-Astra-Purpose` header is ignored. This lets the merchant declare what
|
|
241
246
|
* semantic purpose each tool fulfils rather than trusting agent self-declaration.
|
|
242
247
|
*
|
|
248
|
+
* When `action` is set (Bug 14, §4.6 — symmetric with `purpose`), it is
|
|
249
|
+
* authoritative over `X-Astra-Action` / body declarations, letting the
|
|
250
|
+
* merchant pin a dotted-verb action (e.g. `shopping.search`) for a tool
|
|
251
|
+
* whose callers would otherwise fall to the bare tool-name transport default.
|
|
252
|
+
*
|
|
243
253
|
* When `resource` is set, it overrides the default (`req.path`) for that
|
|
244
254
|
* tool's verify-access call — e.g. mapping `list_products` to `/api/catalog`.
|
|
245
255
|
*/
|
|
246
256
|
interface ToolGateConfig {
|
|
247
257
|
minAccessLevel: AccessLevel;
|
|
248
258
|
purpose?: string;
|
|
259
|
+
action?: string;
|
|
249
260
|
resource?: string;
|
|
250
261
|
}
|
|
251
262
|
interface McpMiddlewareOptions extends GatewayConfig {
|
|
@@ -253,15 +264,22 @@ interface McpMiddlewareOptions extends GatewayConfig {
|
|
|
253
264
|
* Per-tool gating for `tools/call` invocations. Tools not listed inherit
|
|
254
265
|
* the default tier from `mcpRiskTier` (`tools/call` → `'standard'`).
|
|
255
266
|
*
|
|
256
|
-
* Accepts both the shorthand access-level string and the full object shape
|
|
267
|
+
* Accepts both the shorthand access-level string and the full object shape.
|
|
268
|
+
* NOTE (3.2.0): the access-level band no longer gates, and a gated `tools/call`
|
|
269
|
+
* must carry a resolvable PDLSS **purpose** (the backend requires it). So the
|
|
270
|
+
* bare string shorthand — which carries no purpose — only works if the caller
|
|
271
|
+
* supplies the purpose another way (an `X-Astra-Purpose` header, or
|
|
272
|
+
* `params._meta.astrasync.purpose`); otherwise the call fails fast with a
|
|
273
|
+
* `PDLSS_PURPOSE_REQUIRED` 400. Prefer the **object form with `purpose`**:
|
|
257
274
|
* ```typescript
|
|
258
275
|
* toolGates: {
|
|
259
|
-
* browse_catalog: 'read-only',
|
|
260
|
-
*
|
|
261
|
-
*
|
|
276
|
+
* browse_catalog: 'read-only', // shorthand — purpose must come
|
|
277
|
+
* // from a header / _meta, else 400
|
|
278
|
+
* list_products: { purpose: 'shopping', // object form (recommended)
|
|
279
|
+
* action: 'shopping.search',
|
|
262
280
|
* resource: '/api/catalog' },
|
|
263
|
-
* start_checkout: {
|
|
264
|
-
*
|
|
281
|
+
* start_checkout: { purpose: 'shopping',
|
|
282
|
+
* action: 'shopping.purchase',
|
|
265
283
|
* resource: '/api/checkout/*' },
|
|
266
284
|
* }
|
|
267
285
|
* ```
|
package/dist/adapters/mcp.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Request, Response, RequestHandler } from 'express';
|
|
2
|
-
import { A as AccessLevel, G as GatewayConfig, i as VerificationResult } from '../types-
|
|
2
|
+
import { A as AccessLevel, G as GatewayConfig, i as VerificationResult } from '../types-rFh4VMH4.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* MCP server-side helpers — companion to `transport/mcp.ts` (which handles the
|
|
@@ -137,7 +137,7 @@ interface McpPdlssMapping {
|
|
|
137
137
|
action: string;
|
|
138
138
|
resource: string;
|
|
139
139
|
purposeSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | undefined;
|
|
140
|
-
actionSource: 'header' | 'meta' | 'tool_argument' | 'transport_layer';
|
|
140
|
+
actionSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | 'transport_layer';
|
|
141
141
|
}
|
|
142
142
|
/**
|
|
143
143
|
* v2.5.0 — PDLSS field derivation for MCP requests.
|
|
@@ -149,14 +149,18 @@ interface McpPdlssMapping {
|
|
|
149
149
|
* Resource precedence:
|
|
150
150
|
* - `toolGate.resource` if provided, else `requestPath`.
|
|
151
151
|
*
|
|
152
|
-
* Action precedence (
|
|
153
|
-
*
|
|
152
|
+
* Action precedence (3.1.0, Bug 14 §4.6 — toolGate override added for
|
|
153
|
+
* symmetry with purpose):
|
|
154
|
+
* - `toolGate.action` authoritative → header → body `_meta` → body
|
|
155
|
+
* `arguments` → bare tool name / method (transport_layer, unchanged —
|
|
156
|
+
* bare tool names are legitimate enumerated actions, never aliased).
|
|
154
157
|
*
|
|
155
158
|
* @param requestPath The HTTP request path (e.g. '/mcp'). Required.
|
|
156
159
|
* @param toolGate Resolved per-tool config from `toolGates`, if present.
|
|
157
160
|
*/
|
|
158
161
|
declare function mcpToPdlss(parsed: ParsedMcpRequest, requestPath: string, headerPurpose?: string, headerAction?: string, toolGate?: {
|
|
159
162
|
purpose?: string;
|
|
163
|
+
action?: string;
|
|
160
164
|
resource?: string;
|
|
161
165
|
}): McpPdlssMapping;
|
|
162
166
|
/**
|
|
@@ -234,18 +238,25 @@ declare global {
|
|
|
234
238
|
}
|
|
235
239
|
}
|
|
236
240
|
/**
|
|
237
|
-
* Extended per-tool gate with optional PDLSS purpose + resource
|
|
241
|
+
* Extended per-tool gate with optional PDLSS purpose + action + resource
|
|
242
|
+
* overrides.
|
|
238
243
|
*
|
|
239
244
|
* When `purpose` is set, it is authoritative for that tool — the agent's
|
|
240
245
|
* `X-Astra-Purpose` header is ignored. This lets the merchant declare what
|
|
241
246
|
* semantic purpose each tool fulfils rather than trusting agent self-declaration.
|
|
242
247
|
*
|
|
248
|
+
* When `action` is set (Bug 14, §4.6 — symmetric with `purpose`), it is
|
|
249
|
+
* authoritative over `X-Astra-Action` / body declarations, letting the
|
|
250
|
+
* merchant pin a dotted-verb action (e.g. `shopping.search`) for a tool
|
|
251
|
+
* whose callers would otherwise fall to the bare tool-name transport default.
|
|
252
|
+
*
|
|
243
253
|
* When `resource` is set, it overrides the default (`req.path`) for that
|
|
244
254
|
* tool's verify-access call — e.g. mapping `list_products` to `/api/catalog`.
|
|
245
255
|
*/
|
|
246
256
|
interface ToolGateConfig {
|
|
247
257
|
minAccessLevel: AccessLevel;
|
|
248
258
|
purpose?: string;
|
|
259
|
+
action?: string;
|
|
249
260
|
resource?: string;
|
|
250
261
|
}
|
|
251
262
|
interface McpMiddlewareOptions extends GatewayConfig {
|
|
@@ -253,15 +264,22 @@ interface McpMiddlewareOptions extends GatewayConfig {
|
|
|
253
264
|
* Per-tool gating for `tools/call` invocations. Tools not listed inherit
|
|
254
265
|
* the default tier from `mcpRiskTier` (`tools/call` → `'standard'`).
|
|
255
266
|
*
|
|
256
|
-
* Accepts both the shorthand access-level string and the full object shape
|
|
267
|
+
* Accepts both the shorthand access-level string and the full object shape.
|
|
268
|
+
* NOTE (3.2.0): the access-level band no longer gates, and a gated `tools/call`
|
|
269
|
+
* must carry a resolvable PDLSS **purpose** (the backend requires it). So the
|
|
270
|
+
* bare string shorthand — which carries no purpose — only works if the caller
|
|
271
|
+
* supplies the purpose another way (an `X-Astra-Purpose` header, or
|
|
272
|
+
* `params._meta.astrasync.purpose`); otherwise the call fails fast with a
|
|
273
|
+
* `PDLSS_PURPOSE_REQUIRED` 400. Prefer the **object form with `purpose`**:
|
|
257
274
|
* ```typescript
|
|
258
275
|
* toolGates: {
|
|
259
|
-
* browse_catalog: 'read-only',
|
|
260
|
-
*
|
|
261
|
-
*
|
|
276
|
+
* browse_catalog: 'read-only', // shorthand — purpose must come
|
|
277
|
+
* // from a header / _meta, else 400
|
|
278
|
+
* list_products: { purpose: 'shopping', // object form (recommended)
|
|
279
|
+
* action: 'shopping.search',
|
|
262
280
|
* resource: '/api/catalog' },
|
|
263
|
-
* start_checkout: {
|
|
264
|
-
*
|
|
281
|
+
* start_checkout: { purpose: 'shopping',
|
|
282
|
+
* action: 'shopping.purchase',
|
|
265
283
|
* resource: '/api/checkout/*' },
|
|
266
284
|
* }
|
|
267
285
|
* ```
|
package/dist/adapters/mcp.js
CHANGED
|
@@ -32,26 +32,15 @@ __export(mcp_exports, {
|
|
|
32
32
|
module.exports = __toCommonJS(mcp_exports);
|
|
33
33
|
|
|
34
34
|
// src/access-levels.ts
|
|
35
|
-
var ACCESS_LEVEL_HIERARCHY = {
|
|
36
|
-
none: 0,
|
|
37
|
-
restricted: 1,
|
|
38
|
-
"read-only": 2,
|
|
39
|
-
standard: 3,
|
|
40
|
-
full: 4,
|
|
41
|
-
internal: 5
|
|
42
|
-
};
|
|
43
35
|
function getTrustLevel(score) {
|
|
44
36
|
if (score >= 80) return "PLATINUM";
|
|
45
37
|
if (score >= 60) return "GOLD";
|
|
46
38
|
if (score >= 40) return "SILVER";
|
|
47
39
|
return "BRONZE";
|
|
48
40
|
}
|
|
49
|
-
function hasMinimumAccess(actual, required) {
|
|
50
|
-
return ACCESS_LEVEL_HIERARCHY[actual] >= ACCESS_LEVEL_HIERARCHY[required];
|
|
51
|
-
}
|
|
52
41
|
|
|
53
42
|
// src/version.ts
|
|
54
|
-
var SDK_VERSION = "3.
|
|
43
|
+
var SDK_VERSION = "3.2.0";
|
|
55
44
|
|
|
56
45
|
// src/well-known.ts
|
|
57
46
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -156,7 +145,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
|
156
145
|
}
|
|
157
146
|
}
|
|
158
147
|
var verificationCache = /* @__PURE__ */ new Map();
|
|
159
|
-
function getCacheKey(request) {
|
|
148
|
+
function getCacheKey(request, counterpartyId) {
|
|
160
149
|
const c = request.credentials;
|
|
161
150
|
return [
|
|
162
151
|
c.astraId || "",
|
|
@@ -169,6 +158,14 @@ function getCacheKey(request) {
|
|
|
169
158
|
request.jurisdiction || "",
|
|
170
159
|
request.transactionValue ?? "",
|
|
171
160
|
request.currency || "",
|
|
161
|
+
// SECURITY (cross-merchant cache leak): the merchant identity is sent via
|
|
162
|
+
// `config.counterpartyId`, NOT on the request, so it was previously absent
|
|
163
|
+
// from the key — two verifies for the SAME agent/purpose/action/value but
|
|
164
|
+
// DIFFERENT merchants collided, and a grant at a permissive merchant (low
|
|
165
|
+
// trust floor) was served for a stricter one. Same bug class as the
|
|
166
|
+
// duration omission (F-A1-07). counterpartyId affects the backend verdict
|
|
167
|
+
// (trust floor / per-route policy), so it MUST key the cache.
|
|
168
|
+
counterpartyId || "",
|
|
172
169
|
request.counterpartyUrl || "",
|
|
173
170
|
request.counterpartyType || "",
|
|
174
171
|
request.isSubAgentRequest ? "1" : "0",
|
|
@@ -192,8 +189,8 @@ function getCacheKey(request) {
|
|
|
192
189
|
request.callerMetadata?.agentCardUrl || ""
|
|
193
190
|
].join("|");
|
|
194
191
|
}
|
|
195
|
-
function getCachedResult(request) {
|
|
196
|
-
const key = getCacheKey(request);
|
|
192
|
+
function getCachedResult(request, counterpartyId) {
|
|
193
|
+
const key = getCacheKey(request, counterpartyId);
|
|
197
194
|
const cached = verificationCache.get(key);
|
|
198
195
|
if (cached && cached.expiresAt > Date.now()) {
|
|
199
196
|
return cached.result;
|
|
@@ -205,9 +202,9 @@ function getCachedResult(request) {
|
|
|
205
202
|
}
|
|
206
203
|
var DEFAULT_AUTONOMOUS_TTL_SECONDS = 60;
|
|
207
204
|
var DEFAULT_STEP_UP_TTL_SECONDS = 300;
|
|
208
|
-
function cacheResult(request, result, configuredTtl) {
|
|
205
|
+
function cacheResult(request, result, configuredTtl, counterpartyId) {
|
|
209
206
|
const ttlSeconds = configuredTtl && configuredTtl > 0 ? configuredTtl : result.requiresStepUp ? DEFAULT_STEP_UP_TTL_SECONDS : DEFAULT_AUTONOMOUS_TTL_SECONDS;
|
|
210
|
-
const key = getCacheKey(request);
|
|
207
|
+
const key = getCacheKey(request, counterpartyId);
|
|
211
208
|
verificationCache.set(key, {
|
|
212
209
|
result,
|
|
213
210
|
expiresAt: Date.now() + ttlSeconds * 1e3
|
|
@@ -399,7 +396,7 @@ async function verify(config, request) {
|
|
|
399
396
|
);
|
|
400
397
|
}
|
|
401
398
|
if (mergedConfig.cacheTtl !== 0) {
|
|
402
|
-
const cached = getCachedResult(request);
|
|
399
|
+
const cached = getCachedResult(request, mergedConfig.counterpartyId);
|
|
403
400
|
if (cached) {
|
|
404
401
|
if (mergedConfig.debug) {
|
|
405
402
|
console.log("[VerificationGateway] Returning cached result");
|
|
@@ -451,8 +448,8 @@ async function verify(config, request) {
|
|
|
451
448
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
452
449
|
// Extract sessionId so decisions can be recorded for denials too
|
|
453
450
|
sessionId: apiResponse.sessionId,
|
|
454
|
-
//
|
|
455
|
-
//
|
|
451
|
+
// Anonymous traffic has no session → correlationId is the per-attempt
|
|
452
|
+
// linking key (the sessionId-equivalent for anonymous callers).
|
|
456
453
|
correlationId: apiResponse.correlationId,
|
|
457
454
|
recommendation: apiResponse.recommendation,
|
|
458
455
|
recommendationReasons: apiResponse.recommendationReasons
|
|
@@ -526,17 +523,14 @@ async function verify(config, request) {
|
|
|
526
523
|
};
|
|
527
524
|
} else if (result.recommendation === "step_up_required") {
|
|
528
525
|
result.requiresStepUp = true;
|
|
529
|
-
if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
|
|
530
|
-
result.accessLevel = "read-only";
|
|
531
|
-
}
|
|
532
526
|
result.denialReasons = result.recommendationReasons || ["Step-up verification required"];
|
|
533
527
|
}
|
|
534
528
|
if (mergedConfig.cacheTtl !== 0 && result.recommendation !== "deny") {
|
|
535
|
-
cacheResult(request, result, mergedConfig.cacheTtl);
|
|
529
|
+
cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);
|
|
536
530
|
}
|
|
537
531
|
return result;
|
|
538
532
|
}
|
|
539
|
-
async function recordDecision(config, sessionId, decision, reason
|
|
533
|
+
async function recordDecision(config, sessionId, decision, reason) {
|
|
540
534
|
const headers = { "Content-Type": "application/json" };
|
|
541
535
|
if (config.apiKey) {
|
|
542
536
|
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
@@ -545,36 +539,7 @@ async function recordDecision(config, sessionId, decision, reason, override) {
|
|
|
545
539
|
await fetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {
|
|
546
540
|
method: "POST",
|
|
547
541
|
headers,
|
|
548
|
-
body: JSON.stringify({
|
|
549
|
-
decision,
|
|
550
|
-
reason,
|
|
551
|
-
...override && {
|
|
552
|
-
overriddenBy: override.overriddenBy,
|
|
553
|
-
toolName: override.toolName,
|
|
554
|
-
requestedLevel: override.requestedLevel,
|
|
555
|
-
grantedLevel: override.grantedLevel
|
|
556
|
-
}
|
|
557
|
-
})
|
|
558
|
-
}).catch(() => {
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
async function recordAnonymousLocalOverride(config, correlationId, override, reason) {
|
|
562
|
-
const headers = { "Content-Type": "application/json" };
|
|
563
|
-
if (config.apiKey) {
|
|
564
|
-
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
565
|
-
headers["X-API-Key"] = config.apiKey;
|
|
566
|
-
}
|
|
567
|
-
await fetch(`${config.apiBaseUrl}/agents/verify-access/local-override`, {
|
|
568
|
-
method: "POST",
|
|
569
|
-
headers,
|
|
570
|
-
body: JSON.stringify({
|
|
571
|
-
correlationId,
|
|
572
|
-
reason,
|
|
573
|
-
overriddenBy: override.overriddenBy,
|
|
574
|
-
toolName: override.toolName,
|
|
575
|
-
requestedLevel: override.requestedLevel,
|
|
576
|
-
grantedLevel: override.grantedLevel
|
|
577
|
-
})
|
|
542
|
+
body: JSON.stringify({ decision, reason })
|
|
578
543
|
}).catch(() => {
|
|
579
544
|
});
|
|
580
545
|
}
|
|
@@ -677,7 +642,10 @@ function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate)
|
|
|
677
642
|
}
|
|
678
643
|
let action;
|
|
679
644
|
let actionSource;
|
|
680
|
-
if (
|
|
645
|
+
if (toolGate?.action !== void 0) {
|
|
646
|
+
action = toolGate.action;
|
|
647
|
+
actionSource = "tool_gate";
|
|
648
|
+
} else if (headerAction) {
|
|
681
649
|
action = headerAction;
|
|
682
650
|
actionSource = "header";
|
|
683
651
|
} else if (parsed.actionFromBody && parsed.actionSourceFromBody) {
|
|
@@ -788,7 +756,6 @@ function createMcpMiddleware(options) {
|
|
|
788
756
|
return next();
|
|
789
757
|
}
|
|
790
758
|
req.mcpRequest = parsed;
|
|
791
|
-
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
792
759
|
const headerRaw = req.headers["x-astra-id"] ?? req.headers["x-astra-agentid"];
|
|
793
760
|
const headerAstraId = typeof headerRaw === "string" ? headerRaw : Array.isArray(headerRaw) ? headerRaw[0] : void 0;
|
|
794
761
|
const bodyAstraId = parsed.agentIdFromBody;
|
|
@@ -825,7 +792,7 @@ function createMcpMiddleware(options) {
|
|
|
825
792
|
return next();
|
|
826
793
|
}
|
|
827
794
|
}
|
|
828
|
-
const { level: minAccessLevel
|
|
795
|
+
const { level: minAccessLevel } = resolveMinAccessLevel(parsed, {
|
|
829
796
|
toolGates,
|
|
830
797
|
methodGates
|
|
831
798
|
});
|
|
@@ -851,7 +818,7 @@ function createMcpMiddleware(options) {
|
|
|
851
818
|
req.path,
|
|
852
819
|
headerPurpose,
|
|
853
820
|
headerAction,
|
|
854
|
-
gate ? { purpose: gate.purpose, resource: gate.resource } : void 0
|
|
821
|
+
gate ? { purpose: gate.purpose, action: gate.action, resource: gate.resource } : void 0
|
|
855
822
|
);
|
|
856
823
|
if (config.debug) {
|
|
857
824
|
console.debug("[mcp-middleware] pdlss resolved", {
|
|
@@ -861,6 +828,23 @@ function createMcpMiddleware(options) {
|
|
|
861
828
|
resolved_action: pdlss.action
|
|
862
829
|
});
|
|
863
830
|
}
|
|
831
|
+
if (!pdlss.purpose) {
|
|
832
|
+
const id = req.body?.id ?? null;
|
|
833
|
+
res.status(400).json({
|
|
834
|
+
jsonrpc: "2.0",
|
|
835
|
+
id,
|
|
836
|
+
error: {
|
|
837
|
+
code: -32602,
|
|
838
|
+
message: "PDLSS_PURPOSE_REQUIRED",
|
|
839
|
+
data: {
|
|
840
|
+
dimension: "pdlss.purpose",
|
|
841
|
+
detail: "This tool is access-gated but the call declared no PDLSS purpose. Supply a bare-category purpose via the X-Astra-Purpose header or params._meta.astrasync.purpose, or have the merchant set the tool\u2019s purpose in its toolGate config.",
|
|
842
|
+
resolvedAction: pdlss.action
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
864
848
|
const counterpartyUrl = config.counterpartyUrl || `${req.protocol}://${req.get("host")}${req.path}`;
|
|
865
849
|
const shouldRecordDecisions = recordDecisions !== false;
|
|
866
850
|
const result = await verify(config, {
|
|
@@ -884,7 +868,6 @@ function createMcpMiddleware(options) {
|
|
|
884
868
|
});
|
|
885
869
|
req.agentVerification = result;
|
|
886
870
|
const sessionId = result.sessionId;
|
|
887
|
-
const correlationId = result.correlationId;
|
|
888
871
|
if (!result.identityVerified || !result.policyAllowed) {
|
|
889
872
|
if (shouldRecordDecisions && sessionId) {
|
|
890
873
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
@@ -905,48 +888,6 @@ function createMcpMiddleware(options) {
|
|
|
905
888
|
}
|
|
906
889
|
return next();
|
|
907
890
|
}
|
|
908
|
-
if (!hasMinimumAccess(result.accessLevel, minAccessLevel)) {
|
|
909
|
-
const insufficientFailure = {
|
|
910
|
-
dimension: "access_level.insufficient",
|
|
911
|
-
message: `Tool requires accessLevel '${minAccessLevel}'; agent has '${result.accessLevel}'.`,
|
|
912
|
-
guidance: "Request elevated access via step-up verification (coming soon \u2014 ships this month). Step-up lets the agent owner approve a one-time elevation for this specific counterparty + purpose without changing the agent's baseline trust score."
|
|
913
|
-
};
|
|
914
|
-
result.failures = [...result.failures ?? [], insufficientFailure];
|
|
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
|
-
}
|
|
923
|
-
if (shouldRecordDecisions) {
|
|
924
|
-
const overrideKind = gateSource === "toolGate" ? "toolGate" : gateSource === "methodGate" ? "methodGate" : "other";
|
|
925
|
-
const override = {
|
|
926
|
-
overriddenBy: overrideKind,
|
|
927
|
-
...parsed.toolName && { toolName: parsed.toolName },
|
|
928
|
-
requestedLevel: minAccessLevel,
|
|
929
|
-
grantedLevel: result.accessLevel
|
|
930
|
-
};
|
|
931
|
-
if (sessionId) {
|
|
932
|
-
recordDecision(config, sessionId, "denied", result.denialReasons?.[0], override).catch(
|
|
933
|
-
() => {
|
|
934
|
-
}
|
|
935
|
-
);
|
|
936
|
-
} else if (correlationId) {
|
|
937
|
-
recordAnonymousLocalOverride(
|
|
938
|
-
config,
|
|
939
|
-
correlationId,
|
|
940
|
-
override,
|
|
941
|
-
result.denialReasons?.[0]
|
|
942
|
-
).catch(() => {
|
|
943
|
-
});
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
dedupeFailures(result);
|
|
947
|
-
onDenied(result, req, res);
|
|
948
|
-
return;
|
|
949
|
-
}
|
|
950
891
|
if (effectiveAstraId) {
|
|
951
892
|
res.setHeader(
|
|
952
893
|
MCP_VERIFIED_HOP_HEADER,
|