@agenticprimitives/mcp-runtime 0.1.0-alpha.2
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/LICENSE +21 -0
- package/README.md +43 -0
- package/dist/declare-resource.d.ts +6 -0
- package/dist/declare-resource.d.ts.map +1 -0
- package/dist/declare-resource.js +4 -0
- package/dist/declare-resource.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/jti-stores.d.ts +46 -0
- package/dist/jti-stores.d.ts.map +1 -0
- package/dist/jti-stores.js +125 -0
- package/dist/jti-stores.js.map +1 -0
- package/dist/service-mac.d.ts +124 -0
- package/dist/service-mac.d.ts.map +1 -0
- package/dist/service-mac.js +260 -0
- package/dist/service-mac.js.map +1 -0
- package/dist/types.d.ts +52 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/with-delegation.d.ts +135 -0
- package/dist/with-delegation.d.ts.map +1 -0
- package/dist/with-delegation.js +266 -0
- package/dist/with-delegation.js.map +1 -0
- package/package.json +70 -0
- package/spec.md +6 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Agentic Trust Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @agenticprimitives/mcp-runtime
|
|
2
|
+
|
|
3
|
+
Delegation-aware authorization middleware around the official MCP TypeScript SDK. Eliminates the ~65% code duplication observed across smart-agent's three mature MCP servers (`person-mcp`, `org-mcp`, `people-group-mcp`).
|
|
4
|
+
|
|
5
|
+
This package is the **decision layer**, not the SDK. The official `@modelcontextprotocol/sdk` already provides tool/resource/prompt registration, transports, and OAuth 2.1+PKCE+RFC-9728+RFC-8707 plumbing (mandated by the 2026-03-15 MCP spec). We add the bridge to `@agenticprimitives/delegation` + `@agenticprimitives/tool-policy`.
|
|
6
|
+
|
|
7
|
+
See [`spec.md`](./spec.md) → [`specs/205-mcp-runtime.md`](../../specs/205-mcp-runtime.md).
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server';
|
|
13
|
+
import { withDelegation, createSqliteJtiStore } from '@agenticprimitives/mcp-runtime';
|
|
14
|
+
|
|
15
|
+
const config = {
|
|
16
|
+
audience: 'urn:mcp:server:person',
|
|
17
|
+
chainId: 31337,
|
|
18
|
+
rpcUrl: process.env.RPC_URL!,
|
|
19
|
+
delegationManager: process.env.DELEGATION_MANAGER_ADDRESS as `0x${string}`,
|
|
20
|
+
enforcerMap,
|
|
21
|
+
jtiStore: createSqliteJtiStore(db, 'token_usage'),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const server = new McpServer({ name: 'person-mcp', version: '0.1.0' });
|
|
25
|
+
|
|
26
|
+
server.registerTool({
|
|
27
|
+
name: 'get_profile',
|
|
28
|
+
inputSchema: { type: 'object', properties: { token: { type: 'string' } } },
|
|
29
|
+
handler: withDelegation(config, async ({ principal }) => {
|
|
30
|
+
return db.profiles.findUnique({ where: { ownerAddress: principal } });
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The handler no longer touches auth. The wrapper performs: HMAC envelope check → session-key signature → EIP-712 hash → on-chain `isRevoked` → ERC-1271 verify → caveat eval (fail-closed) → JTI usage tracking → `tool-policy.evaluatePolicy` → hand `{ principal, grants? }` to the inner handler.
|
|
36
|
+
|
|
37
|
+
## Cross-delegation
|
|
38
|
+
|
|
39
|
+
**Removed from the public surface in H7-B.8** (XPKG-002 / EXT-024 closure). The previous `withCrossDelegation` was a stub that unconditionally rejected. Per spec 100 §6, experimental capability lives behind a `./experimental` subpath; when cross-delegation work resumes it lands there. See [`docs/audits/2026-05-packages-contracts-production-readiness.md`](../../docs/audits/2026-05-packages-contracts-production-readiness.md) (PKG-mcp-runtime-001).
|
|
40
|
+
|
|
41
|
+
## Status
|
|
42
|
+
|
|
43
|
+
Pre-alpha. Spec stable.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ResourceDefinition } from './types';
|
|
2
|
+
import type { ToolClassification } from '@agenticprimitives/tool-policy';
|
|
3
|
+
export declare function declareResource(def: ResourceDefinition, classification: ToolClassification): ResourceDefinition & {
|
|
4
|
+
_classification: ToolClassification;
|
|
5
|
+
};
|
|
6
|
+
//# sourceMappingURL=declare-resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declare-resource.d.ts","sourceRoot":"","sources":["../src/declare-resource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEzE,wBAAgB,eAAe,CAC7B,GAAG,EAAE,kBAAkB,EACvB,cAAc,EAAE,kBAAkB,GACjC,kBAAkB,GAAG;IAAE,eAAe,EAAE,kBAAkB,CAAA;CAAE,CAE9D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declare-resource.js","sourceRoot":"","sources":["../src/declare-resource.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,eAAe,CAC7B,GAAuB,EACvB,cAAkC;IAElC,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC,CAAC;AACrE,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { withDelegation, verifyDelegationForResource, McpAuthError, type McpAuthErrorCode, type PrivateAuthFailureContext, } from './with-delegation';
|
|
2
|
+
export { declareResource } from './declare-resource';
|
|
3
|
+
export { createMemoryJtiStore, createSqliteJtiStore, createPostgresJtiStore, type MigratableJtiStore, } from './jti-stores';
|
|
4
|
+
export { generateServiceMac, verifyServiceMac, bodyDigestHex, } from './service-mac';
|
|
5
|
+
export type { MacProviderLike, ServiceMacContext, ServiceMacHeaders, } from './service-mac';
|
|
6
|
+
export type { Address, Hex, Caveat, DataScopeGrant, Delegation, EnforcerAddressMap, JtiStore, ToolClassification, McpResourceVerifyConfig, ResourceDefinition, BetterSqlite3DatabaseLike, PgPoolLike, } from './types';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,cAAc,EACd,2BAA2B,EAC3B,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,KAAK,kBAAkB,GACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,OAAO,EACP,GAAG,EACH,MAAM,EACN,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,QAAQ,EACR,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,EAClB,yBAAyB,EACzB,UAAU,GACX,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// @agenticprimitives/mcp-runtime — public API
|
|
2
|
+
//
|
|
3
|
+
// See ../../specs/205-mcp-runtime.md for the full contract.
|
|
4
|
+
// H7-B.8: `withCrossDelegation` + `verifyCrossDelegationForResource` removed
|
|
5
|
+
// from the public surface (XPKG-002 / EXT-024 closure). Both were stubs that
|
|
6
|
+
// unconditionally rejected. They will resurface behind `./experimental` per
|
|
7
|
+
// spec 100 §6 when the cross-delegation work resumes.
|
|
8
|
+
export { withDelegation, verifyDelegationForResource, McpAuthError, } from './with-delegation';
|
|
9
|
+
export { declareResource } from './declare-resource';
|
|
10
|
+
export { createMemoryJtiStore, createSqliteJtiStore, createPostgresJtiStore, } from './jti-stores';
|
|
11
|
+
export { generateServiceMac, verifyServiceMac, bodyDigestHex, } from './service-mac';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,EAAE;AACF,4DAA4D;AAE5D,6EAA6E;AAC7E,6EAA6E;AAC7E,4EAA4E;AAC5E,sDAAsD;AACtD,OAAO,EACL,cAAc,EACd,2BAA2B,EAC3B,YAAY,GAGb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,GAEvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GACd,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { JtiStore } from '@agenticprimitives/delegation';
|
|
2
|
+
import type { BetterSqlite3DatabaseLike, PgPoolLike } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Sentinel env var. When set to "true", `createMemoryJtiStore` will
|
|
5
|
+
* construct in production without throwing. Required for one-off
|
|
6
|
+
* scenarios (CI smoke tests against a prod-built bundle, isolated dev
|
|
7
|
+
* worker instances). All callers MUST treat this as a loud opt-in — a
|
|
8
|
+
* one-time `console.warn` is emitted on construction.
|
|
9
|
+
*/
|
|
10
|
+
export declare const ALLOW_MEMORY_JTI_ENV = "AGENTIC_ALLOW_MEMORY_JTI_STORE";
|
|
11
|
+
/**
|
|
12
|
+
* H7-B.9 / XPKG-005 — explicit environment opt-in. The memory store is
|
|
13
|
+
* the only JTI store that ships with this package and has cross-isolate
|
|
14
|
+
* replay-vulnerability; production deploys MUST pass `environment:
|
|
15
|
+
* 'production'` to gate it on (and supply the `AP_ALLOW_MEMORY_JTI_STORE`
|
|
16
|
+
* opt-out). The previous `process.env.NODE_ENV` check silently opened on
|
|
17
|
+
* Cloudflare Workers / SES where `process.env` is undefined.
|
|
18
|
+
*/
|
|
19
|
+
export interface CreateMemoryJtiStoreOpts {
|
|
20
|
+
environment?: 'production' | 'development';
|
|
21
|
+
}
|
|
22
|
+
export declare function createMemoryJtiStore(opts?: CreateMemoryJtiStoreOpts): JtiStore;
|
|
23
|
+
/**
|
|
24
|
+
* JtiStore extended with an explicit `migrate()` step. H7-B.6: the SQL /
|
|
25
|
+
* pg adapters MUST be migrated once at bootstrap before any `trackUsage`
|
|
26
|
+
* is called. The migrate step is idempotent (`CREATE TABLE IF NOT EXISTS`)
|
|
27
|
+
* but requires DDL permission — keep it out of the security hot path.
|
|
28
|
+
*/
|
|
29
|
+
export interface MigratableJtiStore extends JtiStore {
|
|
30
|
+
migrate(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* H7-B.6 — SQLite JTI store. Construction no longer issues DDL; call
|
|
34
|
+
* `await store.migrate()` from your bootstrap before serving traffic.
|
|
35
|
+
* Calling `trackUsage` without a prior `migrate()` throws a setup error
|
|
36
|
+
* (NOT a silent noop) so least-privilege deploys fail loud, not silent.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createSqliteJtiStore(db: BetterSqlite3DatabaseLike, table?: string): MigratableJtiStore;
|
|
39
|
+
/**
|
|
40
|
+
* H7-B.6 — Postgres JTI store. Construction no longer issues DDL; call
|
|
41
|
+
* `await store.migrate()` from your bootstrap. Calling `trackUsage` without
|
|
42
|
+
* a prior `migrate()` throws a setup error so a misconfigured least-privilege
|
|
43
|
+
* deploy fails loud rather than silently disabling replay protection.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createPostgresJtiStore(pool: PgPoolLike, table?: string): MigratableJtiStore;
|
|
46
|
+
//# sourceMappingURL=jti-stores.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jti-stores.d.ts","sourceRoot":"","sources":["../src/jti-stores.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErE;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,mCAAmC,CAAC;AAIrE;;;;;;;GAOG;AACH,MAAM,WAAW,wBAAwB;IACvC,WAAW,CAAC,EAAE,YAAY,GAAG,aAAa,CAAC;CAC5C;AAED,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,wBAA6B,GAAG,QAAQ,CAgClF;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAmB,SAAQ,QAAQ;IAClD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,yBAAyB,EAC7B,KAAK,GAAE,MAAsB,GAC5B,kBAAkB,CAqCpB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,UAAU,EAChB,KAAK,GAAE,MAAsB,GAC5B,kBAAkB,CAgCpB"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// JtiStore adapters: memory (test), sqlite (demo + many prod), postgres (prod).
|
|
2
|
+
// Each implementation MUST be atomic under concurrent writers per the
|
|
3
|
+
// security invariant in spec 205 §5.
|
|
4
|
+
//
|
|
5
|
+
// H7-B.6 / PKG-MCP-RUNTIME-001 closure (also covers EXT-027). Prior versions
|
|
6
|
+
// ran `CREATE TABLE IF NOT EXISTS …` inside the adapter constructor (sqlite)
|
|
7
|
+
// or lazily on first `trackUsage` (postgres). That coupled the security hot
|
|
8
|
+
// path to runtime DDL permissions:
|
|
9
|
+
// - Postgres deploys following least-privilege (app role lacks DDL): first
|
|
10
|
+
// `trackUsage` throws → JTI store down → replay protection silently
|
|
11
|
+
// disabled (caller swallows → fail-open).
|
|
12
|
+
// - SQLite: CREATE runs at construction → mig-vs-app race.
|
|
13
|
+
//
|
|
14
|
+
// Fix: explicit `migrate()` step the consumer wires once at bootstrap; the
|
|
15
|
+
// runtime path no longer issues DDL. If `migrate()` was not called and the
|
|
16
|
+
// table is missing, `trackUsage` fails LOUD with a setup error (not a silent
|
|
17
|
+
// noop).
|
|
18
|
+
/**
|
|
19
|
+
* Sentinel env var. When set to "true", `createMemoryJtiStore` will
|
|
20
|
+
* construct in production without throwing. Required for one-off
|
|
21
|
+
* scenarios (CI smoke tests against a prod-built bundle, isolated dev
|
|
22
|
+
* worker instances). All callers MUST treat this as a loud opt-in — a
|
|
23
|
+
* one-time `console.warn` is emitted on construction.
|
|
24
|
+
*/
|
|
25
|
+
export const ALLOW_MEMORY_JTI_ENV = 'AGENTIC_ALLOW_MEMORY_JTI_STORE';
|
|
26
|
+
let memoryStoreWarnedOnce = false;
|
|
27
|
+
export function createMemoryJtiStore(opts = {}) {
|
|
28
|
+
// Production guard — H7-B.9: the caller MUST pass `environment: 'production'`
|
|
29
|
+
// to opt into the gate. We no longer infer from NODE_ENV (Workers / SES
|
|
30
|
+
// runtimes don't reliably expose it, leading to silent fall-open).
|
|
31
|
+
const env = typeof process !== 'undefined' ? (process.env ?? {}) : {};
|
|
32
|
+
const environment = opts.environment ?? (env.NODE_ENV === 'production' ? 'production' : 'development');
|
|
33
|
+
if (environment === 'production' && env[ALLOW_MEMORY_JTI_ENV] !== 'true') {
|
|
34
|
+
throw new Error('[mcp-runtime] createMemoryJtiStore refused: environment="production". ' +
|
|
35
|
+
'The memory store offers no cross-process replay protection. Use ' +
|
|
36
|
+
'createSqliteJtiStore / createPostgresJtiStore / a D1-backed store in ' +
|
|
37
|
+
`production. If you genuinely need to override (e.g., isolated test ` +
|
|
38
|
+
`worker), set ${ALLOW_MEMORY_JTI_ENV}=true and accept that delegation ` +
|
|
39
|
+
'tokens are replay-vulnerable across restarts.');
|
|
40
|
+
}
|
|
41
|
+
if (environment === 'production' && !memoryStoreWarnedOnce) {
|
|
42
|
+
console.warn(`[mcp-runtime] ⚠ MEMORY JTI STORE in production — ${ALLOW_MEMORY_JTI_ENV} ` +
|
|
43
|
+
'is set; delegation-token replay protection is non-durable. Remove this ' +
|
|
44
|
+
'flag before handling real-value workloads.');
|
|
45
|
+
memoryStoreWarnedOnce = true;
|
|
46
|
+
}
|
|
47
|
+
const usage = new Map();
|
|
48
|
+
return {
|
|
49
|
+
async trackUsage(jti, limit) {
|
|
50
|
+
const current = (usage.get(jti) ?? 0) + 1;
|
|
51
|
+
usage.set(jti, current);
|
|
52
|
+
return { usage: current, allowed: current <= limit };
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* H7-B.6 — SQLite JTI store. Construction no longer issues DDL; call
|
|
58
|
+
* `await store.migrate()` from your bootstrap before serving traffic.
|
|
59
|
+
* Calling `trackUsage` without a prior `migrate()` throws a setup error
|
|
60
|
+
* (NOT a silent noop) so least-privilege deploys fail loud, not silent.
|
|
61
|
+
*/
|
|
62
|
+
export function createSqliteJtiStore(db, table = 'token_usage') {
|
|
63
|
+
let migrated = false;
|
|
64
|
+
let upsert = null;
|
|
65
|
+
const prepareUpsert = () => db.prepare(`INSERT INTO ${table} (jti, usage) VALUES (?, 1)
|
|
66
|
+
ON CONFLICT(jti) DO UPDATE SET usage = usage + 1
|
|
67
|
+
RETURNING usage`);
|
|
68
|
+
return {
|
|
69
|
+
async migrate() {
|
|
70
|
+
if (migrated)
|
|
71
|
+
return;
|
|
72
|
+
db.prepare(`CREATE TABLE IF NOT EXISTS ${table} (
|
|
73
|
+
jti TEXT PRIMARY KEY,
|
|
74
|
+
usage INTEGER NOT NULL DEFAULT 0,
|
|
75
|
+
first_seen TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
76
|
+
)`).run();
|
|
77
|
+
upsert = prepareUpsert();
|
|
78
|
+
migrated = true;
|
|
79
|
+
},
|
|
80
|
+
async trackUsage(jti, limit) {
|
|
81
|
+
if (!migrated || !upsert) {
|
|
82
|
+
throw new Error(`[mcp-runtime] sqlite JTI store: migrate() was not called before trackUsage. ` +
|
|
83
|
+
`Call \`await store.migrate()\` once at bootstrap (it is idempotent). ` +
|
|
84
|
+
`H7-B.6 (PKG-MCP-RUNTIME-001 / EXT-027 closure) moved DDL off the hot path.`);
|
|
85
|
+
}
|
|
86
|
+
const row = upsert.get(jti);
|
|
87
|
+
const current = row?.usage ?? 1;
|
|
88
|
+
return { usage: current, allowed: current <= limit };
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* H7-B.6 — Postgres JTI store. Construction no longer issues DDL; call
|
|
94
|
+
* `await store.migrate()` from your bootstrap. Calling `trackUsage` without
|
|
95
|
+
* a prior `migrate()` throws a setup error so a misconfigured least-privilege
|
|
96
|
+
* deploy fails loud rather than silently disabling replay protection.
|
|
97
|
+
*/
|
|
98
|
+
export function createPostgresJtiStore(pool, table = 'token_usage') {
|
|
99
|
+
let migrated = false;
|
|
100
|
+
return {
|
|
101
|
+
async migrate() {
|
|
102
|
+
if (migrated)
|
|
103
|
+
return;
|
|
104
|
+
await pool.query(`CREATE TABLE IF NOT EXISTS ${table} (
|
|
105
|
+
jti TEXT PRIMARY KEY,
|
|
106
|
+
usage INTEGER NOT NULL DEFAULT 0,
|
|
107
|
+
first_seen TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
108
|
+
)`);
|
|
109
|
+
migrated = true;
|
|
110
|
+
},
|
|
111
|
+
async trackUsage(jti, limit) {
|
|
112
|
+
if (!migrated) {
|
|
113
|
+
throw new Error(`[mcp-runtime] postgres JTI store: migrate() was not called before trackUsage. ` +
|
|
114
|
+
`Call \`await store.migrate()\` once at bootstrap (it is idempotent). ` +
|
|
115
|
+
`H7-B.6 (PKG-MCP-RUNTIME-001 / EXT-027 closure) moved DDL off the hot path.`);
|
|
116
|
+
}
|
|
117
|
+
const res = await pool.query(`INSERT INTO ${table} (jti, usage) VALUES ($1, 1)
|
|
118
|
+
ON CONFLICT (jti) DO UPDATE SET usage = ${table}.usage + 1
|
|
119
|
+
RETURNING usage`, [jti]);
|
|
120
|
+
const current = res.rows[0]?.usage ?? 1;
|
|
121
|
+
return { usage: current, allowed: current <= limit };
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=jti-stores.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jti-stores.js","sourceRoot":"","sources":["../src/jti-stores.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service-to-service MAC envelope. Audit C1.
|
|
3
|
+
*
|
|
4
|
+
* Closes the gap where MCP requests rely solely on bearer delegation
|
|
5
|
+
* tokens with no route-level service authenticity or replay control.
|
|
6
|
+
* The MAC binds:
|
|
7
|
+
* - `audience` (the MCP server identity, e.g. `urn:mcp:server:person`)
|
|
8
|
+
* - `service` (the caller identity, e.g. `a2a-to-mcp`)
|
|
9
|
+
* - `route` (the tool name being invoked)
|
|
10
|
+
* - `nonce` (one-shot, replay-tracked via JtiStore)
|
|
11
|
+
* - `timestamp` (millisecond epoch, clock-skew bounded)
|
|
12
|
+
* - `bodyDigest` (sha256 of the raw request body)
|
|
13
|
+
*
|
|
14
|
+
* Both sides (a2a caller + mcp verifier) share an HMAC key via
|
|
15
|
+
* `key-custody.buildMacProvider(...)`. For dev, a local-aes provider
|
|
16
|
+
* with a shared 32-byte secret. For production, a GCP KMS HMAC key
|
|
17
|
+
* with IAM scoped to the two service accounts. The wire format is
|
|
18
|
+
* provider-agnostic.
|
|
19
|
+
*
|
|
20
|
+
* Doctrine: MCP runtime stays transport-agnostic. This module ships
|
|
21
|
+
* pure helpers; the demo's Hono middleware wires them to HTTP headers.
|
|
22
|
+
*/
|
|
23
|
+
import type { Hex } from '@agenticprimitives/types';
|
|
24
|
+
import type { JtiStore } from '@agenticprimitives/delegation';
|
|
25
|
+
import { type AuditSink } from '@agenticprimitives/audit';
|
|
26
|
+
export interface MacProviderLike {
|
|
27
|
+
readonly keyVersion: string;
|
|
28
|
+
generateMac?(input: {
|
|
29
|
+
canonicalMessage: Uint8Array;
|
|
30
|
+
service: string;
|
|
31
|
+
audience: string;
|
|
32
|
+
}): Promise<{
|
|
33
|
+
mac: Uint8Array;
|
|
34
|
+
keyId: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
export interface ServiceMacContext {
|
|
38
|
+
/** MCP server identity. Same value MUST be used on both sides. */
|
|
39
|
+
audience: string;
|
|
40
|
+
/** Caller identity (e.g. 'a2a-to-mcp'). */
|
|
41
|
+
service: string;
|
|
42
|
+
/** Route family — typically the tool name. */
|
|
43
|
+
route: string;
|
|
44
|
+
/** sha256 of the raw request body. */
|
|
45
|
+
bodyDigest: Hex;
|
|
46
|
+
}
|
|
47
|
+
export interface ServiceMacHeaders {
|
|
48
|
+
/** base64url(HMAC) over the canonical message. */
|
|
49
|
+
mac: string;
|
|
50
|
+
/** base64url(16 random bytes). One-shot; tracked via JtiStore. */
|
|
51
|
+
nonce: string;
|
|
52
|
+
/** Epoch milliseconds at generate time, as decimal string. */
|
|
53
|
+
timestamp: string;
|
|
54
|
+
/** Provider-supplied key ID (for log/forensics + key rotation). */
|
|
55
|
+
keyId: string;
|
|
56
|
+
}
|
|
57
|
+
/** Hex-encoded sha256 of a body string. Helper for callers. */
|
|
58
|
+
export declare function bodyDigestHex(body: string): Hex;
|
|
59
|
+
/**
|
|
60
|
+
* Caller side: generate the MAC + headers for an outgoing request.
|
|
61
|
+
* The provider MUST support `generateMac`.
|
|
62
|
+
*
|
|
63
|
+
* H7-F.3 / PKG-MCP-RUNTIME-006 / CT-9 closure — accepts an optional
|
|
64
|
+
* `auditSink` that receives `mcp-runtime.service-mac.issue` on
|
|
65
|
+
* successful generation (mirroring the verify side's
|
|
66
|
+
* `service-mac.{accept,reject}`). The audit row carries `keyId`,
|
|
67
|
+
* `service`, `audience`, and the `correlationId` the caller threads in,
|
|
68
|
+
* so a forensics query can join the issue with the corresponding
|
|
69
|
+
* downstream accept/reject.
|
|
70
|
+
*
|
|
71
|
+
* Fail-soft: audit emission failures NEVER break the MAC issuance.
|
|
72
|
+
*/
|
|
73
|
+
export declare function generateServiceMac(args: {
|
|
74
|
+
ctx: ServiceMacContext;
|
|
75
|
+
provider: MacProviderLike;
|
|
76
|
+
/** Override for tests. Production: leave undefined for crypto.randomBytes-derived nonce. */
|
|
77
|
+
nonce?: Uint8Array;
|
|
78
|
+
/** Override for tests. Production: leave undefined. */
|
|
79
|
+
now?: () => number;
|
|
80
|
+
/** H7-F.3 — emit `mcp-runtime.service-mac.issue` on success. */
|
|
81
|
+
auditSink?: AuditSink;
|
|
82
|
+
/** Correlation id stitched into the emitted event. */
|
|
83
|
+
correlationId?: string;
|
|
84
|
+
}): Promise<ServiceMacHeaders>;
|
|
85
|
+
/**
|
|
86
|
+
* Verifier side: recompute the MAC + check it matches in constant time,
|
|
87
|
+
* + check clock skew, + consume the nonce (replay tracking via JtiStore).
|
|
88
|
+
*
|
|
89
|
+
* Fail-closed: any failure returns `{ ok: false, reason }`. Callers MUST
|
|
90
|
+
* NOT echo `reason` to external clients — log it, return generic 401.
|
|
91
|
+
*/
|
|
92
|
+
export declare function verifyServiceMac(args: {
|
|
93
|
+
ctx: ServiceMacContext;
|
|
94
|
+
headers: ServiceMacHeaders;
|
|
95
|
+
provider: MacProviderLike;
|
|
96
|
+
jtiStore: JtiStore;
|
|
97
|
+
/** Default 60_000ms. */
|
|
98
|
+
maxClockSkewMs?: number;
|
|
99
|
+
now?: () => number;
|
|
100
|
+
/**
|
|
101
|
+
* Audit sink (audit C3). When provided, both outcomes emit:
|
|
102
|
+
*
|
|
103
|
+
* - On reject: `mcp-runtime.service-mac.reject` with a `reason` string
|
|
104
|
+
* classifying the failure (clock skew, mac mismatch, nonce replay,
|
|
105
|
+
* malformed input, ...).
|
|
106
|
+
* - On accept: `mcp-runtime.service-mac.accept` with the keyId + a
|
|
107
|
+
* short hash of the consumed nonce.
|
|
108
|
+
*
|
|
109
|
+
* Both events fire even when the downstream `withDelegation` wrapper
|
|
110
|
+
* also emits — service-mac and delegation are separate primitives
|
|
111
|
+
* with separate threat models, so anomaly detection (accept rate
|
|
112
|
+
* per primitive, missing-pair detection) needs them as distinct
|
|
113
|
+
* rows. Per-emit overhead is one D1 INSERT; not worth coalescing.
|
|
114
|
+
*/
|
|
115
|
+
auditSink?: AuditSink;
|
|
116
|
+
/** Correlation ID threaded into emitted events. */
|
|
117
|
+
correlationId?: string;
|
|
118
|
+
}): Promise<{
|
|
119
|
+
ok: true;
|
|
120
|
+
} | {
|
|
121
|
+
ok: false;
|
|
122
|
+
reason: string;
|
|
123
|
+
}>;
|
|
124
|
+
//# sourceMappingURL=service-mac.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-mac.d.ts","sourceRoot":"","sources":["../src/service-mac.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAItE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,CAAC,KAAK,EAAE;QAClB,gBAAgB,EAAE,UAAU,CAAC;QAC7B,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,UAAU,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,iBAAiB;IAChC,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,UAAU,EAAE,GAAG,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;CACf;AAoDD,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAE/C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,GAAG,EAAE,iBAAiB,CAAC;IACvB,QAAQ,EAAE,eAAe,CAAC;IAC1B,4FAA4F;IAC5F,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,uDAAuD;IACvD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,gEAAgE;IAChE,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAwC7B;AASD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,EAAE,iBAAiB,CAAC;IACvB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,QAAQ,CAAC;IACnB,wBAAwB;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;;;;;;;;;;;;;OAcG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAkGxD"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service-to-service MAC envelope. Audit C1.
|
|
3
|
+
*
|
|
4
|
+
* Closes the gap where MCP requests rely solely on bearer delegation
|
|
5
|
+
* tokens with no route-level service authenticity or replay control.
|
|
6
|
+
* The MAC binds:
|
|
7
|
+
* - `audience` (the MCP server identity, e.g. `urn:mcp:server:person`)
|
|
8
|
+
* - `service` (the caller identity, e.g. `a2a-to-mcp`)
|
|
9
|
+
* - `route` (the tool name being invoked)
|
|
10
|
+
* - `nonce` (one-shot, replay-tracked via JtiStore)
|
|
11
|
+
* - `timestamp` (millisecond epoch, clock-skew bounded)
|
|
12
|
+
* - `bodyDigest` (sha256 of the raw request body)
|
|
13
|
+
*
|
|
14
|
+
* Both sides (a2a caller + mcp verifier) share an HMAC key via
|
|
15
|
+
* `key-custody.buildMacProvider(...)`. For dev, a local-aes provider
|
|
16
|
+
* with a shared 32-byte secret. For production, a GCP KMS HMAC key
|
|
17
|
+
* with IAM scoped to the two service accounts. The wire format is
|
|
18
|
+
* provider-agnostic.
|
|
19
|
+
*
|
|
20
|
+
* Doctrine: MCP runtime stays transport-agnostic. This module ships
|
|
21
|
+
* pure helpers; the demo's Hono middleware wires them to HTTP headers.
|
|
22
|
+
*/
|
|
23
|
+
import { sha256, toHex } from 'viem';
|
|
24
|
+
import { buildEvent } from '@agenticprimitives/audit';
|
|
25
|
+
const VERSION = 'agentic-a2a-mcp-v1';
|
|
26
|
+
const DEFAULT_CLOCK_SKEW_MS = 60_000;
|
|
27
|
+
function canonicalMessage(ctx, nonce, timestamp) {
|
|
28
|
+
const text = VERSION +
|
|
29
|
+
'\n' +
|
|
30
|
+
ctx.audience +
|
|
31
|
+
'\n' +
|
|
32
|
+
ctx.service +
|
|
33
|
+
'\n' +
|
|
34
|
+
ctx.route +
|
|
35
|
+
'\n' +
|
|
36
|
+
nonce +
|
|
37
|
+
'\n' +
|
|
38
|
+
timestamp +
|
|
39
|
+
'\n' +
|
|
40
|
+
ctx.bodyDigest.toLowerCase();
|
|
41
|
+
return new TextEncoder().encode(text);
|
|
42
|
+
}
|
|
43
|
+
function base64urlEncode(bytes) {
|
|
44
|
+
const bin = Array.from(bytes, (b) => String.fromCharCode(b)).join('');
|
|
45
|
+
const b64 = typeof btoa === 'function'
|
|
46
|
+
? btoa(bin)
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
|
+
: globalThis.Buffer.from(bytes).toString('base64');
|
|
49
|
+
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
50
|
+
}
|
|
51
|
+
function base64urlDecode(s) {
|
|
52
|
+
const padded = s.replace(/-/g, '+').replace(/_/g, '/') +
|
|
53
|
+
'=='.slice((2 - (s.length & 3)) & 3);
|
|
54
|
+
const bin = typeof atob === 'function'
|
|
55
|
+
? atob(padded)
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
|
+
: globalThis.Buffer.from(padded, 'base64').toString('binary');
|
|
58
|
+
const out = new Uint8Array(bin.length);
|
|
59
|
+
for (let i = 0; i < bin.length; i++)
|
|
60
|
+
out[i] = bin.charCodeAt(i);
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
function constantTimeEqual(a, b) {
|
|
64
|
+
if (a.length !== b.length)
|
|
65
|
+
return false;
|
|
66
|
+
let diff = 0;
|
|
67
|
+
for (let i = 0; i < a.length; i++)
|
|
68
|
+
diff |= (a[i] ?? 0) ^ (b[i] ?? 0);
|
|
69
|
+
return diff === 0;
|
|
70
|
+
}
|
|
71
|
+
/** Hex-encoded sha256 of a body string. Helper for callers. */
|
|
72
|
+
export function bodyDigestHex(body) {
|
|
73
|
+
return sha256(toHex(body));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Caller side: generate the MAC + headers for an outgoing request.
|
|
77
|
+
* The provider MUST support `generateMac`.
|
|
78
|
+
*
|
|
79
|
+
* H7-F.3 / PKG-MCP-RUNTIME-006 / CT-9 closure — accepts an optional
|
|
80
|
+
* `auditSink` that receives `mcp-runtime.service-mac.issue` on
|
|
81
|
+
* successful generation (mirroring the verify side's
|
|
82
|
+
* `service-mac.{accept,reject}`). The audit row carries `keyId`,
|
|
83
|
+
* `service`, `audience`, and the `correlationId` the caller threads in,
|
|
84
|
+
* so a forensics query can join the issue with the corresponding
|
|
85
|
+
* downstream accept/reject.
|
|
86
|
+
*
|
|
87
|
+
* Fail-soft: audit emission failures NEVER break the MAC issuance.
|
|
88
|
+
*/
|
|
89
|
+
export async function generateServiceMac(args) {
|
|
90
|
+
if (!args.provider.generateMac) {
|
|
91
|
+
throw new Error('serviceMac: provider lacks generateMac (use a MAC-capable backend)');
|
|
92
|
+
}
|
|
93
|
+
const nonceBytes = args.nonce ?? cryptoRandomBytes(16);
|
|
94
|
+
const nonce = base64urlEncode(nonceBytes);
|
|
95
|
+
const timestamp = String((args.now ?? Date.now)());
|
|
96
|
+
const canonical = canonicalMessage(args.ctx, nonce, timestamp);
|
|
97
|
+
const { mac, keyId } = await args.provider.generateMac({
|
|
98
|
+
canonicalMessage: canonical,
|
|
99
|
+
service: args.ctx.service,
|
|
100
|
+
audience: args.ctx.audience,
|
|
101
|
+
});
|
|
102
|
+
const headers = {
|
|
103
|
+
mac: base64urlEncode(mac),
|
|
104
|
+
nonce,
|
|
105
|
+
timestamp,
|
|
106
|
+
keyId,
|
|
107
|
+
};
|
|
108
|
+
if (args.auditSink) {
|
|
109
|
+
try {
|
|
110
|
+
await args.auditSink.write({
|
|
111
|
+
id: cryptoRandomBytesHex(8),
|
|
112
|
+
timestamp: new Date().toISOString(),
|
|
113
|
+
action: 'mcp-runtime.service-mac.issue',
|
|
114
|
+
outcome: 'success',
|
|
115
|
+
correlationId: args.correlationId,
|
|
116
|
+
actor: { type: 'service', id: args.ctx.service },
|
|
117
|
+
audience: args.ctx.audience,
|
|
118
|
+
subject: { type: 'service-mac', id: keyId ?? 'unknown' },
|
|
119
|
+
context: {
|
|
120
|
+
route: args.ctx.route,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
/* H7-F.3: fail-soft. Issuance must not break on audit failure. */
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return headers;
|
|
129
|
+
}
|
|
130
|
+
function cryptoRandomBytesHex(n) {
|
|
131
|
+
const buf = cryptoRandomBytes(n);
|
|
132
|
+
let hex = '';
|
|
133
|
+
for (const b of buf)
|
|
134
|
+
hex += b.toString(16).padStart(2, '0');
|
|
135
|
+
return hex;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Verifier side: recompute the MAC + check it matches in constant time,
|
|
139
|
+
* + check clock skew, + consume the nonce (replay tracking via JtiStore).
|
|
140
|
+
*
|
|
141
|
+
* Fail-closed: any failure returns `{ ok: false, reason }`. Callers MUST
|
|
142
|
+
* NOT echo `reason` to external clients — log it, return generic 401.
|
|
143
|
+
*/
|
|
144
|
+
export async function verifyServiceMac(args) {
|
|
145
|
+
const emitReject = async (reason) => {
|
|
146
|
+
if (!args.auditSink)
|
|
147
|
+
return;
|
|
148
|
+
try {
|
|
149
|
+
await args.auditSink.write(buildEvent({
|
|
150
|
+
action: 'mcp-runtime.service-mac.reject',
|
|
151
|
+
outcome: 'denied',
|
|
152
|
+
correlationId: args.correlationId,
|
|
153
|
+
actor: { type: 'service', id: args.ctx.service },
|
|
154
|
+
subject: { type: 'tool', id: args.ctx.route },
|
|
155
|
+
audience: args.ctx.audience,
|
|
156
|
+
reason,
|
|
157
|
+
context: {
|
|
158
|
+
keyId: args.headers.keyId,
|
|
159
|
+
// Hash of the nonce so the raw value (which is one-shot
|
|
160
|
+
// and tied to a specific request) isn't surfaced in logs
|
|
161
|
+
// beyond what's already required for the rejection trail.
|
|
162
|
+
nonceHash: nonceHashShort(args.headers.nonce),
|
|
163
|
+
},
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
/* fail-soft */
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const reject = async (reason) => {
|
|
171
|
+
await emitReject(reason);
|
|
172
|
+
return { ok: false, reason };
|
|
173
|
+
};
|
|
174
|
+
if (!args.provider.generateMac) {
|
|
175
|
+
return reject('verifier provider lacks generateMac');
|
|
176
|
+
}
|
|
177
|
+
// Clock-skew gate.
|
|
178
|
+
const ts = Number(args.headers.timestamp);
|
|
179
|
+
if (!Number.isFinite(ts) || ts <= 0) {
|
|
180
|
+
return reject('malformed timestamp');
|
|
181
|
+
}
|
|
182
|
+
const now = (args.now ?? Date.now)();
|
|
183
|
+
const skew = args.maxClockSkewMs ?? DEFAULT_CLOCK_SKEW_MS;
|
|
184
|
+
if (Math.abs(now - ts) > skew) {
|
|
185
|
+
return reject(`clock skew ${Math.abs(now - ts)}ms exceeds ${skew}ms`);
|
|
186
|
+
}
|
|
187
|
+
// MAC recompute + constant-time compare.
|
|
188
|
+
const canonical = canonicalMessage(args.ctx, args.headers.nonce, args.headers.timestamp);
|
|
189
|
+
let expected;
|
|
190
|
+
try {
|
|
191
|
+
const got = await args.provider.generateMac({
|
|
192
|
+
canonicalMessage: canonical,
|
|
193
|
+
service: args.ctx.service,
|
|
194
|
+
audience: args.ctx.audience,
|
|
195
|
+
});
|
|
196
|
+
expected = got.mac;
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
return reject(`mac recompute failed: ${e instanceof Error ? e.message : e}`);
|
|
200
|
+
}
|
|
201
|
+
let received;
|
|
202
|
+
try {
|
|
203
|
+
received = base64urlDecode(args.headers.mac);
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
return reject(`malformed mac base64url: ${e instanceof Error ? e.message : e}`);
|
|
207
|
+
}
|
|
208
|
+
if (!constantTimeEqual(expected, received)) {
|
|
209
|
+
return reject('mac mismatch');
|
|
210
|
+
}
|
|
211
|
+
// Replay: track the nonce via JTI store. limit=1 means single-use:
|
|
212
|
+
// first call succeeds (usage=1, allowed=true); second sees usage=2,
|
|
213
|
+
// allowed=false. JTI key is namespaced to distinguish from
|
|
214
|
+
// delegation-token JTIs.
|
|
215
|
+
const jti = `mac:${args.ctx.audience}:${args.ctx.service}:${args.headers.nonce}`;
|
|
216
|
+
const tracked = await args.jtiStore.trackUsage(jti, 1);
|
|
217
|
+
if (!tracked.allowed) {
|
|
218
|
+
return reject('mac nonce already consumed (replay)');
|
|
219
|
+
}
|
|
220
|
+
void ts; // ts is bounded by clock-skew gate above; no further use here.
|
|
221
|
+
if (args.auditSink) {
|
|
222
|
+
try {
|
|
223
|
+
await args.auditSink.write(buildEvent({
|
|
224
|
+
action: 'mcp-runtime.service-mac.accept',
|
|
225
|
+
outcome: 'success',
|
|
226
|
+
correlationId: args.correlationId,
|
|
227
|
+
actor: { type: 'service', id: args.ctx.service },
|
|
228
|
+
subject: { type: 'tool', id: args.ctx.route },
|
|
229
|
+
audience: args.ctx.audience,
|
|
230
|
+
context: {
|
|
231
|
+
keyId: args.headers.keyId,
|
|
232
|
+
nonceHash: nonceHashShort(args.headers.nonce),
|
|
233
|
+
},
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
/* fail-soft */
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return { ok: true };
|
|
241
|
+
}
|
|
242
|
+
// ─── Internals ────────────────────────────────────────────────────────
|
|
243
|
+
function nonceHashShort(nonce) {
|
|
244
|
+
// 8-byte prefix of sha256(nonce) — enough to correlate events
|
|
245
|
+
// without surfacing the raw one-shot nonce in logs.
|
|
246
|
+
const digest = sha256(toHex(nonce));
|
|
247
|
+
return digest.slice(0, 18); // '0x' + 16 hex chars
|
|
248
|
+
}
|
|
249
|
+
function cryptoRandomBytes(n) {
|
|
250
|
+
const out = new Uint8Array(n);
|
|
251
|
+
// Both Web Crypto (browsers, Workers) and Node 18+ have globalThis.crypto.
|
|
252
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
253
|
+
const g = globalThis;
|
|
254
|
+
if (g.crypto?.getRandomValues) {
|
|
255
|
+
g.crypto.getRandomValues(out);
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
throw new Error('serviceMac: crypto.getRandomValues unavailable');
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=service-mac.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-mac.js","sourceRoot":"","sources":["../src/service-mac.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Address, Hex } from '@agenticprimitives/types';
|
|
2
|
+
import type { Caveat, DataScopeGrant, Delegation, EnforcerAddressMap, JtiStore, VerifyError } from '@agenticprimitives/delegation';
|
|
3
|
+
import type { ToolClassification } from '@agenticprimitives/tool-policy';
|
|
4
|
+
export type { Address, Hex, Caveat, DataScopeGrant, Delegation, EnforcerAddressMap, JtiStore, ToolClassification, VerifyError };
|
|
5
|
+
export interface McpResourceVerifyConfig {
|
|
6
|
+
audience: string;
|
|
7
|
+
chainId: number;
|
|
8
|
+
rpcUrl: string;
|
|
9
|
+
delegationManager: Address;
|
|
10
|
+
enforcerMap: EnforcerAddressMap;
|
|
11
|
+
jtiStore: JtiStore;
|
|
12
|
+
acceptLegacyCrossDelegations?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Whether to require the delegator's smart account to be on-chain.
|
|
15
|
+
* Default: `true` (fail-closed). When the account isn't deployed, ERC-1271
|
|
16
|
+
* can't be verified. Demos using counterfactual addresses without
|
|
17
|
+
* deploying may set `false` explicitly.
|
|
18
|
+
*/
|
|
19
|
+
requireDeployed?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Spec 207: `QuorumEnforcer` contract address for this chain. When a
|
|
22
|
+
* tool's classification produces a `requiresQuorum: true` decision
|
|
23
|
+
* from `tool-policy.evaluateThresholdPolicy(...)`, `withDelegation`
|
|
24
|
+
* threads this address into `delegation.verifyDelegationToken`'s
|
|
25
|
+
* `requireQuorumCaveat` opt so the delegation MUST carry a quorum
|
|
26
|
+
* caveat with this enforcer or verify fails closed.
|
|
27
|
+
*
|
|
28
|
+
* Consumer apps SHOULD configure this from their deployments JSON
|
|
29
|
+
* (packages/contracts/deployments-<network>.json's `quorumEnforcer`
|
|
30
|
+
* field). When unset, T3+ tools that require quorum will fail
|
|
31
|
+
* closed at the boundary — apps that don't ship multi-sig can
|
|
32
|
+
* either omit T3+ tools or leave this unset and stick to T1/T2.
|
|
33
|
+
*/
|
|
34
|
+
quorumEnforcer?: Address;
|
|
35
|
+
}
|
|
36
|
+
export interface ResourceDefinition {
|
|
37
|
+
name: string;
|
|
38
|
+
audience: string;
|
|
39
|
+
fields?: string[];
|
|
40
|
+
}
|
|
41
|
+
export interface BetterSqlite3DatabaseLike {
|
|
42
|
+
prepare(sql: string): {
|
|
43
|
+
run(...args: unknown[]): unknown;
|
|
44
|
+
get(...args: unknown[]): unknown;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export interface PgPoolLike {
|
|
48
|
+
query(text: string, params?: unknown[]): Promise<{
|
|
49
|
+
rows: unknown[];
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EACV,MAAM,EACN,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,QAAQ,EACR,WAAW,EACZ,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEzE,YAAY,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,QAAQ,EAAE,kBAAkB,EAAE,WAAW,EAAE,CAAC;AAEhI,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,OAAO,CAAC;IAC3B,WAAW,EAAE,kBAAkB,CAAC;IAChC,QAAQ,EAAE,QAAQ,CAAC;IACnB,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG;QACpB,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QACjC,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;KAClC,CAAC;CACH;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC,CAAC;CACvE"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { Address } from '@agenticprimitives/types';
|
|
2
|
+
import type { ToolClassification } from '@agenticprimitives/tool-policy';
|
|
3
|
+
import { type AuditSink, type MetricsSink } from '@agenticprimitives/audit';
|
|
4
|
+
import type { DataScopeGrant, McpResourceVerifyConfig } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* H7-F.1 / PKG-MCP-RUNTIME-003 / EXT-026 / EXT-032 closure — split
|
|
7
|
+
* public error surface from private failure context.
|
|
8
|
+
*
|
|
9
|
+
* Previously `McpAuthError.reason` carried the full denial cause
|
|
10
|
+
* ("policy deny: high-risk tool requires quorum", "delegation
|
|
11
|
+
* revoked at block N", "ERC-1271 returned 0x00…", etc.). A relying
|
|
12
|
+
* app that forwarded `error.reason` to the client leaked denial
|
|
13
|
+
* cause + occasionally PII (signer addresses, delegation hashes).
|
|
14
|
+
*
|
|
15
|
+
* The new surface:
|
|
16
|
+
* - {@link McpAuthError} is OPAQUE: carries only `code` (a small,
|
|
17
|
+
* bounded set) + an opaque `correlationId` the operator can use
|
|
18
|
+
* to look up the audit row.
|
|
19
|
+
* - {@link PrivateAuthFailureContext} is the rich shape emitted to
|
|
20
|
+
* the audit sink at the moment of failure. NEVER returned to the
|
|
21
|
+
* caller; consumed only by ops + forensics tooling.
|
|
22
|
+
*
|
|
23
|
+
* Migration note: tests that previously asserted on `error.reason`
|
|
24
|
+
* should assert on `error.code` instead and (for the rich shape)
|
|
25
|
+
* intercept the audit sink.
|
|
26
|
+
*/
|
|
27
|
+
export type McpAuthErrorCode = 'auth-failed' | 'auth-misconfigured' | 'auth-paused';
|
|
28
|
+
export declare class McpAuthError extends Error {
|
|
29
|
+
readonly code: McpAuthErrorCode;
|
|
30
|
+
readonly correlationId: string;
|
|
31
|
+
constructor(code: McpAuthErrorCode, correlationId: string);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Rich failure context emitted to the audit sink. NEVER returned to
|
|
35
|
+
* the caller.
|
|
36
|
+
*/
|
|
37
|
+
export interface PrivateAuthFailureContext {
|
|
38
|
+
/** Same correlationId as the thrown {@link McpAuthError}. */
|
|
39
|
+
correlationId: string;
|
|
40
|
+
/** The opaque public code. */
|
|
41
|
+
code: McpAuthErrorCode;
|
|
42
|
+
/**
|
|
43
|
+
* The ORIGINAL detailed denial reason (private). May contain
|
|
44
|
+
* delegation hashes, on-chain addresses, classification tier labels,
|
|
45
|
+
* etc. Routed to the audit sink (durable, op-only).
|
|
46
|
+
*/
|
|
47
|
+
reason: string;
|
|
48
|
+
/** Tool name if known. */
|
|
49
|
+
toolName?: string;
|
|
50
|
+
/** Step the failure happened at (`verify`, `policy`, `classification`, etc.). */
|
|
51
|
+
stage?: string;
|
|
52
|
+
}
|
|
53
|
+
export declare function withDelegation<A extends Record<string, unknown>>(config: McpResourceVerifyConfig, handler: (args: A & {
|
|
54
|
+
principal: Address;
|
|
55
|
+
grants?: DataScopeGrant[];
|
|
56
|
+
}) => Promise<unknown>, opts?: {
|
|
57
|
+
toolName?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Tool classification metadata (audit H2). When provided, the
|
|
60
|
+
* `evaluatePolicy()` decision engine runs after delegation verify;
|
|
61
|
+
* `deny` and `requires-consent` both reject with McpAuthError. When
|
|
62
|
+
* omitted, an internal warning is logged but the call proceeds
|
|
63
|
+
* (back-compat for unclassified demo tools). Production code MUST
|
|
64
|
+
* pass classification — the production preflight will eventually
|
|
65
|
+
* enforce this.
|
|
66
|
+
*/
|
|
67
|
+
classification?: ToolClassification;
|
|
68
|
+
/**
|
|
69
|
+
* Audit sink (audit C3). When provided, the wrapper emits
|
|
70
|
+
* `mcp-runtime.with-delegation.{accept,reject}` events on every
|
|
71
|
+
* call. Omit only for tests / paths that explicitly opt out of
|
|
72
|
+
* forensics; production code MUST pass a sink and the preflight
|
|
73
|
+
* will eventually enforce.
|
|
74
|
+
*/
|
|
75
|
+
auditSink?: AuditSink;
|
|
76
|
+
/** Correlation ID threaded into emitted events. */
|
|
77
|
+
correlationId?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Metrics sink (production-readiness wave 1). When provided, the
|
|
80
|
+
* wrapper emits:
|
|
81
|
+
* - counter `mcp_runtime.with_delegation.calls` per call
|
|
82
|
+
* (tags: tool, audience, outcome ∈ accept|reject)
|
|
83
|
+
* - histogram `mcp_runtime.with_delegation.duration_ms` per call
|
|
84
|
+
* (tags: tool, audience, outcome)
|
|
85
|
+
* Cardinality is bounded by the tool registry; safe for production
|
|
86
|
+
* Prometheus / Datadog / OpenTelemetry pipelines.
|
|
87
|
+
*/
|
|
88
|
+
metricsSink?: MetricsSink;
|
|
89
|
+
/**
|
|
90
|
+
* W3C traceparent header value (`00-{trace-id}-{parent-id}-{flags}`)
|
|
91
|
+
* captured from the inbound request. Forwarded on outbound calls
|
|
92
|
+
* the handler makes and stamped into emitted audit events so the
|
|
93
|
+
* full session→token→tool chain stitches together end-to-end.
|
|
94
|
+
*/
|
|
95
|
+
traceparent?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Production-readiness gate (audit H1). Inverted default behaviour:
|
|
98
|
+
* `withDelegation` runs in PRODUCTION mode unless the consumer
|
|
99
|
+
* explicitly opts out via `developmentMode: true` (test/dev shim)
|
|
100
|
+
* OR the runtime reports `NODE_ENV !== 'production'`. In
|
|
101
|
+
* production, the wrapper throws at construction time if
|
|
102
|
+
* `classification` or `auditSink` is missing. This makes the
|
|
103
|
+
* package API impossible to misuse: you can't register a tool
|
|
104
|
+
* wrapper without the metadata the policy engine + audit pipeline
|
|
105
|
+
* need.
|
|
106
|
+
*
|
|
107
|
+
* Pass `environment: 'production'` to force production (the
|
|
108
|
+
* canonical override; useful for tests that need to exercise prod
|
|
109
|
+
* gates without setting NODE_ENV). Pass `environment: 'development'`
|
|
110
|
+
* or `developmentMode: true` to opt out — required only for tests.
|
|
111
|
+
*/
|
|
112
|
+
environment?: 'production' | 'development';
|
|
113
|
+
/**
|
|
114
|
+
* Explicit opt-out shorthand for non-production callers. Equivalent
|
|
115
|
+
* to `environment: 'development'`. Useful so test code reads as
|
|
116
|
+
* "this is intentionally a dev wrapper" rather than referencing
|
|
117
|
+
* the environment axis directly.
|
|
118
|
+
*/
|
|
119
|
+
developmentMode?: boolean;
|
|
120
|
+
}): (args: A & {
|
|
121
|
+
token: string;
|
|
122
|
+
}) => Promise<unknown>;
|
|
123
|
+
export interface VerifyDelegationForResourceOpts {
|
|
124
|
+
toolName?: string;
|
|
125
|
+
}
|
|
126
|
+
export declare function verifyDelegationForResource(token: string, config: McpResourceVerifyConfig, ctx?: {
|
|
127
|
+
toolName?: string;
|
|
128
|
+
timestamp?: number;
|
|
129
|
+
}): Promise<{
|
|
130
|
+
principal: Address;
|
|
131
|
+
grants?: DataScopeGrant[];
|
|
132
|
+
} | {
|
|
133
|
+
error: string;
|
|
134
|
+
}>;
|
|
135
|
+
//# sourceMappingURL=with-delegation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-delegation.d.ts","sourceRoot":"","sources":["../src/with-delegation.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAO,MAAM,0BAA0B,CAAC;AAG7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAc,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACxF,OAAO,KAAK,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AA4BvE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,gBAAgB,GACxB,aAAa,GACb,oBAAoB,GACpB,aAAa,CAAC;AAElB,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;gBACnB,IAAI,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM;CAM1D;AAED;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,IAAI,EAAE,gBAAgB,CAAC;IACvB;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9D,MAAM,EAAE,uBAAuB,EAC/B,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,cAAc,EAAE,CAAA;CAAE,KAAK,OAAO,CAAC,OAAO,CAAC,EAC1F,IAAI,CAAC,EAAE;IACL,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACpC;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;;;;OAeG;IACH,WAAW,CAAC,EAAE,YAAY,GAAG,aAAa,CAAC;IAC3C;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,GACA,CAAC,IAAI,EAAE,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAgNnD;AAED,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,uBAAuB,EAC/B,GAAG,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9C,OAAO,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,cAAc,EAAE,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAYhF"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// withDelegation — the headline wrapper.
|
|
2
|
+
//
|
|
3
|
+
// Pipeline (per spec 205 §2):
|
|
4
|
+
// 1. extract token from args
|
|
5
|
+
// 2. delegation.verifyDelegationToken(token, opts) — full chain check
|
|
6
|
+
// 3. tool-policy.evaluatePolicy(ctx) for classification gating
|
|
7
|
+
// (audit H2; fail-closed on deny / requires-consent / unknown
|
|
8
|
+
// classification)
|
|
9
|
+
// 4. invoke inner handler with verified { principal, grants? }
|
|
10
|
+
//
|
|
11
|
+
// Error responses must NOT leak the specific failure mode (malformed vs
|
|
12
|
+
// expired vs revoked vs caveat-failed vs policy-denied). Single
|
|
13
|
+
// "auth failed" error class.
|
|
14
|
+
import { verifyDelegationToken } from '@agenticprimitives/delegation';
|
|
15
|
+
import { evaluatePolicy, evaluateThresholdPolicy } from '@agenticprimitives/tool-policy';
|
|
16
|
+
import { buildEvent } from '@agenticprimitives/audit';
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the effective environment for production-mode gates. Order
|
|
19
|
+
* of precedence:
|
|
20
|
+
* 1. Explicit `opts.environment` value.
|
|
21
|
+
* 2. `developmentMode: true` → 'development'.
|
|
22
|
+
* 3. `process.env.NODE_ENV` if readable.
|
|
23
|
+
* 4. Default to 'production' — safe-by-default when the runtime is
|
|
24
|
+
* ambiguous (Cloudflare Workers, Deno, browser). Consumers who
|
|
25
|
+
* want a permissive wrapper MUST opt out explicitly.
|
|
26
|
+
*/
|
|
27
|
+
function inferEnvironment(opts) {
|
|
28
|
+
if (opts?.environment)
|
|
29
|
+
return opts.environment;
|
|
30
|
+
if (opts?.developmentMode === true)
|
|
31
|
+
return 'development';
|
|
32
|
+
try {
|
|
33
|
+
if (typeof process !== 'undefined' && process.env?.NODE_ENV) {
|
|
34
|
+
return process.env.NODE_ENV === 'production' ? 'production' : 'development';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
/* SES / Workers may throw on process access */
|
|
39
|
+
}
|
|
40
|
+
return 'production';
|
|
41
|
+
}
|
|
42
|
+
export class McpAuthError extends Error {
|
|
43
|
+
code;
|
|
44
|
+
correlationId;
|
|
45
|
+
constructor(code, correlationId) {
|
|
46
|
+
super('mcp: auth failed');
|
|
47
|
+
this.name = 'McpAuthError';
|
|
48
|
+
this.code = code;
|
|
49
|
+
this.correlationId = correlationId;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function withDelegation(config, handler, opts) {
|
|
53
|
+
// Inferred environment — production-by-default per audit H1. The
|
|
54
|
+
// construction-time gate now fires unless the consumer explicitly
|
|
55
|
+
// opts into development.
|
|
56
|
+
const env = inferEnvironment(opts);
|
|
57
|
+
if (env === 'production') {
|
|
58
|
+
if (!opts?.classification) {
|
|
59
|
+
throw new Error('[mcp-runtime] withDelegation requires `classification` in production. ' +
|
|
60
|
+
'The policy engine (tool-policy.evaluatePolicy) MUST run; an unclassified tool ' +
|
|
61
|
+
'is a security regression. Pass `opts.classification = declareTool(...)`. ' +
|
|
62
|
+
'For tests, pass `developmentMode: true` to opt out of the strict gate.');
|
|
63
|
+
}
|
|
64
|
+
if (!opts?.auditSink) {
|
|
65
|
+
throw new Error('[mcp-runtime] withDelegation requires `auditSink` in production. ' +
|
|
66
|
+
'Audit emission is the only forensic trail for delegation accept/reject; ' +
|
|
67
|
+
'production deployments MUST persist these. Pass a durable sink (D1, ' +
|
|
68
|
+
'Cloud Logging, etc.) — wrap with composeSinks(durable, console) if you ' +
|
|
69
|
+
'still want a tail-friendly mirror.');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return async (args) => {
|
|
73
|
+
// Pull `quorumProof` off args (audit H3). It's a delegation-layer
|
|
74
|
+
// concern, not handler input. Either the caller serializes proof
|
|
75
|
+
// alongside the token, or there's no proof — and the verifier
|
|
76
|
+
// rejects accordingly when `requireQuorumCaveat` is set.
|
|
77
|
+
const { token, quorumProof, ...rest } = args;
|
|
78
|
+
const toolName = opts?.toolName ?? 'unknown';
|
|
79
|
+
// H7-F.1: every request gets a stable correlationId. Caller-supplied
|
|
80
|
+
// wins; otherwise mint a random one so the thrown McpAuthError carries
|
|
81
|
+
// a non-empty handle the operator can correlate with audit rows.
|
|
82
|
+
const correlationId = opts?.correlationId ??
|
|
83
|
+
`wd-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
84
|
+
const startedAt = Date.now();
|
|
85
|
+
const metric = opts?.metricsSink;
|
|
86
|
+
const emit = async (outcome, reason, principal) => {
|
|
87
|
+
// Metrics fire in lockstep with audit emissions so a single
|
|
88
|
+
// observability pipeline sees both the structured event AND the
|
|
89
|
+
// counter/histogram. Cardinality is bounded by tool name +
|
|
90
|
+
// audience + outcome — safe for production Prometheus.
|
|
91
|
+
if (metric) {
|
|
92
|
+
const tags = {
|
|
93
|
+
tool: toolName,
|
|
94
|
+
audience: config.audience ?? 'unknown',
|
|
95
|
+
outcome: outcome === 'success' ? 'accept' : 'reject',
|
|
96
|
+
};
|
|
97
|
+
try {
|
|
98
|
+
metric.increment('mcp_runtime.with_delegation.calls', 1, tags);
|
|
99
|
+
}
|
|
100
|
+
catch { /* fail-soft */ }
|
|
101
|
+
try {
|
|
102
|
+
metric.observe('mcp_runtime.with_delegation.duration_ms', Date.now() - startedAt, tags);
|
|
103
|
+
}
|
|
104
|
+
catch { /* fail-soft */ }
|
|
105
|
+
}
|
|
106
|
+
if (!opts?.auditSink)
|
|
107
|
+
return;
|
|
108
|
+
try {
|
|
109
|
+
await opts.auditSink.write(buildEvent({
|
|
110
|
+
action: outcome === 'success'
|
|
111
|
+
? 'mcp-runtime.with-delegation.accept'
|
|
112
|
+
: 'mcp-runtime.with-delegation.reject',
|
|
113
|
+
outcome,
|
|
114
|
+
correlationId,
|
|
115
|
+
actor: principal ? { type: 'user', id: principal } : { type: 'unknown' },
|
|
116
|
+
subject: { type: 'tool', id: toolName },
|
|
117
|
+
audience: config.audience,
|
|
118
|
+
chainId: config.chainId,
|
|
119
|
+
reason,
|
|
120
|
+
// Traceparent (W3C) stamped into context for downstream
|
|
121
|
+
// trace correlation. Reject events still carry the trace.
|
|
122
|
+
context: opts?.traceparent ? { traceparent: opts.traceparent } : undefined,
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Fail-soft: audit emission must never break the auth flow.
|
|
127
|
+
// composeSinks should be doing this for us, but belt-and-braces.
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
if (typeof token !== 'string' || token.length === 0) {
|
|
131
|
+
// H7-F.1: private reason goes to audit; caller sees opaque code.
|
|
132
|
+
await emit('denied', 'missing token', undefined);
|
|
133
|
+
throw new McpAuthError('auth-failed', correlationId);
|
|
134
|
+
}
|
|
135
|
+
// Spec 207 threshold-policy: when a classification is provided,
|
|
136
|
+
// derive the threshold-policy decision via tool-policy +
|
|
137
|
+
// translate into delegation's verify opts. Pre-verify check so a
|
|
138
|
+
// missing quorum caveat or absent on-chain blessing fails closed
|
|
139
|
+
// before any chain reads.
|
|
140
|
+
let requireQuorumCaveat;
|
|
141
|
+
let requireAcceptedOnChain;
|
|
142
|
+
if (opts?.classification) {
|
|
143
|
+
const thrDecision = evaluateThresholdPolicy(opts.classification);
|
|
144
|
+
if (thrDecision.requiresQuorum) {
|
|
145
|
+
if (!config.quorumEnforcer) {
|
|
146
|
+
// Fail closed at the boundary — a T3+ tool that needs
|
|
147
|
+
// quorum can't be verified if the runtime doesn't know
|
|
148
|
+
// where to find the QuorumEnforcer. Consumer apps must
|
|
149
|
+
// wire `config.quorumEnforcer` from their deployments JSON.
|
|
150
|
+
const detail = 'tool requires quorum caveat but mcp-runtime has no quorumEnforcer configured';
|
|
151
|
+
console.error('[mcp-runtime] auth misconfigured:', detail);
|
|
152
|
+
await emit('denied', detail, undefined);
|
|
153
|
+
// H7-F.1: server-side config gap surfaces as the distinct
|
|
154
|
+
// 'auth-misconfigured' code so the caller can distinguish from
|
|
155
|
+
// a legitimate credential reject.
|
|
156
|
+
throw new McpAuthError('auth-misconfigured', correlationId);
|
|
157
|
+
}
|
|
158
|
+
requireQuorumCaveat = { enforcer: config.quorumEnforcer };
|
|
159
|
+
}
|
|
160
|
+
if (thrDecision.requiresAcceptedOnChain) {
|
|
161
|
+
requireAcceptedOnChain = true;
|
|
162
|
+
}
|
|
163
|
+
// `requiresUv` from the threshold-policy decision is verified
|
|
164
|
+
// at the SIGNER layer (the wallet sets the WebAuthn UV flag when
|
|
165
|
+
// producing the delegation signature). delegation's verify path
|
|
166
|
+
// doesn't re-check UV because it would need to parse the
|
|
167
|
+
// passkey signature blob; that's the consumer app's
|
|
168
|
+
// responsibility at signing time.
|
|
169
|
+
}
|
|
170
|
+
const result = await verifyDelegationToken(token, {
|
|
171
|
+
audience: config.audience,
|
|
172
|
+
chainId: config.chainId,
|
|
173
|
+
rpcUrl: config.rpcUrl,
|
|
174
|
+
delegationManager: config.delegationManager,
|
|
175
|
+
enforcerMap: config.enforcerMap,
|
|
176
|
+
jtiStore: config.jtiStore,
|
|
177
|
+
toolName: opts?.toolName,
|
|
178
|
+
requireDeployed: config.requireDeployed,
|
|
179
|
+
// Thread the audit sink + correlation id down so delegation
|
|
180
|
+
// emits `delegation.verify.{accept,reject}` events through the
|
|
181
|
+
// same sink as `mcp-runtime.with-delegation.*`. Pass 3b.
|
|
182
|
+
auditSink: opts?.auditSink,
|
|
183
|
+
correlationId,
|
|
184
|
+
// Spec 207 threshold-policy gates (6c.4). Both undefined for T1
|
|
185
|
+
// tools; either or both set for T2+ depending on the tool's
|
|
186
|
+
// `@sa-risk-tier` classification.
|
|
187
|
+
requireQuorumCaveat,
|
|
188
|
+
requireAcceptedOnChain,
|
|
189
|
+
// Audit H3 — when requireQuorumCaveat is set, delegation refuses
|
|
190
|
+
// without an explicit proof. Forward the caller-supplied proof
|
|
191
|
+
// through; if missing, the verifier rejects (a Wave H1 production
|
|
192
|
+
// wrapper would have already thrown at construction if the tool
|
|
193
|
+
// is unclassified).
|
|
194
|
+
quorumProof,
|
|
195
|
+
});
|
|
196
|
+
if ('error' in result) {
|
|
197
|
+
// H7-F.1: the private reason (which may carry delegation hashes
|
|
198
|
+
// or signer addresses) is emitted to the audit sink and stays
|
|
199
|
+
// server-side. The caller sees only the opaque code + correlationId
|
|
200
|
+
// they can quote to the operator for forensics.
|
|
201
|
+
console.error('[mcp-runtime] auth failed:', result.error);
|
|
202
|
+
await emit('denied', result.error, undefined);
|
|
203
|
+
throw new McpAuthError('auth-failed', correlationId);
|
|
204
|
+
}
|
|
205
|
+
// Policy enforcement (audit H2). Fail-closed on deny + requires-consent.
|
|
206
|
+
if (opts?.classification) {
|
|
207
|
+
const decision = evaluatePolicy({
|
|
208
|
+
toolName,
|
|
209
|
+
classification: opts.classification,
|
|
210
|
+
callerKind: 'user-session',
|
|
211
|
+
delegation: {
|
|
212
|
+
delegator: result.principal,
|
|
213
|
+
delegate: result.principal,
|
|
214
|
+
caveats: [],
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
if (decision.decision !== 'allow') {
|
|
218
|
+
// Spec 207 reconciliation: critical-risk tools have historically
|
|
219
|
+
// returned `requires-consent` from evaluatePolicy + the wrapper
|
|
220
|
+
// failed closed because the runtime doesn't host a consent loop
|
|
221
|
+
// (audit H2). The threshold-policy `requiresAcceptedOnChain`
|
|
222
|
+
// gate IS the consent loop for that path — the user committed
|
|
223
|
+
// an `acceptSessionDelegation(hash)` transaction in advance.
|
|
224
|
+
// When that gate is in force AND it passed (verify didn't
|
|
225
|
+
// reject), the requires-consent outcome is satisfied.
|
|
226
|
+
const thrDec = evaluateThresholdPolicy(opts.classification);
|
|
227
|
+
const satisfiedByOnChainBlessing = decision.decision === 'requires-consent' && thrDec.requiresAcceptedOnChain;
|
|
228
|
+
if (!satisfiedByOnChainBlessing) {
|
|
229
|
+
const detail = decision.decision === 'deny'
|
|
230
|
+
? `policy deny: ${decision.reason}`
|
|
231
|
+
: `policy requires-consent (${decision.promptId}); runtime does not host consent loop`;
|
|
232
|
+
console.error('[mcp-runtime] auth failed:', detail);
|
|
233
|
+
await emit('denied', detail, result.principal);
|
|
234
|
+
// H7-F.1: same opaque shape as other reject paths.
|
|
235
|
+
throw new McpAuthError('auth-failed', correlationId);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
console.warn('[mcp-runtime] withDelegation called without classification — ' +
|
|
241
|
+
'consider passing opts.classification for policy enforcement (audit H2)');
|
|
242
|
+
}
|
|
243
|
+
await emit('success', undefined, result.principal);
|
|
244
|
+
return handler({ ...rest, principal: result.principal, grants: result.grants });
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
export async function verifyDelegationForResource(token, config, ctx) {
|
|
248
|
+
return verifyDelegationToken(token, {
|
|
249
|
+
audience: config.audience,
|
|
250
|
+
chainId: config.chainId,
|
|
251
|
+
rpcUrl: config.rpcUrl,
|
|
252
|
+
delegationManager: config.delegationManager,
|
|
253
|
+
enforcerMap: config.enforcerMap,
|
|
254
|
+
jtiStore: config.jtiStore,
|
|
255
|
+
toolName: ctx?.toolName,
|
|
256
|
+
requireDeployed: config.requireDeployed,
|
|
257
|
+
now: ctx?.timestamp ? () => ctx.timestamp * 1000 : undefined,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
// H7-B.8 (XPKG-002 / EXT-024 closure) — `withCrossDelegation` +
|
|
261
|
+
// `verifyCrossDelegationForResource` were public symbols that
|
|
262
|
+
// unconditionally rejected. Removed from the public surface; will land
|
|
263
|
+
// behind `./experimental` per spec 100 §6 when the cross-delegation work
|
|
264
|
+
// resumes. See PKG-mcp-runtime-001 in
|
|
265
|
+
// docs/audits/2026-05-packages-contracts-production-readiness.md.
|
|
266
|
+
//# sourceMappingURL=with-delegation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-delegation.js","sourceRoot":"","sources":["../src/with-delegation.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agenticprimitives/mcp-runtime",
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
|
+
"description": "Delegation-aware authorization middleware around the official MCP TypeScript SDK. The decision layer, not the SDK.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/agentictrustlabs/agenticprimitives.git",
|
|
9
|
+
"directory": "packages/mcp-runtime"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/agentictrustlabs/agenticprimitives/tree/master/packages/mcp-runtime",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/agentictrustlabs/agenticprimitives/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./testing": {
|
|
24
|
+
"types": "./dist/testing.d.ts",
|
|
25
|
+
"import": "./dist/testing.js"
|
|
26
|
+
},
|
|
27
|
+
"./lint": {
|
|
28
|
+
"types": "./dist/lint.d.ts",
|
|
29
|
+
"import": "./dist/lint.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"LICENSE",
|
|
34
|
+
"dist",
|
|
35
|
+
"spec.md",
|
|
36
|
+
"README.md"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.build.json",
|
|
40
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:unit": "vitest run test/unit",
|
|
43
|
+
"test:integration": "vitest run test/integration --passWithNoTests",
|
|
44
|
+
"test:watch": "vitest",
|
|
45
|
+
"clean": "rm -rf dist"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@agenticprimitives/audit": "workspace:*",
|
|
52
|
+
"@agenticprimitives/delegation": "workspace:*",
|
|
53
|
+
"@agenticprimitives/key-custody": "workspace:*",
|
|
54
|
+
"@agenticprimitives/tool-policy": "workspace:*",
|
|
55
|
+
"@agenticprimitives/types": "workspace:*",
|
|
56
|
+
"@modelcontextprotocol/sdk": ">=1.29.0 <2",
|
|
57
|
+
"viem": "^2.50.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/node": "^22.7.0",
|
|
61
|
+
"vitest": "^2.1.0"
|
|
62
|
+
},
|
|
63
|
+
"keywords": [
|
|
64
|
+
"mcp",
|
|
65
|
+
"model-context-protocol",
|
|
66
|
+
"delegation",
|
|
67
|
+
"authorization",
|
|
68
|
+
"agentic"
|
|
69
|
+
]
|
|
70
|
+
}
|