@mcp-i/core 1.1.0-canary.2 → 1.1.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/README.md +123 -333
- package/dist/auth/handshake.d.ts +19 -4
- package/dist/auth/handshake.d.ts.map +1 -1
- package/dist/auth/handshake.js +52 -15
- package/dist/auth/handshake.js.map +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/delegation/did-key-resolver.d.ts.map +1 -1
- package/dist/delegation/did-key-resolver.js +9 -6
- package/dist/delegation/did-key-resolver.js.map +1 -1
- package/dist/delegation/outbound-headers.d.ts +2 -4
- package/dist/delegation/outbound-headers.d.ts.map +1 -1
- package/dist/delegation/outbound-headers.js +2 -3
- package/dist/delegation/outbound-headers.js.map +1 -1
- package/dist/delegation/statuslist-manager.d.ts.map +1 -1
- package/dist/delegation/statuslist-manager.js +1 -1
- package/dist/delegation/statuslist-manager.js.map +1 -1
- package/dist/delegation/vc-verifier.d.ts.map +1 -1
- package/dist/delegation/vc-verifier.js +2 -2
- package/dist/delegation/vc-verifier.js.map +1 -1
- package/dist/errors.d.ts +42 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +45 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/index.d.ts +1 -0
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +1 -0
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/mcpi-transport.d.ts +39 -0
- package/dist/middleware/mcpi-transport.d.ts.map +1 -0
- package/dist/middleware/mcpi-transport.js +121 -0
- package/dist/middleware/mcpi-transport.js.map +1 -0
- package/dist/middleware/with-mcpi-server.d.ts +25 -9
- package/dist/middleware/with-mcpi-server.d.ts.map +1 -1
- package/dist/middleware/with-mcpi-server.js +62 -47
- package/dist/middleware/with-mcpi-server.js.map +1 -1
- package/dist/middleware/with-mcpi.d.ts +26 -5
- package/dist/middleware/with-mcpi.d.ts.map +1 -1
- package/dist/middleware/with-mcpi.js +108 -10
- package/dist/middleware/with-mcpi.js.map +1 -1
- package/dist/providers/memory.js +2 -2
- package/dist/providers/memory.js.map +1 -1
- package/dist/session/manager.d.ts +7 -1
- package/dist/session/manager.d.ts.map +1 -1
- package/dist/session/manager.js +20 -4
- package/dist/session/manager.js.map +1 -1
- package/dist/utils/crypto-service.d.ts.map +1 -1
- package/dist/utils/crypto-service.js +11 -10
- package/dist/utils/crypto-service.js.map +1 -1
- package/dist/utils/did-helpers.d.ts +12 -0
- package/dist/utils/did-helpers.d.ts.map +1 -1
- package/dist/utils/did-helpers.js +18 -0
- package/dist/utils/did-helpers.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/errors.test.ts +56 -0
- package/src/__tests__/integration/full-flow.test.ts +1 -1
- package/src/__tests__/integration/mcp-enhance-server.test.ts +48 -5
- package/src/__tests__/integration/mcp-transport-context7.test.ts +19 -15
- package/src/__tests__/integration/mcp-transport.test.ts +13 -10
- package/src/__tests__/providers/base.test.ts +1 -1
- package/src/__tests__/providers/memory.test.ts +2 -2
- package/src/__tests__/utils/mock-providers.ts +2 -2
- package/src/auth/__tests__/handshake.test.ts +190 -0
- package/src/auth/handshake.ts +88 -21
- package/src/auth/index.ts +1 -0
- package/src/delegation/__tests__/did-key-resolver.test.ts +2 -2
- package/src/delegation/__tests__/outbound-headers.test.ts +16 -20
- package/src/delegation/__tests__/statuslist-manager.test.ts +120 -7
- package/src/delegation/__tests__/vc-verifier.test.ts +45 -3
- package/src/delegation/did-key-resolver.ts +11 -6
- package/src/delegation/outbound-headers.ts +1 -4
- package/src/delegation/statuslist-manager.ts +3 -1
- package/src/delegation/vc-verifier.ts +3 -2
- package/src/errors.ts +65 -0
- package/src/index.ts +10 -0
- package/src/middleware/__tests__/mcpi-transport.test.ts +150 -0
- package/src/middleware/__tests__/with-mcpi-server.test.ts +117 -0
- package/src/middleware/__tests__/with-mcpi.test.ts +124 -6
- package/src/middleware/index.ts +6 -0
- package/src/middleware/mcpi-transport.ts +162 -0
- package/src/middleware/with-mcpi-server.ts +83 -92
- package/src/middleware/with-mcpi.ts +147 -11
- package/src/proof/__tests__/errors.test.ts +79 -0
- package/src/proof/__tests__/verifier.test.ts +5 -5
- package/src/providers/memory.ts +2 -2
- package/src/session/__tests__/session-manager.test.ts +3 -3
- package/src/session/manager.ts +28 -6
- package/src/utils/crypto-service.ts +11 -10
- package/src/utils/did-helpers.ts +19 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"did-helpers.d.ts","sourceRoot":"","sources":["../../src/utils/did-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,QAAQ,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD,GAAG,MAAM,CAMT;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIlD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpD;AAQD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,UAAU,GAAG,MAAM,CAW1E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAMxE"}
|
|
1
|
+
{"version":3,"file":"did-helpers.d.ts","sourceRoot":"","sources":["../../src/utils/did-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,QAAQ,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD,GAAG,MAAM,CAMT;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIlD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpD;AAQD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,UAAU,GAAG,MAAM,CAW1E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAMxE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMlD"}
|
|
@@ -190,4 +190,22 @@ export function generateDidKeyFromBase64(publicKeyBase64) {
|
|
|
190
190
|
const publicKeyBytes = Uint8Array.from(atob(publicKeyBase64), (c) => c.charCodeAt(0));
|
|
191
191
|
return generateDidKeyFromBytes(publicKeyBytes);
|
|
192
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Get the spec-compliant fragment identifier for a did:key DID.
|
|
195
|
+
*
|
|
196
|
+
* Per the did:key spec (W3C CCG), the fragment equals the multibase-encoded
|
|
197
|
+
* public key value (the DID-specific-id). For example:
|
|
198
|
+
* did:key:z6MkABC... → z6MkABC...
|
|
199
|
+
*
|
|
200
|
+
* @see https://w3c-ccg.github.io/did-key-spec/#document-creation-algorithm
|
|
201
|
+
* @param did - A did:key DID string
|
|
202
|
+
* @returns The fragment identifier (multibase value), or 'keys-1' as fallback for non-did:key
|
|
203
|
+
*/
|
|
204
|
+
export function didKeyFragment(did) {
|
|
205
|
+
if (did.startsWith('did:key:')) {
|
|
206
|
+
return did.slice('did:key:'.length);
|
|
207
|
+
}
|
|
208
|
+
// Fallback for non-did:key methods
|
|
209
|
+
return 'keys-1';
|
|
210
|
+
}
|
|
193
211
|
//# sourceMappingURL=did-helpers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"did-helpers.js","sourceRoot":"","sources":["../../src/utils/did-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IACzC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;AACpB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,IAAY;IACpD,OAAO,YAAY,CAAC,IAAI,CAAC,KAAK,YAAY,CAAC,IAAI,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,YAAY,CAAC,MAE5B;IACC,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;IACxE,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,8CAA8C;IAC9C,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC;AACxC,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,OAAO,cAAc,CAAC,GAAG,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,yBAAyB,GAAG,IAAI,UAAU,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAE/D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,uBAAuB,CAAC,cAA0B;IAChE,yCAAyC;IACzC,MAAM,aAAa,GAAG,IAAI,UAAU,CAClC,yBAAyB,CAAC,MAAM,GAAG,cAAc,CAAC,MAAM,CACzD,CAAC;IACF,aAAa,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IAC7C,aAAa,CAAC,GAAG,CAAC,cAAc,EAAE,yBAAyB,CAAC,MAAM,CAAC,CAAC;IAEpE,iDAAiD;IACjD,MAAM,aAAa,GAAG,YAAY,CAAC,aAAa,CAAC,CAAC;IAClD,OAAO,YAAY,aAAa,EAAE,CAAC;AACrC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,wBAAwB,CAAC,eAAuB;IAC9D,yBAAyB;IACzB,MAAM,cAAc,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAClE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAChB,CAAC;IACF,OAAO,uBAAuB,CAAC,cAAc,CAAC,CAAC;AACjD,CAAC"}
|
|
1
|
+
{"version":3,"file":"did-helpers.js","sourceRoot":"","sources":["../../src/utils/did-helpers.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-i/core",
|
|
3
|
-
"version": "1.1.0
|
|
4
|
-
"description": "MCP-I
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Core library for MCP-I — delegation, proof, and session primitives for Model Context Protocol Identity",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -62,7 +62,6 @@
|
|
|
62
62
|
"example:inspector": "npx @modelcontextprotocol/inspector npx tsx examples/node-server/server.ts --stdio"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@mcp-i/core": "^1.1.0-canary.1",
|
|
66
65
|
"jose": "^5.6.3",
|
|
67
66
|
"json-canonicalize": "^2.0.0"
|
|
68
67
|
},
|
|
@@ -76,6 +75,7 @@
|
|
|
76
75
|
},
|
|
77
76
|
"devDependencies": {
|
|
78
77
|
"@eslint/js": "^10.0.1",
|
|
78
|
+
"@kya-os/consent": "^0.1.37",
|
|
79
79
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
80
80
|
"@types/express": "^5.0.6",
|
|
81
81
|
"@types/node": "^20.14.9",
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
MCPI_ERROR_CODES,
|
|
4
|
+
createMCPIError,
|
|
5
|
+
} from "../errors.js";
|
|
6
|
+
|
|
7
|
+
describe("MCPI_ERROR_CODES", () => {
|
|
8
|
+
it("should define all canonical error codes", () => {
|
|
9
|
+
const codes = Object.values(MCPI_ERROR_CODES);
|
|
10
|
+
expect(codes).toContain("invalid_proof");
|
|
11
|
+
expect(codes).toContain("invalid_jws");
|
|
12
|
+
expect(codes).toContain("nonce_replay");
|
|
13
|
+
expect(codes).toContain("timestamp_skew");
|
|
14
|
+
expect(codes).toContain("did_not_found");
|
|
15
|
+
expect(codes).toContain("invalid_public_key");
|
|
16
|
+
expect(codes).toContain("handshake_failed");
|
|
17
|
+
expect(codes).toContain("session_expired");
|
|
18
|
+
expect(codes).toContain("invalid_request");
|
|
19
|
+
expect(codes).toContain("needs_authorization");
|
|
20
|
+
expect(codes).toContain("insufficient_scope");
|
|
21
|
+
expect(codes).toContain("delegation_expired");
|
|
22
|
+
expect(codes).toContain("delegation_not_yet_valid");
|
|
23
|
+
expect(codes).toContain("delegation_revoked");
|
|
24
|
+
expect(codes).toContain("delegation_invalid");
|
|
25
|
+
expect(codes).toContain("budget_exceeded");
|
|
26
|
+
expect(codes).toContain("rate_limit_exceeded");
|
|
27
|
+
expect(codes).toContain("invalid_token");
|
|
28
|
+
expect(codes).toContain("token_expired");
|
|
29
|
+
expect(codes).toContain("mirror_pending");
|
|
30
|
+
expect(codes).toContain("claim_failed");
|
|
31
|
+
expect(codes).toContain("configuration_error");
|
|
32
|
+
expect(codes).toContain("runtime_error");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should have key === value for every code", () => {
|
|
36
|
+
for (const [key, value] of Object.entries(MCPI_ERROR_CODES)) {
|
|
37
|
+
expect(key).toBe(value);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("createMCPIError", () => {
|
|
43
|
+
it("should create an error response with code and message", () => {
|
|
44
|
+
const error = createMCPIError("handshake_failed", "Nonce already used");
|
|
45
|
+
expect(error.code).toBe("handshake_failed");
|
|
46
|
+
expect(error.message).toBe("Nonce already used");
|
|
47
|
+
expect(error.details).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should include details when provided", () => {
|
|
51
|
+
const error = createMCPIError("delegation_revoked", "Credential revoked", {
|
|
52
|
+
credentialId: "urn:uuid:123",
|
|
53
|
+
});
|
|
54
|
+
expect(error.details).toEqual({ credentialId: "urn:uuid:123" });
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -74,7 +74,7 @@ describe("MCP-I Full Protocol Flow", () => {
|
|
|
74
74
|
const agent = await identityProvider.getIdentity();
|
|
75
75
|
|
|
76
76
|
expect(agent.did).toMatch(/^did:key:z/);
|
|
77
|
-
expect(agent.kid).toMatch(/#
|
|
77
|
+
expect(agent.kid).toMatch(/#z[\w]+$/);
|
|
78
78
|
|
|
79
79
|
// ── Step 2: Establish session via handshake ──────────────────
|
|
80
80
|
const serverDid = "did:web:test-server.example.com";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* withMCPI() Integration Tests
|
|
3
3
|
*
|
|
4
4
|
* Tests the dream API: `withMCPI(server, { crypto })` auto-registers
|
|
5
|
-
* the
|
|
5
|
+
* the `_mcpi` protocol tool and auto-attaches proofs to all tool responses.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { describe, it, expect, afterEach } from 'vitest';
|
|
@@ -20,6 +20,7 @@ async function createTestPair(options?: {
|
|
|
20
20
|
excludeTools?: string[];
|
|
21
21
|
autoSession?: boolean;
|
|
22
22
|
registerToolsBeforeWithMCPI?: boolean;
|
|
23
|
+
handshakeExposure?: 'tool' | 'none';
|
|
23
24
|
}) {
|
|
24
25
|
const crypto = new NodeCryptoProvider();
|
|
25
26
|
const server = new McpServer(
|
|
@@ -46,6 +47,7 @@ async function createTestPair(options?: {
|
|
|
46
47
|
autoSession: options?.autoSession ?? true,
|
|
47
48
|
proofAllTools: options?.proofAllTools,
|
|
48
49
|
excludeTools: options?.excludeTools,
|
|
50
|
+
handshakeExposure: options?.handshakeExposure,
|
|
49
51
|
});
|
|
50
52
|
|
|
51
53
|
// Register tools AFTER withMCPI to test late registration
|
|
@@ -103,13 +105,23 @@ describe('withMCPI()', () => {
|
|
|
103
105
|
return pair;
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
it('auto-registers
|
|
108
|
+
it('auto-registers _mcpi tool', async () => {
|
|
107
109
|
const { client } = await create();
|
|
108
110
|
|
|
109
111
|
const result = await client.listTools();
|
|
110
112
|
const toolNames = result.tools.map((t) => t.name);
|
|
111
113
|
|
|
112
|
-
expect(toolNames).toContain('
|
|
114
|
+
expect(toolNames).toContain('_mcpi');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('handshakeExposure: none does not auto-register _mcpi', async () => {
|
|
118
|
+
const { client } = await create({ handshakeExposure: 'none' });
|
|
119
|
+
|
|
120
|
+
const result = await client.listTools();
|
|
121
|
+
const toolNames = result.tools.map((t) => t.name);
|
|
122
|
+
|
|
123
|
+
expect(toolNames).not.toContain('_mcpi');
|
|
124
|
+
expect(toolNames).toContain('greet');
|
|
113
125
|
});
|
|
114
126
|
|
|
115
127
|
it('auto-proofs all registered tools', async () => {
|
|
@@ -204,8 +216,9 @@ describe('withMCPI()', () => {
|
|
|
204
216
|
const { client, mcpi } = await create({ autoSession: false });
|
|
205
217
|
|
|
206
218
|
const result = await client.callTool({
|
|
207
|
-
name: '
|
|
219
|
+
name: '_mcpi',
|
|
208
220
|
arguments: {
|
|
221
|
+
action: 'handshake',
|
|
209
222
|
nonce: `test-${Date.now()}`,
|
|
210
223
|
audience: mcpi.identity.did,
|
|
211
224
|
timestamp: Math.floor(Date.now() / 1000),
|
|
@@ -219,6 +232,36 @@ describe('withMCPI()', () => {
|
|
|
219
232
|
expect(parsed.serverDid).toBe(mcpi.identity.did);
|
|
220
233
|
});
|
|
221
234
|
|
|
235
|
+
it('manual handshake API works when handshake tool is not exposed', async () => {
|
|
236
|
+
const { client, mcpi } = await create({
|
|
237
|
+
autoSession: false,
|
|
238
|
+
handshakeExposure: 'none',
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
await mcpi.handleHandshake({
|
|
242
|
+
nonce: `manual-${Date.now()}`,
|
|
243
|
+
audience: mcpi.identity.did,
|
|
244
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const result = await client.callTool({
|
|
248
|
+
name: 'greet',
|
|
249
|
+
arguments: { name: 'Manual Handshake' },
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const first = result.content[0] as { type: string; text: string };
|
|
253
|
+
expect(first.text).toBe('Hello, Manual Handshake!');
|
|
254
|
+
|
|
255
|
+
expect(result._meta).toBeDefined();
|
|
256
|
+
const proof = (result._meta as Record<string, unknown>).proof as {
|
|
257
|
+
jws: string;
|
|
258
|
+
meta: Record<string, unknown>;
|
|
259
|
+
};
|
|
260
|
+
expect(proof).toBeDefined();
|
|
261
|
+
expect(proof.jws).toBeDefined();
|
|
262
|
+
expect(proof.meta.sessionId).toMatch(/^mcpi_/);
|
|
263
|
+
});
|
|
264
|
+
|
|
222
265
|
it('multiple tools share the same auto-session', async () => {
|
|
223
266
|
const { client } = await create();
|
|
224
267
|
|
|
@@ -292,7 +335,7 @@ describe('generateIdentity()', () => {
|
|
|
292
335
|
const identity = await generateIdentity(crypto);
|
|
293
336
|
|
|
294
337
|
expect(identity.did).toMatch(/^did:key:z6Mk/);
|
|
295
|
-
expect(identity.kid).toBe(`${identity.did}
|
|
338
|
+
expect(identity.kid).toBe(`${identity.did}#${identity.did.replace('did:key:', '')}`);
|
|
296
339
|
expect(identity.privateKey).toBeDefined();
|
|
297
340
|
expect(identity.publicKey).toBeDefined();
|
|
298
341
|
// Keys should be base64 strings
|
|
@@ -25,7 +25,7 @@ async function setupMcpServerPair(options?: { autoSession?: boolean }) {
|
|
|
25
25
|
const crypto = new NodeCryptoProvider();
|
|
26
26
|
const keyPair = await crypto.generateKeyPair();
|
|
27
27
|
const did = generateDidKeyFromBase64(keyPair.publicKey);
|
|
28
|
-
const kid = `${did}
|
|
28
|
+
const kid = `${did}#${did.replace('did:key:', '')}`;
|
|
29
29
|
|
|
30
30
|
const mcpi = createMCPIMiddleware(
|
|
31
31
|
{
|
|
@@ -43,18 +43,19 @@ async function setupMcpServerPair(options?: { autoSession?: boolean }) {
|
|
|
43
43
|
{ instructions: 'Test server for McpServer + MCP-I integration' },
|
|
44
44
|
);
|
|
45
45
|
|
|
46
|
-
// Register
|
|
46
|
+
// Register unified _mcpi tool (same pattern as Context7 integration)
|
|
47
47
|
server.registerTool(
|
|
48
|
-
'
|
|
48
|
+
'_mcpi',
|
|
49
49
|
{
|
|
50
|
-
description: 'MCP-I
|
|
50
|
+
description: 'MCP-I protocol',
|
|
51
51
|
inputSchema: {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
action: z.enum(['handshake', 'identity', 'reputation']),
|
|
53
|
+
nonce: z.string().optional(),
|
|
54
|
+
audience: z.string().optional(),
|
|
55
|
+
timestamp: z.number().optional(),
|
|
55
56
|
},
|
|
56
57
|
},
|
|
57
|
-
async (args) => mcpi.
|
|
58
|
+
async (args) => mcpi.handleMCPI(args as Record<string, unknown>),
|
|
58
59
|
);
|
|
59
60
|
|
|
60
61
|
// Wrap a test tool with proof (simulates Context7's resolve-library-id)
|
|
@@ -135,13 +136,13 @@ describe('McpServer (High-Level API) + MCP-I Integration', () => {
|
|
|
135
136
|
return pair;
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
it('listTools returns
|
|
139
|
+
it('listTools returns _mcpi + registered tools', async () => {
|
|
139
140
|
const { client } = await createPair();
|
|
140
141
|
|
|
141
142
|
const result = await client.listTools();
|
|
142
143
|
const toolNames = result.tools.map((t) => t.name);
|
|
143
144
|
|
|
144
|
-
expect(toolNames).toContain('
|
|
145
|
+
expect(toolNames).toContain('_mcpi');
|
|
145
146
|
expect(toolNames).toContain('search');
|
|
146
147
|
expect(toolNames).toContain('fetch-docs');
|
|
147
148
|
});
|
|
@@ -150,8 +151,9 @@ describe('McpServer (High-Level API) + MCP-I Integration', () => {
|
|
|
150
151
|
const { client, did } = await createPair();
|
|
151
152
|
|
|
152
153
|
const result = await client.callTool({
|
|
153
|
-
name: '
|
|
154
|
+
name: '_mcpi',
|
|
154
155
|
arguments: {
|
|
156
|
+
action: 'handshake',
|
|
155
157
|
nonce: `mcpserver-test-${Date.now()}`,
|
|
156
158
|
audience: did,
|
|
157
159
|
timestamp: Math.floor(Date.now() / 1000),
|
|
@@ -173,8 +175,9 @@ describe('McpServer (High-Level API) + MCP-I Integration', () => {
|
|
|
173
175
|
|
|
174
176
|
// Handshake first
|
|
175
177
|
await client.callTool({
|
|
176
|
-
name: '
|
|
178
|
+
name: '_mcpi',
|
|
177
179
|
arguments: {
|
|
180
|
+
action: 'handshake',
|
|
178
181
|
nonce: `mcpserver-test-${Date.now()}`,
|
|
179
182
|
audience: did,
|
|
180
183
|
timestamp: Math.floor(Date.now() / 1000),
|
|
@@ -337,13 +340,13 @@ describe('McpServer + withMCPI() (Dream API)', () => {
|
|
|
337
340
|
return { client, server, mcpi };
|
|
338
341
|
}
|
|
339
342
|
|
|
340
|
-
it('listTools returns
|
|
343
|
+
it('listTools returns _mcpi + registered tools (withMCPI)', async () => {
|
|
341
344
|
const { client } = await createWithMCPIPair();
|
|
342
345
|
|
|
343
346
|
const result = await client.listTools();
|
|
344
347
|
const toolNames = result.tools.map((t) => t.name);
|
|
345
348
|
|
|
346
|
-
expect(toolNames).toContain('
|
|
349
|
+
expect(toolNames).toContain('_mcpi');
|
|
347
350
|
expect(toolNames).toContain('search');
|
|
348
351
|
expect(toolNames).toContain('fetch-docs');
|
|
349
352
|
});
|
|
@@ -373,8 +376,9 @@ describe('McpServer + withMCPI() (Dream API)', () => {
|
|
|
373
376
|
const { client, mcpi } = await createWithMCPIPair({ autoSession: false });
|
|
374
377
|
|
|
375
378
|
const result = await client.callTool({
|
|
376
|
-
name: '
|
|
379
|
+
name: '_mcpi',
|
|
377
380
|
arguments: {
|
|
381
|
+
action: 'handshake',
|
|
378
382
|
nonce: `withmcpi-test-${Date.now()}`,
|
|
379
383
|
audience: mcpi.identity.did,
|
|
380
384
|
timestamp: Math.floor(Date.now() / 1000),
|
|
@@ -64,7 +64,7 @@ async function setupMcpPair(options?: { autoSession?: boolean }) {
|
|
|
64
64
|
const crypto = new NodeCryptoProvider();
|
|
65
65
|
const keyPair = await crypto.generateKeyPair();
|
|
66
66
|
const did = generateDidKeyFromBase64(keyPair.publicKey);
|
|
67
|
-
const kid = `${did}
|
|
67
|
+
const kid = `${did}#${did.replace('did:key:', '')}`;
|
|
68
68
|
|
|
69
69
|
const mcpi = createMCPIMiddleware(
|
|
70
70
|
{
|
|
@@ -101,7 +101,7 @@ async function setupMcpPair(options?: { autoSession?: boolean }) {
|
|
|
101
101
|
|
|
102
102
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
103
103
|
tools: [
|
|
104
|
-
mcpi.
|
|
104
|
+
mcpi.mcpiTool,
|
|
105
105
|
{
|
|
106
106
|
name: 'greet',
|
|
107
107
|
description: 'Returns a greeting with proof',
|
|
@@ -127,8 +127,8 @@ async function setupMcpPair(options?: { autoSession?: boolean }) {
|
|
|
127
127
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
128
128
|
const { name, arguments: args = {} } = request.params;
|
|
129
129
|
|
|
130
|
-
if (name === '
|
|
131
|
-
return mcpi.
|
|
130
|
+
if (name === '_mcpi') {
|
|
131
|
+
return mcpi.handleMCPI(args as Record<string, unknown>);
|
|
132
132
|
}
|
|
133
133
|
if (name === 'greet') {
|
|
134
134
|
return greetHandler(args as Record<string, unknown>);
|
|
@@ -157,7 +157,7 @@ async function issueDelegationVC(scopes: string[]): Promise<DelegationCredential
|
|
|
157
157
|
const crypto = new NodeCryptoProvider();
|
|
158
158
|
const keyPair = await crypto.generateKeyPair();
|
|
159
159
|
const did = generateDidKeyFromBase64(keyPair.publicKey);
|
|
160
|
-
const kid = `${did}
|
|
160
|
+
const kid = `${did}#${did.replace('did:key:', '')}`;
|
|
161
161
|
|
|
162
162
|
const signingFn = async (
|
|
163
163
|
canonicalVC: string,
|
|
@@ -211,14 +211,14 @@ describe('MCP Transport Integration', () => {
|
|
|
211
211
|
return pair;
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
it('listTools returns
|
|
214
|
+
it('listTools returns _mcpi + app tools', async () => {
|
|
215
215
|
const { client } = await createPair();
|
|
216
216
|
|
|
217
217
|
const result = await client.listTools();
|
|
218
218
|
const toolNames = result.tools.map((t) => t.name);
|
|
219
219
|
|
|
220
220
|
expect(toolNames).toHaveLength(3);
|
|
221
|
-
expect(toolNames).toContain('
|
|
221
|
+
expect(toolNames).toContain('_mcpi');
|
|
222
222
|
expect(toolNames).toContain('greet');
|
|
223
223
|
expect(toolNames).toContain('restricted_greet');
|
|
224
224
|
});
|
|
@@ -227,8 +227,9 @@ describe('MCP Transport Integration', () => {
|
|
|
227
227
|
const { client, did } = await createPair();
|
|
228
228
|
|
|
229
229
|
const result = await client.callTool({
|
|
230
|
-
name: '
|
|
230
|
+
name: '_mcpi',
|
|
231
231
|
arguments: {
|
|
232
|
+
action: 'handshake',
|
|
232
233
|
nonce: `transport-test-${Date.now()}`,
|
|
233
234
|
audience: did,
|
|
234
235
|
timestamp: Math.floor(Date.now() / 1000),
|
|
@@ -250,8 +251,9 @@ describe('MCP Transport Integration', () => {
|
|
|
250
251
|
|
|
251
252
|
// Handshake first
|
|
252
253
|
await client.callTool({
|
|
253
|
-
name: '
|
|
254
|
+
name: '_mcpi',
|
|
254
255
|
arguments: {
|
|
256
|
+
action: 'handshake',
|
|
255
257
|
nonce: `transport-test-${Date.now()}`,
|
|
256
258
|
audience: did,
|
|
257
259
|
timestamp: Math.floor(Date.now() / 1000),
|
|
@@ -287,8 +289,9 @@ describe('MCP Transport Integration', () => {
|
|
|
287
289
|
|
|
288
290
|
// Handshake
|
|
289
291
|
await client.callTool({
|
|
290
|
-
name: '
|
|
292
|
+
name: '_mcpi',
|
|
291
293
|
arguments: {
|
|
294
|
+
action: 'handshake',
|
|
292
295
|
nonce: `transport-test-${Date.now()}`,
|
|
293
296
|
audience: did,
|
|
294
297
|
timestamp: Math.floor(Date.now() / 1000),
|
|
@@ -155,7 +155,7 @@ describe('Base Provider Classes', () => {
|
|
|
155
155
|
it('should have proper type structure', () => {
|
|
156
156
|
const validIdentity = {
|
|
157
157
|
did: 'did:key:z123',
|
|
158
|
-
kid: 'did:key:z123#
|
|
158
|
+
kid: 'did:key:z123#z123',
|
|
159
159
|
privateKey: 'private-key',
|
|
160
160
|
publicKey: 'public-key',
|
|
161
161
|
createdAt: new Date().toISOString(),
|
|
@@ -222,7 +222,7 @@ describe('MemoryIdentityProvider', () => {
|
|
|
222
222
|
|
|
223
223
|
expect(identity).toBeDefined();
|
|
224
224
|
expect(identity.did).toMatch(/^did:key:z/);
|
|
225
|
-
expect(identity.kid).toBe(`${identity.did}
|
|
225
|
+
expect(identity.kid).toBe(`${identity.did}#${identity.did.replace('did:key:', '')}`);
|
|
226
226
|
expect(identity.privateKey).toMatch(/^[A-Za-z0-9+/=]+$/); // Valid base64 string
|
|
227
227
|
expect(identity.publicKey).toMatch(/^[A-Za-z0-9+/=]+$/); // Valid base64 string
|
|
228
228
|
expect(identity.type).toBe('development');
|
|
@@ -246,7 +246,7 @@ describe('MemoryIdentityProvider', () => {
|
|
|
246
246
|
it('should save and return the saved identity', async () => {
|
|
247
247
|
const customIdentity = {
|
|
248
248
|
did: 'did:key:zcustom',
|
|
249
|
-
kid: 'did:key:zcustom#
|
|
249
|
+
kid: 'did:key:zcustom#zcustom',
|
|
250
250
|
privateKey: 'custom-private',
|
|
251
251
|
publicKey: 'custom-public',
|
|
252
252
|
createdAt: new Date().toISOString(),
|
|
@@ -251,7 +251,7 @@ export class MockIdentityProvider extends IdentityProvider {
|
|
|
251
251
|
if (!this.identity) {
|
|
252
252
|
this.identity = {
|
|
253
253
|
did: 'did:key:zmock123',
|
|
254
|
-
kid: 'did:key:zmock123#
|
|
254
|
+
kid: 'did:key:zmock123#zmock123',
|
|
255
255
|
privateKey: 'mock-private-key',
|
|
256
256
|
publicKey: 'mock-public-key',
|
|
257
257
|
createdAt: new Date().toISOString(),
|
|
@@ -271,7 +271,7 @@ export class MockIdentityProvider extends IdentityProvider {
|
|
|
271
271
|
this.rotateCount++;
|
|
272
272
|
this.identity = {
|
|
273
273
|
did: `did:key:zmock456-${this.rotateCount}`,
|
|
274
|
-
kid: `did:key:zmock456-${this.rotateCount}#
|
|
274
|
+
kid: `did:key:zmock456-${this.rotateCount}#zmock456-${this.rotateCount}`,
|
|
275
275
|
privateKey: `mock-private-key-rotated-${this.rotateCount}`,
|
|
276
276
|
publicKey: `mock-public-key-rotated-${this.rotateCount}`,
|
|
277
277
|
createdAt: new Date().toISOString(),
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
verifyOrHints,
|
|
4
|
+
MemoryResumeTokenStore,
|
|
5
|
+
type AuthHandshakeConfig,
|
|
6
|
+
} from '../handshake.js';
|
|
7
|
+
|
|
8
|
+
// Mock global fetch
|
|
9
|
+
const mockFetch = vi.fn();
|
|
10
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
11
|
+
|
|
12
|
+
function createConfig(overrides?: {
|
|
13
|
+
minReputationScore?: number;
|
|
14
|
+
unknownAgentPolicy?: 'deny' | 'require-consent' | 'allow';
|
|
15
|
+
reputationApiUrl?: string;
|
|
16
|
+
}): AuthHandshakeConfig {
|
|
17
|
+
return {
|
|
18
|
+
delegationVerifier: {
|
|
19
|
+
verify: vi.fn().mockResolvedValue({ valid: false, reason: 'No delegation' }),
|
|
20
|
+
},
|
|
21
|
+
resumeTokenStore: new MemoryResumeTokenStore(),
|
|
22
|
+
reputationService: {
|
|
23
|
+
apiUrl: overrides?.reputationApiUrl ?? 'https://reputation.example.com',
|
|
24
|
+
},
|
|
25
|
+
authorization: {
|
|
26
|
+
authorizationUrl: 'https://example.com/consent',
|
|
27
|
+
minReputationScore: overrides?.minReputationScore ?? 30,
|
|
28
|
+
unknownAgentPolicy: overrides?.unknownAgentPolicy,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function mockReputationResponse(score: number, status = 200) {
|
|
34
|
+
mockFetch.mockResolvedValueOnce({
|
|
35
|
+
ok: status >= 200 && status < 300,
|
|
36
|
+
status,
|
|
37
|
+
statusText: status === 200 ? 'OK' : 'Error',
|
|
38
|
+
json: async () => ({ score }),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function mockReputationNotFound() {
|
|
43
|
+
mockFetch.mockResolvedValueOnce({
|
|
44
|
+
ok: false,
|
|
45
|
+
status: 404,
|
|
46
|
+
statusText: 'Not Found',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function mockReputationNetworkError() {
|
|
51
|
+
mockFetch.mockRejectedValueOnce(new Error('ECONNREFUSED'));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
describe('verifyOrHints — unknownAgentPolicy', () => {
|
|
55
|
+
const agentDid = 'did:key:z6MkTest';
|
|
56
|
+
const scopes = ['read:data'];
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
vi.clearAllMocks();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('known agents (reputation service returns a score)', () => {
|
|
63
|
+
it('should reject known agent with score below threshold', async () => {
|
|
64
|
+
const config = createConfig({ minReputationScore: 30 });
|
|
65
|
+
mockReputationResponse(10);
|
|
66
|
+
|
|
67
|
+
const result = await verifyOrHints(agentDid, scopes, config);
|
|
68
|
+
|
|
69
|
+
expect(result.authorized).toBe(false);
|
|
70
|
+
expect(result.reason).toBe('Low reputation score');
|
|
71
|
+
expect(result.reputation?.score).toBe(10);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should allow known agent with score above threshold to proceed to delegation', async () => {
|
|
75
|
+
const config = createConfig({ minReputationScore: 30 });
|
|
76
|
+
mockReputationResponse(80);
|
|
77
|
+
|
|
78
|
+
const result = await verifyOrHints(agentDid, scopes, config);
|
|
79
|
+
|
|
80
|
+
// Not authorized (no delegation), but passed reputation gate
|
|
81
|
+
expect(result.authorized).toBe(false);
|
|
82
|
+
expect(result.reason).toBe('No delegation');
|
|
83
|
+
expect(result.reputation?.score).toBe(80);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('unknown agents (404 from reputation service)', () => {
|
|
88
|
+
it('should deny unknown agent when policy is "deny"', async () => {
|
|
89
|
+
const config = createConfig({ unknownAgentPolicy: 'deny' });
|
|
90
|
+
mockReputationNotFound();
|
|
91
|
+
|
|
92
|
+
const result = await verifyOrHints(agentDid, scopes, config);
|
|
93
|
+
|
|
94
|
+
expect(result.authorized).toBe(false);
|
|
95
|
+
expect(result.reason).toBe('Unknown agent — policy: deny');
|
|
96
|
+
expect(result.reputation?.score).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should require consent for unknown agent when policy is "require-consent"', async () => {
|
|
100
|
+
const config = createConfig({ unknownAgentPolicy: 'require-consent' });
|
|
101
|
+
mockReputationNotFound();
|
|
102
|
+
|
|
103
|
+
const result = await verifyOrHints(agentDid, scopes, config);
|
|
104
|
+
|
|
105
|
+
expect(result.authorized).toBe(false);
|
|
106
|
+
expect(result.reason).toBe('Unknown agent — policy: require-consent');
|
|
107
|
+
expect(result.authError).toBeDefined();
|
|
108
|
+
expect(result.reputation?.score).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should default to "require-consent" when no policy is set', async () => {
|
|
112
|
+
const config = createConfig(); // no unknownAgentPolicy
|
|
113
|
+
mockReputationNotFound();
|
|
114
|
+
|
|
115
|
+
const result = await verifyOrHints(agentDid, scopes, config);
|
|
116
|
+
|
|
117
|
+
expect(result.authorized).toBe(false);
|
|
118
|
+
expect(result.reason).toBe('Unknown agent — policy: require-consent');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should allow unknown agent when policy is "allow"', async () => {
|
|
122
|
+
const config = createConfig({ unknownAgentPolicy: 'allow' });
|
|
123
|
+
mockReputationNotFound();
|
|
124
|
+
|
|
125
|
+
const result = await verifyOrHints(agentDid, scopes, config);
|
|
126
|
+
|
|
127
|
+
// Not authorized (no delegation), but passed reputation gate
|
|
128
|
+
expect(result.authorized).toBe(false);
|
|
129
|
+
expect(result.reason).toBe('No delegation');
|
|
130
|
+
expect(result.reputation?.score).toBeNull();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('reputation service unreachable (network error)', () => {
|
|
135
|
+
it('should treat network errors as unknown agent', async () => {
|
|
136
|
+
const config = createConfig({ unknownAgentPolicy: 'deny' });
|
|
137
|
+
mockReputationNetworkError();
|
|
138
|
+
|
|
139
|
+
const result = await verifyOrHints(agentDid, scopes, config);
|
|
140
|
+
|
|
141
|
+
expect(result.authorized).toBe(false);
|
|
142
|
+
expect(result.reason).toBe('Unknown agent — policy: deny');
|
|
143
|
+
expect(result.reputation?.score).toBeNull();
|
|
144
|
+
expect(result.reputation?.riskLevel).toBe('unknown');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should route to consent on network error with default policy', async () => {
|
|
148
|
+
const config = createConfig(); // default = require-consent
|
|
149
|
+
mockReputationNetworkError();
|
|
150
|
+
|
|
151
|
+
const result = await verifyOrHints(agentDid, scopes, config);
|
|
152
|
+
|
|
153
|
+
expect(result.authorized).toBe(false);
|
|
154
|
+
expect(result.reason).toBe('Unknown agent — policy: require-consent');
|
|
155
|
+
expect(result.authError).toBeDefined();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should allow through on network error when policy is "allow"', async () => {
|
|
159
|
+
const config = createConfig({ unknownAgentPolicy: 'allow' });
|
|
160
|
+
mockReputationNetworkError();
|
|
161
|
+
|
|
162
|
+
const result = await verifyOrHints(agentDid, scopes, config);
|
|
163
|
+
|
|
164
|
+
// Passes reputation gate, fails on delegation
|
|
165
|
+
expect(result.authorized).toBe(false);
|
|
166
|
+
expect(result.reason).toBe('No delegation');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('no reputation service configured', () => {
|
|
171
|
+
it('should skip reputation check entirely when no service configured', async () => {
|
|
172
|
+
const config: AuthHandshakeConfig = {
|
|
173
|
+
delegationVerifier: {
|
|
174
|
+
verify: vi.fn().mockResolvedValue({ valid: false, reason: 'No delegation' }),
|
|
175
|
+
},
|
|
176
|
+
resumeTokenStore: new MemoryResumeTokenStore(),
|
|
177
|
+
// No reputationService
|
|
178
|
+
authorization: {
|
|
179
|
+
authorizationUrl: 'https://example.com/consent',
|
|
180
|
+
minReputationScore: 30,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const result = await verifyOrHints(agentDid, scopes, config);
|
|
185
|
+
|
|
186
|
+
expect(result.reputation).toBeUndefined();
|
|
187
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
});
|