agent-passport-system-mcp 3.2.2 → 3.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/build/__tests__/capability-expiry.test.d.ts +1 -0
- package/build/__tests__/capability-expiry.test.js +36 -0
- package/build/__tests__/capability-token-e2e.test.js +38 -0
- package/build/capabilityToken/challengeReceipt.js +17 -6
- package/build/capabilityToken/verify.d.ts +3 -3
- package/build/capabilityToken/verify.js +21 -7
- package/build/index.js +27 -12
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ Or for remote SSE:
|
|
|
69
69
|
```
|
|
70
70
|
</details>
|
|
71
71
|
|
|
72
|
-
## Tools (
|
|
72
|
+
## Tools (150)
|
|
73
73
|
|
|
74
74
|
### Identity (Layer 1) — 5 tools
|
|
75
75
|
|
|
@@ -209,14 +209,13 @@ Layer 1 — Agent Passport Protocol (Ed25519 identity)
|
|
|
209
209
|
|
|
210
210
|
## Recognition
|
|
211
211
|
|
|
212
|
-
-
|
|
212
|
+
- Three contribution PRs merged into the Microsoft Agent Governance Toolkit by a Microsoft maintainer (#274, #598, #1328)
|
|
213
213
|
- Public comment submitted to NIST NCCoE on AI Agent Identity and Authorization standards
|
|
214
214
|
- Collaboration with IETF DAAP draft author on delegation spec
|
|
215
|
-
- Endorsed by Garry Tan (CEO, Y Combinator)
|
|
216
215
|
|
|
217
216
|
## Links
|
|
218
217
|
|
|
219
|
-
- npm SDK: [agent-passport-system](https://www.npmjs.com/package/agent-passport-system) (v2.
|
|
218
|
+
- npm SDK: [agent-passport-system](https://www.npmjs.com/package/agent-passport-system) (v2.9.0, 3,881 tests)
|
|
220
219
|
- Python SDK: [agent-passport-system](https://pypi.org/project/agent-passport-system/) (v2.4.0a3 pre-release; v2.3.0 stable)
|
|
221
220
|
- Go SDK: [agent-passport-go](https://pkg.go.dev/github.com/aeoess/agent-passport-go) (v0.2.0-alpha.1; `go get github.com/aeoess/agent-passport-go@v0.2.0-alpha.1`)
|
|
222
221
|
- Paper (Social Contract): [doi.org/10.5281/zenodo.18749779](https://doi.org/10.5281/zenodo.18749779)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Copyright 2024-2026 Tymofii Pidlisnyi. Apache-2.0 license. See LICENSE.
|
|
2
|
+
// Regression: a capability SinkChallenge carries a signed expires_at that no
|
|
3
|
+
// verifier enforced, so an expired permit stayed redeemable forever.
|
|
4
|
+
// verifySinkChallenge / verifyChallengeReceipt now reject an expired challenge.
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import { generateKeyPair } from "agent-passport-system";
|
|
8
|
+
import { issueSinkChallenge } from "../capabilityToken/sinkChallenge.js";
|
|
9
|
+
import { verifySinkChallenge } from "../capabilityToken/verify.js";
|
|
10
|
+
const action = {
|
|
11
|
+
kind: "fs.write",
|
|
12
|
+
target: "file:///tmp/aps-capability-expiry-test.txt",
|
|
13
|
+
parameters: { content_length: 1 },
|
|
14
|
+
resource_version: "v1",
|
|
15
|
+
};
|
|
16
|
+
test("verifySinkChallenge accepts a live challenge and rejects an expired one", () => {
|
|
17
|
+
const sink = generateKeyPair();
|
|
18
|
+
const issued = new Date("2026-01-01T00:00:00.000Z");
|
|
19
|
+
const challenge = issueSinkChallenge({
|
|
20
|
+
sink_id: sink.publicKey,
|
|
21
|
+
subject_id: "subject-1",
|
|
22
|
+
action,
|
|
23
|
+
sink_key: sink,
|
|
24
|
+
validity_seconds: 60,
|
|
25
|
+
now: issued,
|
|
26
|
+
});
|
|
27
|
+
// 30s after issue: within the 60s validity.
|
|
28
|
+
assert.equal(verifySinkChallenge(challenge, sink.publicKey, issued.getTime() + 30_000).ok, true);
|
|
29
|
+
// 120s after issue: past expiry.
|
|
30
|
+
const expired = verifySinkChallenge(challenge, sink.publicKey, issued.getTime() + 120_000);
|
|
31
|
+
assert.equal(expired.ok, false);
|
|
32
|
+
assert.match(expired.reason, /expired/);
|
|
33
|
+
// A valid signature with a wrong key still fails for the signature reason, not expiry.
|
|
34
|
+
const other = generateKeyPair();
|
|
35
|
+
assert.equal(verifySinkChallenge(challenge, other.publicKey, issued.getTime() + 30_000).ok, false);
|
|
36
|
+
});
|
|
@@ -379,3 +379,41 @@ test("Hash stability: re-issuing M1 with the same nonce + clock yields the same
|
|
|
379
379
|
assert.equal(challengeHash(a), challengeHash(b));
|
|
380
380
|
assert.equal(canonicalWithoutSignature(a, "sink_signature"), canonicalWithoutSignature(b, "sink_signature"));
|
|
381
381
|
});
|
|
382
|
+
// Regression (round-3, item 4): the gateway M3 mint must fail closed on an expired challenge.
|
|
383
|
+
// expires_at is signed into the M1 challenge, so minting a PERMIT after it would let a stale
|
|
384
|
+
// challenge be redeemed. mintChallengeReceipt now downgrades an expired permit to a deny.
|
|
385
|
+
test("M3 mint downgrades a permit over an expired challenge to a deny", () => {
|
|
386
|
+
const actors = setupActors();
|
|
387
|
+
const challenge = issueSinkChallenge({
|
|
388
|
+
sink_id: actors.sink.publicKey,
|
|
389
|
+
subject_id: actors.subject.publicKey,
|
|
390
|
+
action: sampleAction(),
|
|
391
|
+
sink_key: actors.sink,
|
|
392
|
+
});
|
|
393
|
+
const request = buildEvaluationRequest({
|
|
394
|
+
challenge,
|
|
395
|
+
delegation_chain: sampleDelegationChain(actors),
|
|
396
|
+
authority_token: sampleAuthorityToken(),
|
|
397
|
+
freshness_beacon: sampleFreshnessBeacon(actors),
|
|
398
|
+
subject_key: actors.subject,
|
|
399
|
+
});
|
|
400
|
+
// Mint with a clock far past the challenge's expires_at: permit must become deny.
|
|
401
|
+
const expired = mintChallengeReceipt({
|
|
402
|
+
request,
|
|
403
|
+
decision: "permit",
|
|
404
|
+
policy_digest: "policy-bundle-sha256-v0.1-fixture",
|
|
405
|
+
gateway_key: actors.gateway,
|
|
406
|
+
now: new Date("3000-01-01T00:00:00Z"),
|
|
407
|
+
});
|
|
408
|
+
assert.equal(expired.decision, "deny");
|
|
409
|
+
assert.equal(expired.deny_reason, "challenge_expired");
|
|
410
|
+
assert.equal(expired.authority_token_preimage, undefined);
|
|
411
|
+
// Control: minting within the validity window still permits.
|
|
412
|
+
const fresh = mintChallengeReceipt({
|
|
413
|
+
request,
|
|
414
|
+
decision: "permit",
|
|
415
|
+
policy_digest: "policy-bundle-sha256-v0.1-fixture",
|
|
416
|
+
gateway_key: actors.gateway,
|
|
417
|
+
});
|
|
418
|
+
assert.equal(fresh.decision, "permit");
|
|
419
|
+
});
|
|
@@ -17,23 +17,34 @@ const CLOSED_DENY_CLAIMS = {
|
|
|
17
17
|
effect_occurred: "unresolved",
|
|
18
18
|
};
|
|
19
19
|
export function mintChallengeReceipt(opts) {
|
|
20
|
+
const evaluatedAt = (opts.now ?? new Date()).toISOString();
|
|
21
|
+
// Fail closed on an expired challenge: expires_at is signed into the challenge, so the gateway
|
|
22
|
+
// must not mint a PERMIT after it. Downgrade an expired permit to a deny. nowMs defaults to the
|
|
23
|
+
// wall clock; an unparseable expires_at is treated as expired.
|
|
24
|
+
const nowMs = (opts.now ?? new Date()).getTime();
|
|
25
|
+
const expMs = Date.parse(opts.request.challenge.expires_at);
|
|
26
|
+
let decision = opts.decision;
|
|
27
|
+
let denyReason = opts.deny_reason;
|
|
28
|
+
if (decision === "permit" && (!Number.isFinite(expMs) || nowMs > expMs)) {
|
|
29
|
+
decision = "deny";
|
|
30
|
+
denyReason = "challenge_expired";
|
|
31
|
+
}
|
|
20
32
|
const cHash = opts.override_challenge_hash ?? challengeHash(opts.request.challenge);
|
|
21
33
|
const claims = opts.epistemic_claims ??
|
|
22
|
-
(
|
|
23
|
-
const evaluatedAt = (opts.now ?? new Date()).toISOString();
|
|
34
|
+
(decision === "permit" ? CLOSED_PERMIT_CLAIMS : CLOSED_DENY_CLAIMS);
|
|
24
35
|
const chainRoot = opts.omit_delegation_chain_root
|
|
25
36
|
? ""
|
|
26
37
|
: opts.override_delegation_chain_root ?? opts.request.delegation_chain_root;
|
|
27
38
|
const unsigned = {
|
|
28
39
|
type: "aps.capability.v1.ChallengeReceipt",
|
|
29
40
|
challenge_hash: cHash,
|
|
30
|
-
decision:
|
|
31
|
-
...(
|
|
32
|
-
? { deny_reason:
|
|
41
|
+
decision: decision,
|
|
42
|
+
...(decision === "deny" && denyReason
|
|
43
|
+
? { deny_reason: denyReason }
|
|
33
44
|
: {}),
|
|
34
45
|
delegation_chain_root: chainRoot,
|
|
35
46
|
delegation_depth: opts.request.delegation_depth,
|
|
36
|
-
...(
|
|
47
|
+
...(decision === "permit"
|
|
37
48
|
? { authority_token_preimage: opts.request.authority_token.token_preimage }
|
|
38
49
|
: {}),
|
|
39
50
|
evaluated_at: evaluatedAt,
|
|
@@ -5,15 +5,15 @@ export type VerifyResult = {
|
|
|
5
5
|
ok: false;
|
|
6
6
|
reason: string;
|
|
7
7
|
};
|
|
8
|
-
export declare function verifySinkChallenge(challenge: SinkChallenge, sinkPublicKey: string): VerifyResult;
|
|
9
|
-
export declare function verifyAuthorityEvaluationRequest(request: AuthorityEvaluationRequest, subjectPublicKey: string, sinkPublicKey: string): VerifyResult;
|
|
8
|
+
export declare function verifySinkChallenge(challenge: SinkChallenge, sinkPublicKey: string, nowMs?: number): VerifyResult;
|
|
9
|
+
export declare function verifyAuthorityEvaluationRequest(request: AuthorityEvaluationRequest, subjectPublicKey: string, sinkPublicKey: string, nowMs?: number): VerifyResult;
|
|
10
10
|
export interface VerifyChallengeReceiptOptions {
|
|
11
11
|
receipt: ChallengeReceipt;
|
|
12
12
|
expected_challenge: SinkChallenge;
|
|
13
13
|
expected_delegation_chain_root: string;
|
|
14
14
|
gateway_public_key: string;
|
|
15
15
|
}
|
|
16
|
-
export declare function verifyChallengeReceipt(opts: VerifyChallengeReceiptOptions): VerifyResult;
|
|
16
|
+
export declare function verifyChallengeReceipt(opts: VerifyChallengeReceiptOptions, nowMs?: number): VerifyResult;
|
|
17
17
|
export interface VerifyEffectReceiptOptions {
|
|
18
18
|
receipt: EffectReceipt;
|
|
19
19
|
challenge_receipt: ChallengeReceipt;
|
|
@@ -5,24 +5,38 @@ import { verify } from "agent-passport-system";
|
|
|
5
5
|
import { canonicalWithoutSignature } from "./canonical.js";
|
|
6
6
|
import { challengeHash } from "./sinkChallenge.js";
|
|
7
7
|
import { challengeReceiptHash } from "./challengeReceipt.js";
|
|
8
|
-
export function verifySinkChallenge(challenge, sinkPublicKey) {
|
|
8
|
+
export function verifySinkChallenge(challenge, sinkPublicKey, nowMs = Date.now()) {
|
|
9
9
|
const payload = canonicalWithoutSignature(challenge, "sink_signature");
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
if (!verify(payload, challenge.sink_signature, sinkPublicKey)) {
|
|
11
|
+
return { ok: false, reason: "sink_signature invalid" };
|
|
12
|
+
}
|
|
13
|
+
// Enforce expiry. expires_at is signed and propagated but was never checked, so an expired
|
|
14
|
+
// capability challenge stayed redeemable forever. nowMs defaults to the wall clock so the check
|
|
15
|
+
// is enforced by default and cannot fail open; tests inject a fixed clock.
|
|
16
|
+
const expMs = Date.parse(challenge.expires_at);
|
|
17
|
+
if (!Number.isFinite(expMs) || nowMs > expMs) {
|
|
18
|
+
return { ok: false, reason: "challenge expired" };
|
|
19
|
+
}
|
|
20
|
+
return { ok: true };
|
|
13
21
|
}
|
|
14
|
-
export function verifyAuthorityEvaluationRequest(request, subjectPublicKey, sinkPublicKey) {
|
|
22
|
+
export function verifyAuthorityEvaluationRequest(request, subjectPublicKey, sinkPublicKey, nowMs = Date.now()) {
|
|
15
23
|
const payload = canonicalWithoutSignature(request, "subject_signature");
|
|
16
24
|
if (!verify(payload, request.subject_signature, subjectPublicKey)) {
|
|
17
25
|
return { ok: false, reason: "subject_signature invalid" };
|
|
18
26
|
}
|
|
19
|
-
return verifySinkChallenge(request.challenge, sinkPublicKey);
|
|
27
|
+
return verifySinkChallenge(request.challenge, sinkPublicKey, nowMs);
|
|
20
28
|
}
|
|
21
|
-
export function verifyChallengeReceipt(opts) {
|
|
29
|
+
export function verifyChallengeReceipt(opts, nowMs = Date.now()) {
|
|
22
30
|
const payload = canonicalWithoutSignature(opts.receipt, "gateway_signature");
|
|
23
31
|
if (!verify(payload, opts.receipt.gateway_signature, opts.gateway_public_key)) {
|
|
24
32
|
return { ok: false, reason: "gateway_signature invalid" };
|
|
25
33
|
}
|
|
34
|
+
// Reject a receipt whose underlying challenge has expired. expires_at was never enforced, so an
|
|
35
|
+
// expired capability stayed redeemable. nowMs defaults to the wall clock (enforced by default).
|
|
36
|
+
const expMs = Date.parse(opts.expected_challenge.expires_at);
|
|
37
|
+
if (!Number.isFinite(expMs) || nowMs > expMs) {
|
|
38
|
+
return { ok: false, reason: "challenge expired" };
|
|
39
|
+
}
|
|
26
40
|
const expectedHash = challengeHash(opts.expected_challenge);
|
|
27
41
|
if (opts.receipt.challenge_hash !== expectedHash) {
|
|
28
42
|
return {
|
package/build/index.js
CHANGED
|
@@ -2309,13 +2309,21 @@ server.tool("commerce_preflight", "Run preflight checks before a purchase. Valid
|
|
|
2309
2309
|
platform: 'node',
|
|
2310
2310
|
models: ['mcp'],
|
|
2311
2311
|
});
|
|
2312
|
-
// Look up or create commerce delegation using actual scope/spend
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2312
|
+
// Look up or create commerce delegation using actual scope/spend.
|
|
2313
|
+
// Reflect spend already recorded on the session delegation instead of the hardcoded 0 that
|
|
2314
|
+
// createCommerceDelegation returns, so the spend gate is not a structural no-op that always
|
|
2315
|
+
// sees the full limit remaining. NOTE: nothing in the MCP yet INCREMENTS sessionDel.spentAmount
|
|
2316
|
+
// (a spend-record path is a metering decision flagged for Tima), so cumulative enforcement here
|
|
2317
|
+
// is only as complete as the session state that feeds it.
|
|
2318
|
+
const commerceDel = {
|
|
2319
|
+
...createCommerceDelegation({
|
|
2320
|
+
agentId: args.agent_id,
|
|
2321
|
+
delegationId: args.delegation_id,
|
|
2322
|
+
spendLimit: actualSpendLimit,
|
|
2323
|
+
approvedMerchants: [], // Empty = all merchants allowed
|
|
2324
|
+
}),
|
|
2325
|
+
spentAmount: sessionDel?.spentAmount ?? 0,
|
|
2326
|
+
};
|
|
2319
2327
|
const result = commercePreflight({
|
|
2320
2328
|
signedPassport: agent.passport,
|
|
2321
2329
|
delegation: commerceDel,
|
|
@@ -2339,11 +2347,18 @@ server.tool("get_commerce_spend", "Get spend analytics for a commerce delegation
|
|
|
2339
2347
|
delegation_id: z.string().describe("Commerce delegation ID"),
|
|
2340
2348
|
spend_limit: z.number().describe("Total allowed spend"),
|
|
2341
2349
|
}, async (args) => {
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2350
|
+
// Report spend recorded on the session delegation instead of always reporting 0. Same caveat
|
|
2351
|
+
// as commerce_preflight: nothing in the MCP yet increments sessionDel.spentAmount (metering
|
|
2352
|
+
// record path flagged for Tima), so this reflects whatever the session state holds.
|
|
2353
|
+
const sessionDel = state.delegations.get(args.delegation_id);
|
|
2354
|
+
const commerceDel = {
|
|
2355
|
+
...createCommerceDelegation({
|
|
2356
|
+
agentId: args.agent_id,
|
|
2357
|
+
delegationId: args.delegation_id,
|
|
2358
|
+
spendLimit: args.spend_limit,
|
|
2359
|
+
}),
|
|
2360
|
+
spentAmount: sessionDel?.spentAmount ?? 0,
|
|
2361
|
+
};
|
|
2347
2362
|
const summary = getSpendSummary(commerceDel);
|
|
2348
2363
|
return {
|
|
2349
2364
|
content: [{
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-passport-system-mcp",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.4",
|
|
4
4
|
"mcpName": "io.github.aeoess/agent-passport-mcp",
|
|
5
|
-
"description": "MCP server for the Agent Passport System
|
|
5
|
+
"description": "MCP server for the Agent Passport System - protocol-layer tools only. 150 tools (132 protocol + 10 gateway deprecation stubs). Identity, delegation, reputation, attestation, coordination, commerce, attribution primitive, attribution settlement. Tracks SDK v2.9.0 (Wave 1 accountability primitives + Phase 4.1 payment rails shipped in SDK; MCP rail-tool exposure deferred). For gateway-runtime tools (ProxyGateway, AgentContext, DataEnforcementGate), use gateway.aeoess.com REST API or pin to v3.1.x.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"agent-passport-system-mcp": "./build/bin.js",
|
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
"zod": "^3.25.76"
|
|
55
55
|
},
|
|
56
56
|
"overrides": {
|
|
57
|
-
"fast-uri": ">=3.1.2"
|
|
57
|
+
"fast-uri": ">=3.1.2",
|
|
58
|
+
"hono": "^4.12.27"
|
|
58
59
|
},
|
|
59
60
|
"devDependencies": {
|
|
60
61
|
"@types/node": "^22.19.19",
|