@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.
Files changed (94) hide show
  1. package/README.md +123 -333
  2. package/dist/auth/handshake.d.ts +19 -4
  3. package/dist/auth/handshake.d.ts.map +1 -1
  4. package/dist/auth/handshake.js +52 -15
  5. package/dist/auth/handshake.js.map +1 -1
  6. package/dist/auth/index.d.ts +1 -1
  7. package/dist/auth/index.d.ts.map +1 -1
  8. package/dist/auth/index.js.map +1 -1
  9. package/dist/delegation/did-key-resolver.d.ts.map +1 -1
  10. package/dist/delegation/did-key-resolver.js +9 -6
  11. package/dist/delegation/did-key-resolver.js.map +1 -1
  12. package/dist/delegation/outbound-headers.d.ts +2 -4
  13. package/dist/delegation/outbound-headers.d.ts.map +1 -1
  14. package/dist/delegation/outbound-headers.js +2 -3
  15. package/dist/delegation/outbound-headers.js.map +1 -1
  16. package/dist/delegation/statuslist-manager.d.ts.map +1 -1
  17. package/dist/delegation/statuslist-manager.js +1 -1
  18. package/dist/delegation/statuslist-manager.js.map +1 -1
  19. package/dist/delegation/vc-verifier.d.ts.map +1 -1
  20. package/dist/delegation/vc-verifier.js +2 -2
  21. package/dist/delegation/vc-verifier.js.map +1 -1
  22. package/dist/errors.d.ts +42 -0
  23. package/dist/errors.d.ts.map +1 -0
  24. package/dist/errors.js +45 -0
  25. package/dist/errors.js.map +1 -0
  26. package/dist/index.d.ts +3 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +3 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/middleware/index.d.ts +1 -0
  31. package/dist/middleware/index.d.ts.map +1 -1
  32. package/dist/middleware/index.js +1 -0
  33. package/dist/middleware/index.js.map +1 -1
  34. package/dist/middleware/mcpi-transport.d.ts +39 -0
  35. package/dist/middleware/mcpi-transport.d.ts.map +1 -0
  36. package/dist/middleware/mcpi-transport.js +121 -0
  37. package/dist/middleware/mcpi-transport.js.map +1 -0
  38. package/dist/middleware/with-mcpi-server.d.ts +25 -9
  39. package/dist/middleware/with-mcpi-server.d.ts.map +1 -1
  40. package/dist/middleware/with-mcpi-server.js +62 -47
  41. package/dist/middleware/with-mcpi-server.js.map +1 -1
  42. package/dist/middleware/with-mcpi.d.ts +26 -5
  43. package/dist/middleware/with-mcpi.d.ts.map +1 -1
  44. package/dist/middleware/with-mcpi.js +108 -10
  45. package/dist/middleware/with-mcpi.js.map +1 -1
  46. package/dist/providers/memory.js +2 -2
  47. package/dist/providers/memory.js.map +1 -1
  48. package/dist/session/manager.d.ts +7 -1
  49. package/dist/session/manager.d.ts.map +1 -1
  50. package/dist/session/manager.js +20 -4
  51. package/dist/session/manager.js.map +1 -1
  52. package/dist/utils/crypto-service.d.ts.map +1 -1
  53. package/dist/utils/crypto-service.js +11 -10
  54. package/dist/utils/crypto-service.js.map +1 -1
  55. package/dist/utils/did-helpers.d.ts +12 -0
  56. package/dist/utils/did-helpers.d.ts.map +1 -1
  57. package/dist/utils/did-helpers.js +18 -0
  58. package/dist/utils/did-helpers.js.map +1 -1
  59. package/package.json +3 -3
  60. package/src/__tests__/errors.test.ts +56 -0
  61. package/src/__tests__/integration/full-flow.test.ts +1 -1
  62. package/src/__tests__/integration/mcp-enhance-server.test.ts +48 -5
  63. package/src/__tests__/integration/mcp-transport-context7.test.ts +19 -15
  64. package/src/__tests__/integration/mcp-transport.test.ts +13 -10
  65. package/src/__tests__/providers/base.test.ts +1 -1
  66. package/src/__tests__/providers/memory.test.ts +2 -2
  67. package/src/__tests__/utils/mock-providers.ts +2 -2
  68. package/src/auth/__tests__/handshake.test.ts +190 -0
  69. package/src/auth/handshake.ts +88 -21
  70. package/src/auth/index.ts +1 -0
  71. package/src/delegation/__tests__/did-key-resolver.test.ts +2 -2
  72. package/src/delegation/__tests__/outbound-headers.test.ts +16 -20
  73. package/src/delegation/__tests__/statuslist-manager.test.ts +120 -7
  74. package/src/delegation/__tests__/vc-verifier.test.ts +45 -3
  75. package/src/delegation/did-key-resolver.ts +11 -6
  76. package/src/delegation/outbound-headers.ts +1 -4
  77. package/src/delegation/statuslist-manager.ts +3 -1
  78. package/src/delegation/vc-verifier.ts +3 -2
  79. package/src/errors.ts +65 -0
  80. package/src/index.ts +10 -0
  81. package/src/middleware/__tests__/mcpi-transport.test.ts +150 -0
  82. package/src/middleware/__tests__/with-mcpi-server.test.ts +117 -0
  83. package/src/middleware/__tests__/with-mcpi.test.ts +124 -6
  84. package/src/middleware/index.ts +6 -0
  85. package/src/middleware/mcpi-transport.ts +162 -0
  86. package/src/middleware/with-mcpi-server.ts +83 -92
  87. package/src/middleware/with-mcpi.ts +147 -11
  88. package/src/proof/__tests__/errors.test.ts +79 -0
  89. package/src/proof/__tests__/verifier.test.ts +5 -5
  90. package/src/providers/memory.ts +2 -2
  91. package/src/session/__tests__/session-manager.test.ts +3 -3
  92. package/src/session/manager.ts +28 -6
  93. package/src/utils/crypto-service.ts +11 -10
  94. 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-canary.2",
4
- "description": "MCP-I protocol reference implementation — delegation, proof, and session for Model Context Protocol Identity",
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(/#keys-1$/);
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 handshake tool and auto-attaches proofs to all tool responses.
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 _mcpi_handshake tool', async () => {
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('_mcpi_handshake');
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: '_mcpi_handshake',
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}#keys-1`);
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}#keys-1`;
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 _mcpi_handshake tool (same pattern as Context7 integration)
46
+ // Register unified _mcpi tool (same pattern as Context7 integration)
47
47
  server.registerTool(
48
- '_mcpi_handshake',
48
+ '_mcpi',
49
49
  {
50
- description: 'MCP-I identity handshake',
50
+ description: 'MCP-I protocol',
51
51
  inputSchema: {
52
- nonce: z.string(),
53
- audience: z.string(),
54
- timestamp: z.number(),
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.handleHandshake(args as Record<string, unknown>),
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 handshake + registered tools', async () => {
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('_mcpi_handshake');
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: '_mcpi_handshake',
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: '_mcpi_handshake',
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 handshake + registered tools (withMCPI)', async () => {
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('_mcpi_handshake');
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: '_mcpi_handshake',
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}#keys-1`;
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.handshakeTool,
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 === '_mcpi_handshake') {
131
- return mcpi.handleHandshake(args as Record<string, unknown>);
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}#keys-1`;
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 handshake + app tools', async () => {
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('_mcpi_handshake');
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: '_mcpi_handshake',
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: '_mcpi_handshake',
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: '_mcpi_handshake',
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#keys-1',
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}#keys-1`);
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#keys-1',
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#keys-1',
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}#keys-1`,
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
+ });