@kya-os/mcp-i-core 1.3.12 → 1.3.14

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 (254) hide show
  1. package/dist/config/remote-config.js +9 -12
  2. package/dist/runtime/base.js +11 -0
  3. package/dist/services/access-control.service.js +5 -0
  4. package/dist/services/tool-protection.service.js +17 -8
  5. package/package.json +2 -2
  6. package/.turbo/turbo-build.log +0 -4
  7. package/.turbo/turbo-test$colon$coverage.log +0 -4586
  8. package/.turbo/turbo-test.log +0 -3169
  9. package/COMPLIANCE_IMPROVEMENT_REPORT.md +0 -483
  10. package/Composer 3.md +0 -615
  11. package/GPT-5.md +0 -1169
  12. package/OPUS-plan.md +0 -352
  13. package/PHASE_3_AND_4.1_SUMMARY.md +0 -585
  14. package/PHASE_3_SUMMARY.md +0 -317
  15. package/PHASE_4.1.3_SUMMARY.md +0 -428
  16. package/PHASE_4.1_COMPLETE.md +0 -525
  17. package/PHASE_4_USER_DID_IDENTITY_LINKING_PLAN.md +0 -1240
  18. package/SCHEMA_COMPLIANCE_REPORT.md +0 -275
  19. package/TEST_PLAN.md +0 -571
  20. package/coverage/coverage-final.json +0 -60
  21. package/dist/cache/oauth-config-cache.d.ts.map +0 -1
  22. package/dist/cache/oauth-config-cache.js.map +0 -1
  23. package/dist/cache/tool-protection-cache.d.ts.map +0 -1
  24. package/dist/cache/tool-protection-cache.js.map +0 -1
  25. package/dist/compliance/index.d.ts.map +0 -1
  26. package/dist/compliance/index.js.map +0 -1
  27. package/dist/compliance/schema-registry.d.ts.map +0 -1
  28. package/dist/compliance/schema-registry.js.map +0 -1
  29. package/dist/compliance/schema-verifier.d.ts.map +0 -1
  30. package/dist/compliance/schema-verifier.js.map +0 -1
  31. package/dist/config/remote-config.d.ts.map +0 -1
  32. package/dist/config/remote-config.js.map +0 -1
  33. package/dist/config.d.ts.map +0 -1
  34. package/dist/config.js.map +0 -1
  35. package/dist/delegation/audience-validator.d.ts.map +0 -1
  36. package/dist/delegation/audience-validator.js.map +0 -1
  37. package/dist/delegation/bitstring.d.ts.map +0 -1
  38. package/dist/delegation/bitstring.js.map +0 -1
  39. package/dist/delegation/cascading-revocation.d.ts.map +0 -1
  40. package/dist/delegation/cascading-revocation.js.map +0 -1
  41. package/dist/delegation/delegation-graph.d.ts.map +0 -1
  42. package/dist/delegation/delegation-graph.js.map +0 -1
  43. package/dist/delegation/did-key-resolver.d.ts.map +0 -1
  44. package/dist/delegation/did-key-resolver.js.map +0 -1
  45. package/dist/delegation/index.d.ts.map +0 -1
  46. package/dist/delegation/index.js.map +0 -1
  47. package/dist/delegation/statuslist-manager.d.ts.map +0 -1
  48. package/dist/delegation/statuslist-manager.js.map +0 -1
  49. package/dist/delegation/storage/index.d.ts.map +0 -1
  50. package/dist/delegation/storage/index.js.map +0 -1
  51. package/dist/delegation/storage/memory-graph-storage.d.ts.map +0 -1
  52. package/dist/delegation/storage/memory-graph-storage.js.map +0 -1
  53. package/dist/delegation/storage/memory-statuslist-storage.d.ts.map +0 -1
  54. package/dist/delegation/storage/memory-statuslist-storage.js.map +0 -1
  55. package/dist/delegation/utils.d.ts.map +0 -1
  56. package/dist/delegation/utils.js.map +0 -1
  57. package/dist/delegation/vc-issuer.d.ts.map +0 -1
  58. package/dist/delegation/vc-issuer.js.map +0 -1
  59. package/dist/delegation/vc-verifier.d.ts.map +0 -1
  60. package/dist/delegation/vc-verifier.js.map +0 -1
  61. package/dist/identity/idp-token-resolver.d.ts.map +0 -1
  62. package/dist/identity/idp-token-resolver.js.map +0 -1
  63. package/dist/identity/idp-token-storage.interface.d.ts.map +0 -1
  64. package/dist/identity/idp-token-storage.interface.js.map +0 -1
  65. package/dist/identity/user-did-manager.d.ts.map +0 -1
  66. package/dist/identity/user-did-manager.js.map +0 -1
  67. package/dist/index.d.ts.map +0 -1
  68. package/dist/index.js.map +0 -1
  69. package/dist/providers/base.d.ts.map +0 -1
  70. package/dist/providers/base.js.map +0 -1
  71. package/dist/providers/memory.d.ts.map +0 -1
  72. package/dist/providers/memory.js.map +0 -1
  73. package/dist/runtime/audit-logger.d.ts.map +0 -1
  74. package/dist/runtime/audit-logger.js.map +0 -1
  75. package/dist/runtime/base.d.ts.map +0 -1
  76. package/dist/runtime/base.js.map +0 -1
  77. package/dist/services/access-control.service.d.ts.map +0 -1
  78. package/dist/services/access-control.service.js.map +0 -1
  79. package/dist/services/authorization/authorization-registry.d.ts.map +0 -1
  80. package/dist/services/authorization/authorization-registry.js.map +0 -1
  81. package/dist/services/authorization/types.d.ts.map +0 -1
  82. package/dist/services/authorization/types.js.map +0 -1
  83. package/dist/services/batch-delegation.service.d.ts.map +0 -1
  84. package/dist/services/batch-delegation.service.js.map +0 -1
  85. package/dist/services/crypto.service.d.ts.map +0 -1
  86. package/dist/services/crypto.service.js.map +0 -1
  87. package/dist/services/errors.d.ts.map +0 -1
  88. package/dist/services/errors.js.map +0 -1
  89. package/dist/services/index.d.ts.map +0 -1
  90. package/dist/services/index.js.map +0 -1
  91. package/dist/services/oauth-config.service.d.ts.map +0 -1
  92. package/dist/services/oauth-config.service.js.map +0 -1
  93. package/dist/services/oauth-provider-registry.d.ts.map +0 -1
  94. package/dist/services/oauth-provider-registry.js.map +0 -1
  95. package/dist/services/oauth-service.d.ts.map +0 -1
  96. package/dist/services/oauth-service.js.map +0 -1
  97. package/dist/services/oauth-token-retrieval.service.d.ts.map +0 -1
  98. package/dist/services/oauth-token-retrieval.service.js.map +0 -1
  99. package/dist/services/proof-verifier.d.ts.map +0 -1
  100. package/dist/services/proof-verifier.js.map +0 -1
  101. package/dist/services/provider-resolver.d.ts.map +0 -1
  102. package/dist/services/provider-resolver.js.map +0 -1
  103. package/dist/services/provider-validator.d.ts.map +0 -1
  104. package/dist/services/provider-validator.js.map +0 -1
  105. package/dist/services/session-registration.service.d.ts.map +0 -1
  106. package/dist/services/session-registration.service.js.map +0 -1
  107. package/dist/services/storage.service.d.ts.map +0 -1
  108. package/dist/services/storage.service.js.map +0 -1
  109. package/dist/services/tool-context-builder.d.ts.map +0 -1
  110. package/dist/services/tool-context-builder.js.map +0 -1
  111. package/dist/services/tool-protection.service.d.ts.map +0 -1
  112. package/dist/services/tool-protection.service.js.map +0 -1
  113. package/dist/types/oauth-required-error.d.ts.map +0 -1
  114. package/dist/types/oauth-required-error.js.map +0 -1
  115. package/dist/types/tool-protection.d.ts.map +0 -1
  116. package/dist/types/tool-protection.js.map +0 -1
  117. package/dist/utils/base58.d.ts.map +0 -1
  118. package/dist/utils/base58.js.map +0 -1
  119. package/dist/utils/base64.d.ts.map +0 -1
  120. package/dist/utils/base64.js.map +0 -1
  121. package/dist/utils/cors.d.ts.map +0 -1
  122. package/dist/utils/cors.js.map +0 -1
  123. package/dist/utils/did-helpers.d.ts.map +0 -1
  124. package/dist/utils/did-helpers.js.map +0 -1
  125. package/dist/utils/index.d.ts.map +0 -1
  126. package/dist/utils/index.js.map +0 -1
  127. package/dist/utils/storage-keys.d.ts.map +0 -1
  128. package/dist/utils/storage-keys.js.map +0 -1
  129. package/docs/API_REFERENCE.md +0 -1362
  130. package/docs/COMPLIANCE_MATRIX.md +0 -691
  131. package/docs/STATUSLIST2021_GUIDE.md +0 -696
  132. package/docs/W3C_VC_DELEGATION_GUIDE.md +0 -710
  133. package/src/__tests__/cache/tool-protection-cache.test.ts +0 -640
  134. package/src/__tests__/config/provider-runtime-config.test.ts +0 -309
  135. package/src/__tests__/delegation-e2e.test.ts +0 -690
  136. package/src/__tests__/identity/user-did-manager.test.ts +0 -232
  137. package/src/__tests__/index.test.ts +0 -56
  138. package/src/__tests__/integration/full-flow.test.ts +0 -789
  139. package/src/__tests__/integration.test.ts +0 -281
  140. package/src/__tests__/providers/base.test.ts +0 -173
  141. package/src/__tests__/providers/memory.test.ts +0 -319
  142. package/src/__tests__/regression/phase2-regression.test.ts +0 -429
  143. package/src/__tests__/runtime/audit-logger.test.ts +0 -154
  144. package/src/__tests__/runtime/base-extensions.test.ts +0 -595
  145. package/src/__tests__/runtime/base.test.ts +0 -869
  146. package/src/__tests__/runtime/delegation-flow.test.ts +0 -164
  147. package/src/__tests__/runtime/proof-client-did.test.ts +0 -376
  148. package/src/__tests__/runtime/route-interception.test.ts +0 -686
  149. package/src/__tests__/runtime/tool-protection-enforcement.test.ts +0 -908
  150. package/src/__tests__/services/agentshield-integration.test.ts +0 -791
  151. package/src/__tests__/services/cache-busting.test.ts +0 -125
  152. package/src/__tests__/services/oauth-service-pkce.test.ts +0 -556
  153. package/src/__tests__/services/provider-resolver-edge-cases.test.ts +0 -591
  154. package/src/__tests__/services/tool-protection-merged-config.test.ts +0 -485
  155. package/src/__tests__/services/tool-protection-oauth-provider.test.ts +0 -480
  156. package/src/__tests__/services/tool-protection.service.test.ts +0 -1373
  157. package/src/__tests__/utils/mock-providers.ts +0 -340
  158. package/src/cache/oauth-config-cache.d.ts +0 -69
  159. package/src/cache/oauth-config-cache.d.ts.map +0 -1
  160. package/src/cache/oauth-config-cache.js.map +0 -1
  161. package/src/cache/oauth-config-cache.ts +0 -123
  162. package/src/cache/tool-protection-cache.ts +0 -171
  163. package/src/compliance/EXAMPLE.md +0 -412
  164. package/src/compliance/__tests__/schema-verifier.test.ts +0 -797
  165. package/src/compliance/index.ts +0 -8
  166. package/src/compliance/schema-registry.ts +0 -460
  167. package/src/compliance/schema-verifier.ts +0 -708
  168. package/src/config/__tests__/merged-config.spec.ts +0 -445
  169. package/src/config/__tests__/remote-config.spec.ts +0 -268
  170. package/src/config/remote-config.ts +0 -264
  171. package/src/config.ts +0 -312
  172. package/src/delegation/__tests__/audience-validator.test.ts +0 -112
  173. package/src/delegation/__tests__/bitstring.test.ts +0 -346
  174. package/src/delegation/__tests__/cascading-revocation.test.ts +0 -628
  175. package/src/delegation/__tests__/delegation-graph.test.ts +0 -584
  176. package/src/delegation/__tests__/did-key-resolver.test.ts +0 -265
  177. package/src/delegation/__tests__/utils.test.ts +0 -152
  178. package/src/delegation/__tests__/vc-issuer.test.ts +0 -442
  179. package/src/delegation/__tests__/vc-verifier.test.ts +0 -922
  180. package/src/delegation/audience-validator.ts +0 -52
  181. package/src/delegation/bitstring.ts +0 -278
  182. package/src/delegation/cascading-revocation.ts +0 -370
  183. package/src/delegation/delegation-graph.ts +0 -299
  184. package/src/delegation/did-key-resolver.ts +0 -179
  185. package/src/delegation/index.ts +0 -14
  186. package/src/delegation/statuslist-manager.ts +0 -353
  187. package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +0 -366
  188. package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +0 -228
  189. package/src/delegation/storage/index.ts +0 -9
  190. package/src/delegation/storage/memory-graph-storage.ts +0 -178
  191. package/src/delegation/storage/memory-statuslist-storage.ts +0 -77
  192. package/src/delegation/utils.ts +0 -221
  193. package/src/delegation/vc-issuer.ts +0 -232
  194. package/src/delegation/vc-verifier.ts +0 -568
  195. package/src/identity/idp-token-resolver.ts +0 -181
  196. package/src/identity/idp-token-storage.interface.ts +0 -94
  197. package/src/identity/user-did-manager.ts +0 -526
  198. package/src/index.ts +0 -310
  199. package/src/providers/base.d.ts +0 -91
  200. package/src/providers/base.d.ts.map +0 -1
  201. package/src/providers/base.js.map +0 -1
  202. package/src/providers/base.ts +0 -96
  203. package/src/providers/memory.ts +0 -142
  204. package/src/runtime/audit-logger.ts +0 -39
  205. package/src/runtime/base.ts +0 -1392
  206. package/src/services/__tests__/access-control.integration.test.ts +0 -443
  207. package/src/services/__tests__/access-control.proof-response-validation.test.ts +0 -578
  208. package/src/services/__tests__/access-control.service.test.ts +0 -970
  209. package/src/services/__tests__/batch-delegation.service.test.ts +0 -351
  210. package/src/services/__tests__/crypto.service.test.ts +0 -531
  211. package/src/services/__tests__/oauth-provider-registry.test.ts +0 -142
  212. package/src/services/__tests__/proof-verifier.integration.test.ts +0 -485
  213. package/src/services/__tests__/proof-verifier.test.ts +0 -489
  214. package/src/services/__tests__/provider-resolution.integration.test.ts +0 -202
  215. package/src/services/__tests__/provider-resolver.test.ts +0 -213
  216. package/src/services/__tests__/storage.service.test.ts +0 -358
  217. package/src/services/access-control.service.ts +0 -990
  218. package/src/services/authorization/authorization-registry.ts +0 -66
  219. package/src/services/authorization/types.ts +0 -71
  220. package/src/services/batch-delegation.service.ts +0 -137
  221. package/src/services/crypto.service.ts +0 -302
  222. package/src/services/errors.ts +0 -76
  223. package/src/services/index.ts +0 -18
  224. package/src/services/oauth-config.service.d.ts +0 -53
  225. package/src/services/oauth-config.service.d.ts.map +0 -1
  226. package/src/services/oauth-config.service.js.map +0 -1
  227. package/src/services/oauth-config.service.ts +0 -192
  228. package/src/services/oauth-provider-registry.d.ts +0 -57
  229. package/src/services/oauth-provider-registry.d.ts.map +0 -1
  230. package/src/services/oauth-provider-registry.js.map +0 -1
  231. package/src/services/oauth-provider-registry.ts +0 -141
  232. package/src/services/oauth-service.ts +0 -544
  233. package/src/services/oauth-token-retrieval.service.ts +0 -245
  234. package/src/services/proof-verifier.ts +0 -478
  235. package/src/services/provider-resolver.d.ts +0 -48
  236. package/src/services/provider-resolver.d.ts.map +0 -1
  237. package/src/services/provider-resolver.js.map +0 -1
  238. package/src/services/provider-resolver.ts +0 -146
  239. package/src/services/provider-validator.ts +0 -170
  240. package/src/services/session-registration.service.ts +0 -251
  241. package/src/services/storage.service.ts +0 -566
  242. package/src/services/tool-context-builder.ts +0 -237
  243. package/src/services/tool-protection.service.ts +0 -1070
  244. package/src/types/oauth-required-error.ts +0 -63
  245. package/src/types/tool-protection.ts +0 -155
  246. package/src/utils/__tests__/did-helpers.test.ts +0 -156
  247. package/src/utils/base58.ts +0 -109
  248. package/src/utils/base64.ts +0 -148
  249. package/src/utils/cors.ts +0 -83
  250. package/src/utils/did-helpers.ts +0 -210
  251. package/src/utils/index.ts +0 -8
  252. package/src/utils/storage-keys.ts +0 -278
  253. package/tsconfig.json +0 -21
  254. package/vitest.config.ts +0 -56
@@ -1,1392 +0,0 @@
1
- /**
2
- * MCPIRuntimeBase - Provider-based runtime
3
- *
4
- * Core runtime that accepts providers for all platform-specific operations.
5
- * This enables the same runtime logic to work across Node.js, Cloudflare Workers,
6
- * and other platforms.
7
- */
8
-
9
- import {
10
- CryptoProvider,
11
- ClockProvider,
12
- FetchProvider,
13
- StorageProvider,
14
- NonceCacheProvider,
15
- IdentityProvider,
16
- AgentIdentity,
17
- } from "../providers/base";
18
- import { DelegationRequiredError } from "../types/tool-protection.js";
19
- import { CryptoService, type Ed25519JWK } from "../services/crypto.service.js";
20
- import { ProofVerifier } from "../services/proof-verifier.js";
21
- import type { DetachedProof } from "@kya-os/contracts/proof";
22
- import type {
23
- DIDDocument,
24
- AgentDocument,
25
- MCPIdentity,
26
- WellKnownConfig,
27
- WellKnownResponse,
28
- } from "@kya-os/contracts/well-known";
29
- import type { AccessControlApiService } from "../services/access-control.service.js";
30
- import type { VerifyDelegationRequest } from "@kya-os/contracts/agentshield-api";
31
- import { AgentShieldAPIError } from "@kya-os/contracts/agentshield-api";
32
-
33
- // Import the new provider runtime config
34
- import type { ProviderRuntimeConfig } from "../config";
35
- import { UserDidManager } from "../identity/user-did-manager";
36
- import type { InterceptedToolCall } from "../types/tool-protection.js";
37
-
38
- /**
39
- * Interface for runtime instances that have AccessControlApiService available
40
- * This allows type-safe access to the access control service without using `as any`
41
- *
42
- * @deprecated AccessControlApiService is now directly available as protected property on MCPIRuntimeBase
43
- */
44
- export interface RuntimeWithAccessControl {
45
- accessControlService?: AccessControlApiService;
46
- }
47
-
48
- export class MCPIRuntimeBase {
49
- protected crypto: CryptoProvider;
50
- protected clock: ClockProvider;
51
- protected fetch: FetchProvider;
52
- protected storage: StorageProvider;
53
- protected nonceCache: NonceCacheProvider;
54
- protected identity: IdentityProvider;
55
- protected config: ProviderRuntimeConfig;
56
- private cachedIdentity?: AgentIdentity;
57
- private sessions: Map<string, any> = new Map();
58
- private lastProof?: any;
59
- private userDidManager?: UserDidManager;
60
- private interceptedCalls: Map<string, any> = new Map(); // Store intercepted tool calls by resume token
61
- private cryptoService?: CryptoService;
62
- protected proofVerifier?: ProofVerifier; // Optional ProofVerifier (injected by subclasses)
63
- protected accessControlService?: AccessControlApiService; // Optional AccessControlApiService (injected by subclasses)
64
-
65
- constructor(config: ProviderRuntimeConfig) {
66
- this.config = config;
67
- this.crypto = config.cryptoProvider;
68
- this.clock = config.clockProvider;
69
- this.fetch = config.fetchProvider;
70
- this.storage = config.storageProvider;
71
- this.nonceCache = config.nonceCacheProvider;
72
- this.identity = config.identityProvider;
73
- // Initialize CryptoService for JWS verification
74
- this.cryptoService = new CryptoService(this.crypto);
75
- }
76
-
77
- /**
78
- * Initialize the runtime
79
- */
80
- async initialize(): Promise<void> {
81
- // Load or generate identity
82
- this.cachedIdentity = await this.identity.getIdentity();
83
-
84
- // Initialize user DID manager if identity config enables it
85
- if (this.config.identity?.generateUserDids) {
86
- this.userDidManager = new UserDidManager({
87
- crypto: this.crypto,
88
- storage: this.config.identity?.userDidStorage
89
- ? {
90
- get: async (key: string) =>
91
- await this.storage.get(`userDid:${key}`),
92
- set: async (key: string, value: string, ttl?: number) => {
93
- await this.storage.set(`userDid:${key}`, value);
94
- },
95
- delete: async (key: string) =>
96
- await this.storage.delete(`userDid:${key}`),
97
- }
98
- : undefined,
99
- useDidWeb: this.config.identity?.userDidStorage === "persistent",
100
- });
101
- }
102
-
103
- // Initialize nonce cache if it has an initialize method
104
- if (
105
- "initialize" in this.nonceCache &&
106
- typeof (this.nonceCache as any).initialize === "function"
107
- ) {
108
- await (this.nonceCache as any).initialize();
109
- }
110
-
111
- // Log initialization if audit is enabled
112
- if (this.config.audit?.enabled) {
113
- this.logAudit("runtime_initialized", {
114
- did: this.cachedIdentity.did,
115
- environment: this.config.environment || "development",
116
- userDidGeneration: this.config.identity?.generateUserDids
117
- ? "enabled"
118
- : "disabled",
119
- });
120
- }
121
- }
122
-
123
- /**
124
- * Get the current agent identity
125
- */
126
- async getIdentity(): Promise<AgentIdentity> {
127
- if (!this.cachedIdentity) {
128
- this.cachedIdentity = await this.identity.getIdentity();
129
- }
130
- return this.cachedIdentity;
131
- }
132
-
133
- /**
134
- * Handle MCP handshake request
135
- *
136
- * Phase 5: Anonymous Sessions Until OAuth
137
- * - Sessions start anonymous (no userDid) unless OAuth identity provided
138
- * - User DID is resolved via AgentShield after OAuth completes
139
- * - Eliminates DID fragmentation (same user = same DID across sessions)
140
- *
141
- * @param request - Handshake request object (may include oauthIdentity for persistent user DID lookup)
142
- * @returns Handshake response with session ID and agent DID
143
- */
144
- async handleHandshake(
145
- request: any & {
146
- oauthIdentity?:
147
- | import("../identity/user-did-manager").OAuthIdentity
148
- | null;
149
- }
150
- ): Promise<any> {
151
- const identity = await this.getIdentity();
152
- const timestamp = this.clock.now();
153
- const sessionId = await this.generateSessionId();
154
-
155
- // Phase 5: Try to resolve user DID from existing OAuth mapping
156
- // Sessions start anonymous - no ephemeral generation
157
- let userDid: string | undefined;
158
- if (this.userDidManager) {
159
- try {
160
- const oauthIdentity = request.oauthIdentity;
161
- const resolvedDid = await this.userDidManager.getOrCreateUserDid(
162
- sessionId,
163
- oauthIdentity
164
- );
165
- // Convert null to undefined for session storage
166
- userDid = resolvedDid ?? undefined;
167
-
168
- if (this.config.audit?.enabled) {
169
- if (userDid) {
170
- console.log("[MCP-I] Resolved existing user DID for session:", {
171
- userDid: userDid.substring(0, 20) + "...",
172
- hasOAuth: !!oauthIdentity,
173
- provider: oauthIdentity?.provider,
174
- });
175
- } else {
176
- console.log("[MCP-I] Session started anonymous (no userDid):", {
177
- sessionId: sessionId.substring(0, 8) + "...",
178
- hasOAuth: !!oauthIdentity,
179
- });
180
- }
181
- }
182
- } catch (error) {
183
- console.warn("[MCP-I] Failed to resolve user DID:", error);
184
- // Continue without user DID - session is anonymous
185
- }
186
- }
187
-
188
- // Extract client info if available
189
- const normalizeString = (value: unknown): string | undefined => {
190
- if (typeof value !== "string") {
191
- return undefined;
192
- }
193
- const trimmed = value.trim();
194
- return trimmed.length > 0 ? trimmed : undefined;
195
- };
196
-
197
- const protocolVersion = normalizeString(request.clientProtocolVersion);
198
- const clientCapabilities = request.clientCapabilities;
199
-
200
- const requestClientInfo = request.clientInfo;
201
- const shouldPersistClientInfo =
202
- requestClientInfo ||
203
- typeof protocolVersion === "string" ||
204
- typeof clientCapabilities !== "undefined";
205
-
206
- const clientInfo = shouldPersistClientInfo
207
- ? {
208
- name: normalizeString(requestClientInfo?.name) ?? "unknown",
209
- title: normalizeString(requestClientInfo?.title),
210
- version: normalizeString(requestClientInfo?.version),
211
- platform: normalizeString(requestClientInfo?.platform),
212
- vendor: normalizeString(requestClientInfo?.vendor),
213
- persistentId: normalizeString(requestClientInfo?.persistentId),
214
- clientId:
215
- normalizeString(requestClientInfo?.clientId) ?? crypto.randomUUID(),
216
- protocolVersion,
217
- capabilities: clientCapabilities,
218
- }
219
- : undefined;
220
-
221
- // Create session with Phase 5 identity state
222
- const session = {
223
- id: sessionId,
224
- clientDid: request.clientDid || userDid, // Use provided clientDid or generated userDid
225
- userDid: userDid, // Store user DID (may be undefined for anonymous sessions)
226
- agentDid: request.agentDid, // ✅ FIXED: Only agent DID, no fallback
227
- serverDid: identity.did, // ✅ NEW: Server's DID (for clarity)
228
- audience: request.audience,
229
- createdAt: timestamp,
230
- expiresAt: this.clock.calculateExpiry(
231
- (this.config.session?.ttlMinutes || 30) * 60
232
- ),
233
- clientInfo, // Store client information
234
- // Phase 5: Identity state tracking
235
- identityState: userDid ? "authenticated" : "anonymous",
236
- oauthIdentity: request.oauthIdentity ?? undefined,
237
- };
238
-
239
- this.sessions.set(sessionId, session);
240
-
241
- // Create handshake response
242
- const response = {
243
- sessionId,
244
- agentDid: identity.did,
245
- timestamp,
246
- capabilities: ["identity", "proof", "audit"],
247
- ...(userDid && { userDid }), // Include user DID in response if generated
248
- };
249
-
250
- // Sign the response
251
- const signature = await this.signData(response);
252
-
253
- return {
254
- ...response,
255
- signature,
256
- };
257
- }
258
-
259
- /**
260
- * Update session identity after OAuth resolution (Phase 5)
261
- *
262
- * Called after AgentShield identity/resolve returns a persistent user DID.
263
- * Updates the session to authenticated state with the resolved DID.
264
- *
265
- * @param sessionId - MCP session ID
266
- * @param userDid - Persistent user DID from AgentShield
267
- * @param oauthIdentity - OAuth identity information
268
- * @throws Error if session not found
269
- */
270
- async updateSessionIdentity(
271
- sessionId: string,
272
- userDid: string,
273
- oauthIdentity?: { provider: string; subject: string; email?: string }
274
- ): Promise<void> {
275
- const session = this.sessions.get(sessionId);
276
- if (!session) {
277
- throw new Error(`Session not found: ${sessionId}`);
278
- }
279
-
280
- // Update session with resolved identity
281
- session.userDid = userDid;
282
- session.identityState = "authenticated";
283
- if (oauthIdentity) {
284
- session.oauthIdentity = oauthIdentity;
285
- }
286
-
287
- // Update the sessions map
288
- this.sessions.set(sessionId, session);
289
-
290
- // Also update UserDidManager cache if available
291
- if (this.userDidManager) {
292
- await this.userDidManager.setUserDidForSession(sessionId, userDid, oauthIdentity);
293
- }
294
-
295
- if (this.config.audit?.enabled) {
296
- console.log("[MCP-I] Session identity updated (Phase 5):", {
297
- sessionId: sessionId.substring(0, 8) + "...",
298
- userDid: userDid.substring(0, 20) + "...",
299
- provider: oauthIdentity?.provider,
300
- identityState: "authenticated",
301
- });
302
- }
303
- }
304
-
305
- /**
306
- * Get session by ID
307
- */
308
- getSession(sessionId: string): any | undefined {
309
- return this.sessions.get(sessionId);
310
- }
311
-
312
- /**
313
- * Process tool call with automatic proof generation
314
- * Returns clean result only - proof is stored for out-of-band retrieval
315
- *
316
- * @param toolName - Name of the tool being called
317
- * @param args - Tool arguments
318
- * @param handler - Tool execution handler
319
- * @param session - Session context (expected fields: id, audience, nonce?, delegationToken?, consentProof?)
320
- */
321
- async processToolCall(
322
- toolName: string,
323
- args: any,
324
- handler: (args: any) => Promise<any>,
325
- session?: any
326
- ): Promise<any> {
327
- // Check tool protection (delegation requirement)
328
- if (this.config.toolProtectionService) {
329
- // Get agent identity to check protection
330
- const identity = await this.getIdentity();
331
-
332
- if (this.config.audit?.enabled) {
333
- console.log("[MCP-I] Checking tool protection:", {
334
- tool: toolName,
335
- agentDid: identity.did.slice(0, 20) + "...",
336
- hasDelegation: !!(session?.delegationToken || session?.consentProof),
337
- });
338
- }
339
-
340
- const protection =
341
- await this.config.toolProtectionService.checkToolProtection(
342
- toolName,
343
- identity.did
344
- );
345
-
346
- if (protection) {
347
- // Tool requires delegation
348
- const hasDelegation = session?.delegationToken || session?.consentProof;
349
-
350
- if (!hasDelegation) {
351
- // No delegation provided - intercept the tool call and store context
352
- // Store intercepted tool call context for resumption
353
- const interceptedCall: InterceptedToolCall = {
354
- toolName,
355
- args,
356
- sessionId: session?.id || "unknown",
357
- timestamp: this.clock.now(),
358
- expiresAt: this.clock.calculateExpiry(1800), // 30 minutes
359
- };
360
-
361
- // Generate resume token
362
- const resumeToken = this.generateResumeToken(interceptedCall);
363
-
364
- // Build consent URL with resume token
365
- // Note: projectId is not available in base class - subclasses should override buildConsentUrl
366
- const consentUrl = this.buildConsentUrl(
367
- toolName,
368
- protection.requiredScopes,
369
- session,
370
- resumeToken
371
- );
372
-
373
- // Create error with intercepted call context and pre-generated resume token
374
- const error = new DelegationRequiredError(
375
- toolName,
376
- protection.requiredScopes,
377
- consentUrl,
378
- interceptedCall,
379
- resumeToken
380
- );
381
-
382
- // Store intercepted call for resumption
383
- this.interceptedCalls.set(resumeToken, interceptedCall);
384
-
385
- // Clean up expired intercepted calls periodically
386
- this.cleanupExpiredInterceptedCalls();
387
-
388
- if (this.config.audit?.enabled) {
389
- console.warn(
390
- "[MCP-I] BLOCKED: Tool requires delegation but none provided",
391
- {
392
- tool: toolName,
393
- requiredScopes: protection.requiredScopes,
394
- agentDid: identity.did.slice(0, 20) + "...",
395
- resumeToken: error.resumeToken,
396
- consentUrl,
397
- }
398
- );
399
- }
400
-
401
- throw error;
402
- }
403
-
404
- // Delegation provided - verify it with AccessControlApiService
405
- const delegationToken = session?.delegationToken;
406
- const consentProof = session?.consentProof;
407
-
408
- if (!this.accessControlService) {
409
- // Access control service not available - log warning but allow execution
410
- // This enables graceful degradation when service is not configured
411
- if (this.config.audit?.enabled) {
412
- console.warn(
413
- "[MCP-I] ⚠️ Delegation token provided but AccessControlApiService not configured - skipping verification",
414
- {
415
- tool: toolName,
416
- agentDid: identity.did.slice(0, 20) + "...",
417
- hasDelegationToken: !!delegationToken,
418
- hasConsentProof: !!consentProof,
419
- }
420
- );
421
- }
422
- } else {
423
- // Verify delegation token with AccessControlApiService
424
- try {
425
- if (this.config.audit?.enabled) {
426
- console.log(
427
- "[MCP-I] 🔐 Verifying delegation token with AccessControlApiService",
428
- {
429
- tool: toolName,
430
- agentDid: identity.did.slice(0, 20) + "...",
431
- hasDelegationToken: !!delegationToken,
432
- hasConsentProof: !!consentProof,
433
- requiredScopes: protection.requiredScopes,
434
- }
435
- );
436
- }
437
-
438
- // Build verification request
439
- const verifyRequest: VerifyDelegationRequest = {
440
- agent_did: identity.did,
441
- scopes: protection.requiredScopes,
442
- };
443
-
444
- // Add delegation token if available (preferred over consent proof)
445
- if (delegationToken) {
446
- verifyRequest.delegation_token = delegationToken;
447
- } else if (consentProof) {
448
- // Consent proof is a JWT credential - use as credential_jwt
449
- verifyRequest.credential_jwt = consentProof;
450
- }
451
-
452
- // Add optional timestamp for verification
453
- verifyRequest.timestamp = this.clock.now();
454
-
455
- // Add client info from session if available
456
- if (session?.clientDid || session?.clientId) {
457
- verifyRequest.client_info = {
458
- origin: session?.serverOrigin,
459
- user_agent: session?.userAgent,
460
- };
461
- }
462
-
463
- // Perform verification
464
- const verificationResult =
465
- await this.accessControlService.verifyDelegation(verifyRequest, {
466
- delegationToken: delegationToken || undefined,
467
- credentialJwt: consentProof || undefined,
468
- });
469
-
470
- // Check verification result
471
- if (!verificationResult.data.valid) {
472
- // Delegation verification failed
473
- const reason =
474
- verificationResult.data.reason ||
475
- "Delegation token invalid or expired";
476
- const errorDetails = verificationResult.data.error;
477
-
478
- if (this.config.audit?.enabled) {
479
- console.error("[MCP-I] ❌ Delegation verification FAILED", {
480
- tool: toolName,
481
- agentDid: identity.did.slice(0, 20) + "...",
482
- reason,
483
- errorCode: errorDetails?.code,
484
- errorMessage: errorDetails?.message,
485
- requiredScopes: protection.requiredScopes,
486
- });
487
- }
488
-
489
- // Throw DelegationRequiredError to trigger consent flow
490
- const interceptedCall: InterceptedToolCall = {
491
- toolName,
492
- args,
493
- sessionId: session?.id || "unknown",
494
- timestamp: this.clock.now(),
495
- expiresAt: this.clock.calculateExpiry(1800), // 30 minutes
496
- };
497
-
498
- const resumeToken = this.generateResumeToken(interceptedCall);
499
- const consentUrl = this.buildConsentUrl(
500
- toolName,
501
- protection.requiredScopes,
502
- session,
503
- resumeToken
504
- );
505
-
506
- this.interceptedCalls.set(resumeToken, interceptedCall);
507
- this.cleanupExpiredInterceptedCalls();
508
-
509
- throw new DelegationRequiredError(
510
- toolName,
511
- protection.requiredScopes,
512
- consentUrl,
513
- interceptedCall,
514
- resumeToken
515
- );
516
- }
517
-
518
- // ✅ SECURITY: Validate user_identifier matches session userDid
519
- // This ensures delegations are user-specific and prevents user isolation bypass
520
- const credential = verificationResult.data.credential;
521
- const delegationUserIdentifier = credential?.user_identifier;
522
- const sessionUserDid = session?.userDid;
523
-
524
- if (delegationUserIdentifier && sessionUserDid) {
525
- if (delegationUserIdentifier !== sessionUserDid) {
526
- // User identifier mismatch - potential security issue
527
- const securityError = `Delegation user_identifier mismatch: delegation has "${delegationUserIdentifier.substring(0, 20)}..." but session has "${sessionUserDid.substring(0, 20)}..."`;
528
-
529
- if (this.config.audit?.enabled) {
530
- console.error(
531
- "[MCP-I] 🔒 SECURITY: User identifier validation FAILED",
532
- {
533
- tool: toolName,
534
- agentDid: identity.did.slice(0, 20) + "...",
535
- delegationUserIdentifier:
536
- delegationUserIdentifier.substring(0, 20) + "...",
537
- sessionUserDid: sessionUserDid.substring(0, 20) + "...",
538
- sessionId: session?.id?.substring(0, 20) + "...",
539
- reason: "user_identifier_mismatch",
540
- severity: "high",
541
- }
542
- );
543
- }
544
-
545
- // Throw DelegationRequiredError to force re-authentication
546
- const interceptedCall: InterceptedToolCall = {
547
- toolName,
548
- args,
549
- sessionId: session?.id || "unknown",
550
- timestamp: this.clock.now(),
551
- expiresAt: this.clock.calculateExpiry(1800), // 30 minutes
552
- };
553
-
554
- const resumeToken = this.generateResumeToken(interceptedCall);
555
- const consentUrl = this.buildConsentUrl(
556
- toolName,
557
- protection.requiredScopes,
558
- session,
559
- resumeToken
560
- );
561
-
562
- this.interceptedCalls.set(resumeToken, interceptedCall);
563
- this.cleanupExpiredInterceptedCalls();
564
-
565
- throw new DelegationRequiredError(
566
- toolName,
567
- protection.requiredScopes,
568
- consentUrl,
569
- interceptedCall,
570
- resumeToken
571
- );
572
- }
573
-
574
- // User identifier matches - log success for audit
575
- if (this.config.audit?.enabled) {
576
- console.log("[MCP-I] ✅ User identifier validation PASSED", {
577
- tool: toolName,
578
- agentDid: identity.did.slice(0, 20) + "...",
579
- userDid: sessionUserDid.substring(0, 20) + "...",
580
- sessionId: session?.id?.substring(0, 20) + "...",
581
- });
582
- }
583
- } else if (delegationUserIdentifier && !sessionUserDid) {
584
- // Delegation has user_identifier but session doesn't - log warning
585
- if (this.config.audit?.enabled) {
586
- console.warn(
587
- "[MCP-I] ⚠️ Delegation has user_identifier but session missing userDid",
588
- {
589
- tool: toolName,
590
- agentDid: identity.did.slice(0, 20) + "...",
591
- delegationUserIdentifier:
592
- delegationUserIdentifier.substring(0, 20) + "...",
593
- sessionId: session?.id?.substring(0, 20) + "...",
594
- }
595
- );
596
- }
597
- }
598
-
599
- // Verification succeeded
600
- if (this.config.audit?.enabled) {
601
- console.log("[MCP-I] ✅ Delegation verification SUCCEEDED", {
602
- tool: toolName,
603
- agentDid: identity.did.slice(0, 20) + "...",
604
- delegationId: verificationResult.data.delegation_id,
605
- credentialScopes: verificationResult.data.credential?.scopes,
606
- requiredScopes: protection.requiredScopes,
607
- });
608
- }
609
- } catch (error: any) {
610
- // Handle verification errors
611
- if (error instanceof DelegationRequiredError) {
612
- // Re-throw DelegationRequiredError as-is (already handled above)
613
- throw error;
614
- }
615
-
616
- // Handle AgentShieldAPIError (network errors, API errors, etc.)
617
- if (error instanceof AgentShieldAPIError) {
618
- if (this.config.audit?.enabled) {
619
- console.error(
620
- "[MCP-I] ❌ Delegation verification error (API failure)",
621
- {
622
- tool: toolName,
623
- agentDid: identity.did.slice(0, 20) + "...",
624
- errorCode: error.code,
625
- errorMessage: error.message,
626
- errorDetails: error.details,
627
- }
628
- );
629
- }
630
-
631
- // On API errors, fail securely by requiring delegation
632
- // This prevents unauthorized access when verification service is unavailable
633
- const interceptedCall: InterceptedToolCall = {
634
- toolName,
635
- args,
636
- sessionId: session?.id || "unknown",
637
- timestamp: this.clock.now(),
638
- expiresAt: this.clock.calculateExpiry(1800),
639
- };
640
-
641
- const resumeToken = this.generateResumeToken(interceptedCall);
642
- const consentUrl = this.buildConsentUrl(
643
- toolName,
644
- protection.requiredScopes,
645
- session,
646
- resumeToken
647
- );
648
-
649
- this.interceptedCalls.set(resumeToken, interceptedCall);
650
- this.cleanupExpiredInterceptedCalls();
651
-
652
- throw new DelegationRequiredError(
653
- toolName,
654
- protection.requiredScopes,
655
- consentUrl,
656
- interceptedCall,
657
- resumeToken
658
- );
659
- }
660
-
661
- // Unexpected error - log and fail securely
662
- if (this.config.audit?.enabled) {
663
- console.error(
664
- "[MCP-I] ❌ Unexpected error during delegation verification",
665
- {
666
- tool: toolName,
667
- agentDid: identity.did.slice(0, 20) + "...",
668
- error: error.message || String(error),
669
- errorStack: error.stack,
670
- }
671
- );
672
- }
673
-
674
- // Fail securely - require delegation on unexpected errors
675
- const interceptedCall: InterceptedToolCall = {
676
- toolName,
677
- args,
678
- sessionId: session?.id || "unknown",
679
- timestamp: this.clock.now(),
680
- expiresAt: this.clock.calculateExpiry(1800),
681
- };
682
-
683
- const resumeToken = this.generateResumeToken(interceptedCall);
684
- const consentUrl = this.buildConsentUrl(
685
- toolName,
686
- protection.requiredScopes,
687
- session,
688
- resumeToken
689
- );
690
-
691
- this.interceptedCalls.set(resumeToken, interceptedCall);
692
- this.cleanupExpiredInterceptedCalls();
693
-
694
- throw new DelegationRequiredError(
695
- toolName,
696
- protection.requiredScopes,
697
- consentUrl,
698
- interceptedCall,
699
- resumeToken
700
- );
701
- }
702
- }
703
- } else {
704
- // No protection required - tool can be executed freely
705
- if (this.config.audit?.enabled) {
706
- console.log(
707
- "[MCP-I] Tool protection check passed (no delegation required)",
708
- {
709
- tool: toolName,
710
- agentDid: identity.did.slice(0, 20) + "...",
711
- reason: "Tool not configured to require delegation",
712
- }
713
- );
714
- }
715
- }
716
- }
717
-
718
- // Execute the tool
719
- const result = await handler(args);
720
-
721
- // Create proof
722
- const proof = await this.createProof(result, session);
723
-
724
- // Store proof for out-of-band retrieval
725
- this.lastProof = proof;
726
-
727
- // Log if audit is enabled
728
- if (this.config.audit?.enabled) {
729
- this.logAudit("tool_executed", {
730
- tool: toolName,
731
- sessionId: session?.id,
732
- timestamp: this.clock.now(),
733
- });
734
- }
735
-
736
- // Return clean result only (no proof in LLM context)
737
- return result;
738
- }
739
-
740
- /**
741
- * Resume a tool call after authorization
742
- *
743
- * @param resumeToken - Token from DelegationRequiredError
744
- * @param handler - Tool execution handler
745
- * @param delegationToken - Delegation token from authorization
746
- * @returns Tool execution result
747
- */
748
- async resumeToolCall(
749
- resumeToken: string,
750
- handler: (args: any) => Promise<any>,
751
- delegationToken?: string
752
- ): Promise<any> {
753
- const interceptedCall = this.interceptedCalls.get(resumeToken);
754
-
755
- if (!interceptedCall) {
756
- throw new Error(`Invalid or expired resume token: ${resumeToken}`);
757
- }
758
-
759
- // Check if call has expired
760
- if (this.clock.hasExpired(interceptedCall.expiresAt)) {
761
- this.interceptedCalls.delete(resumeToken);
762
- throw new Error(`Resume token expired: ${resumeToken}`);
763
- }
764
-
765
- // Get session for the intercepted call
766
- const session = this.sessions.get(interceptedCall.sessionId);
767
- if (!session) {
768
- throw new Error(
769
- `Session not found for intercepted call: ${interceptedCall.sessionId}`
770
- );
771
- }
772
-
773
- // Add delegation token to session
774
- const enhancedSession = {
775
- ...session,
776
- delegationToken,
777
- };
778
-
779
- // Resume the tool call with delegation
780
- const result = await this.processToolCall(
781
- interceptedCall.toolName,
782
- interceptedCall.args,
783
- handler,
784
- enhancedSession
785
- );
786
-
787
- // Clean up intercepted call after successful resumption to prevent memory leak
788
- this.interceptedCalls.delete(resumeToken);
789
-
790
- return result;
791
- }
792
-
793
- /**
794
- * Generate a resume token for intercepted tool call
795
- */
796
- private generateResumeToken(call: InterceptedToolCall): string {
797
- // Create a deterministic token from the call context
798
- const tokenData = JSON.stringify({
799
- tool: call.toolName,
800
- args: call.args,
801
- sessionId: call.sessionId,
802
- timestamp: call.timestamp,
803
- });
804
-
805
- // Simple hash-based token (in production, use proper crypto)
806
- let hash = 0;
807
- for (let i = 0; i < tokenData.length; i++) {
808
- const char = tokenData.charCodeAt(i);
809
- hash = (hash << 5) - hash + char;
810
- hash = hash & hash; // Convert to 32bit integer
811
- }
812
-
813
- return `resume_${Math.abs(hash).toString(36)}_${Date.now().toString(36)}`;
814
- }
815
-
816
- /**
817
- * Clean up expired intercepted calls
818
- */
819
- private cleanupExpiredInterceptedCalls(): void {
820
- const now = this.clock.now();
821
- for (const [token, call] of this.interceptedCalls.entries()) {
822
- if (this.clock.hasExpired(call.expiresAt)) {
823
- this.interceptedCalls.delete(token);
824
- }
825
- }
826
- }
827
-
828
- /**
829
- * Build consent URL for AgentShield delegation flow
830
- *
831
- * Note: Parameter names use snake_case for AgentShield API compatibility.
832
- * This is documented in docs/API_PARITY_GUIDE.md under "Field Naming Conventions".
833
- *
834
- * AgentShield API requires snake_case in URL parameters:
835
- * - session_id (not sessionId)
836
- * - agent_did (not agentDid)
837
- * - resume_token (not resumeToken)
838
- *
839
- * @param toolName - Tool that requires delegation
840
- * @param scopes - Required scopes for the tool
841
- * @param session - Current session context
842
- * @param resumeToken - Token to resume after delegation
843
- * @param projectId - Project ID for AgentShield API
844
- * @returns Full consent URL with snake_case parameters
845
- */
846
- protected buildConsentUrl(
847
- toolName: string,
848
- scopes: string[],
849
- session?: any,
850
- resumeToken?: string,
851
- projectId?: string
852
- ): string {
853
- // Default implementation - override in subclasses
854
- // This URL should point to AgentShield's consent page
855
- // Parameter names use snake_case for AgentShield API compatibility
856
- const params = new URLSearchParams({
857
- tool: toolName,
858
- scopes: scopes.join(","),
859
- session_id: session?.id || "",
860
- agent_did: session?.agentDid || "",
861
- });
862
-
863
- // Add project_id if provided (required for AgentShield consent endpoint)
864
- if (projectId) {
865
- params.set("project_id", projectId);
866
- }
867
-
868
- // Add resume token if provided
869
- if (resumeToken) {
870
- params.set("resume_token", resumeToken);
871
- }
872
-
873
- // Use AgentShield consent endpoint
874
- return `https://kya.vouched.id/bouncer/consent?${params.toString()}`;
875
- }
876
-
877
- /**
878
- * Issue a new nonce and register it in the cache
879
- * Use this to get a nonce for the session context before calling processToolCall
880
- */
881
- async issueNonce(sessionId: string): Promise<string> {
882
- const nonce = await this.generateNonce();
883
- // Get session to extract agentDid for agent-scoped nonce caching
884
- const session = this.sessions.get(sessionId);
885
- const agentDid = session?.agentDid || (await this.getIdentity()).did;
886
- await this.nonceCache.add(
887
- nonce,
888
- 300, // 5 minute expiry in seconds
889
- agentDid // Agent-scoped nonce to prevent cross-agent replay attacks
890
- );
891
- return nonce;
892
- }
893
-
894
- /**
895
- * Create cryptographic proof for data
896
- */
897
- async createProof(data: any, session?: any): Promise<any> {
898
- const identity = await this.getIdentity();
899
- const timestamp = this.clock.now();
900
-
901
- // Use nonce from session if provided, otherwise generate new one
902
- let nonce: string;
903
- if (session?.nonce) {
904
- nonce = session.nonce;
905
- } else {
906
- nonce = await this.generateNonce();
907
- // Add nonce to cache to prevent replay (agent-scoped to prevent cross-agent replay attacks)
908
- const agentDid = session?.agentDid || identity.did;
909
- await this.nonceCache.add(
910
- nonce,
911
- 300, // 5 minute expiry in seconds
912
- agentDid // Agent-scoped nonce to prevent cross-agent replay attacks
913
- );
914
- }
915
-
916
- const proofData = {
917
- data,
918
- timestamp,
919
- nonce,
920
- did: identity.did,
921
- sessionId: session?.id,
922
- audience: session?.audience,
923
- };
924
-
925
- const signature = await this.signData(proofData);
926
-
927
- return {
928
- timestamp,
929
- nonce,
930
- did: identity.did,
931
- signature,
932
- algorithm: "Ed25519",
933
- sessionId: session?.id,
934
- audience: session?.audience,
935
- };
936
- }
937
-
938
- /**
939
- * Verify a proof
940
- *
941
- * Supports both old format (data, proof) and new DetachedProof format.
942
- * When DetachedProof format is used, ProofVerifier is used if available.
943
- *
944
- * @param dataOrProof - Either raw data (old format) or DetachedProof (new format)
945
- * @param proofOrSession - Either proof object (old format) or session context (new format)
946
- * @returns true if proof is valid, false otherwise
947
- */
948
- async verifyProof(dataOrProof: any, proofOrSession?: any): Promise<boolean> {
949
- // Check if first argument is DetachedProof format
950
- if (
951
- dataOrProof &&
952
- typeof dataOrProof === "object" &&
953
- "jws" in dataOrProof &&
954
- "meta" in dataOrProof
955
- ) {
956
- // New DetachedProof format
957
- const detachedProof = dataOrProof as DetachedProof;
958
- const session = proofOrSession;
959
-
960
- // Use ProofVerifier if available
961
- if (this.proofVerifier) {
962
- try {
963
- // Resolve DID to get public key
964
- const didDoc = await this.fetch.resolveDID(detachedProof.meta.did);
965
- const publicKeyJwk = this.extractPublicKeyJwk(
966
- didDoc,
967
- detachedProof.meta.kid
968
- );
969
-
970
- if (!publicKeyJwk) {
971
- console.error("[MCPIRuntimeBase] Failed to extract public key JWK");
972
- return false;
973
- }
974
-
975
- // Verify proof using ProofVerifier
976
- const result = await this.proofVerifier.verifyProof(
977
- detachedProof,
978
- publicKeyJwk
979
- );
980
-
981
- if (result.valid && session) {
982
- // Store canonical payload in session for detached JWS consumers
983
- const canonicalPayload = this.proofVerifier.buildCanonicalPayload(
984
- detachedProof.meta
985
- );
986
- session.canonicalPayload = canonicalPayload;
987
- }
988
-
989
- return result.valid;
990
- } catch (error) {
991
- console.error("[MCPIRuntimeBase] Proof verification failed:", error);
992
- return false;
993
- }
994
- } else {
995
- // Fallback to old verification if ProofVerifier not available
996
- console.warn(
997
- "[MCPIRuntimeBase] ProofVerifier not available, using fallback verification"
998
- );
999
- return this.verifyProofLegacy(dataOrProof, proofOrSession);
1000
- }
1001
- } else {
1002
- // Old format (data, proof)
1003
- return this.verifyProofLegacy(dataOrProof, proofOrSession);
1004
- }
1005
- }
1006
-
1007
- /**
1008
- * Legacy proof verification (backward compatibility)
1009
- * @internal
1010
- */
1011
- private async verifyProofLegacy(data: any, proof: any): Promise<boolean> {
1012
- try {
1013
- // Check nonce hasn't been used (scoped to agent DID to prevent cross-agent replay attacks)
1014
- if (await this.nonceCache.has(proof.nonce, proof.did)) {
1015
- return false;
1016
- }
1017
-
1018
- // Check timestamp is within skew
1019
- if (
1020
- !this.clock.isWithinSkew(
1021
- proof.timestamp,
1022
- this.config.session?.timestampSkewSeconds || 120
1023
- )
1024
- ) {
1025
- return false;
1026
- }
1027
-
1028
- // Resolve DID to get public key
1029
- const didDoc = await this.fetch.resolveDID(proof.did);
1030
- const publicKey = this.extractPublicKey(didDoc);
1031
-
1032
- // Verify signature
1033
- const proofData = {
1034
- data,
1035
- timestamp: proof.timestamp,
1036
- nonce: proof.nonce,
1037
- did: proof.did,
1038
- sessionId: proof.sessionId,
1039
- };
1040
-
1041
- const dataBytes = new TextEncoder().encode(JSON.stringify(proofData));
1042
- const signatureBytes = this.base64ToBytes(proof.signature);
1043
-
1044
- const isValid = await this.crypto.verify(
1045
- dataBytes,
1046
- signatureBytes,
1047
- publicKey
1048
- );
1049
-
1050
- // If signature is valid, add nonce to cache to prevent replay (scoped to agent DID)
1051
- if (isValid) {
1052
- // Pass TTL in seconds, not absolute timestamp
1053
- const ttlSeconds = (this.config.session?.ttlMinutes || 30) * 60; // Convert minutes to seconds
1054
- await this.nonceCache.add(proof.nonce, ttlSeconds, proof.did);
1055
- }
1056
-
1057
- return isValid;
1058
- } catch (error) {
1059
- console.error("Proof verification failed:", error);
1060
- return false;
1061
- }
1062
- }
1063
-
1064
- /**
1065
- * Verify a JWS proof (full compact JWS format: header.payload.signature)
1066
- *
1067
- * This method provides full cryptographic signature verification using CryptoService.
1068
- * Use this when you have a DetachedProof with JWS instead of raw signature.
1069
- *
1070
- * @param jws - Full compact JWS string (header.payload.signature)
1071
- * @param publicKeyJwk - Ed25519 public key in JWK format
1072
- * @param detachedPayload - Optional detached payload for detached JWS format
1073
- * @returns true if signature is valid, false otherwise
1074
- */
1075
- async verifyProofJWS(
1076
- jws: string,
1077
- publicKeyJwk: Ed25519JWK,
1078
- detachedPayload?: string | Uint8Array
1079
- ): Promise<boolean> {
1080
- if (!this.cryptoService) {
1081
- console.error("[MCPIRuntimeBase] CryptoService not initialized");
1082
- return false;
1083
- }
1084
-
1085
- try {
1086
- const options =
1087
- detachedPayload !== undefined ? { detachedPayload } : undefined;
1088
- return await this.cryptoService.verifyJWS(jws, publicKeyJwk, options);
1089
- } catch (error) {
1090
- console.error("[MCPIRuntimeBase] JWS verification failed:", error);
1091
- return false;
1092
- }
1093
- }
1094
-
1095
- /**
1096
- * Get current session
1097
- */
1098
- async getCurrentSession(): Promise<any> {
1099
- // Find non-expired session
1100
- for (const [id, session] of this.sessions) {
1101
- if (!this.clock.hasExpired(session.expiresAt)) {
1102
- return session;
1103
- }
1104
- }
1105
- return null;
1106
- }
1107
-
1108
- /**
1109
- * Get the last generated proof for out-of-band transport
1110
- */
1111
- getLastProof(): any {
1112
- return this.lastProof;
1113
- }
1114
-
1115
- /**
1116
- * Create well-known handler for identity verification
1117
- */
1118
- createWellKnownHandler(
1119
- config?: WellKnownConfig
1120
- ): (path: string) => Promise<WellKnownResponse | MCPIdentity | null> {
1121
- return async (path: string) => {
1122
- const identity = await this.getIdentity();
1123
-
1124
- if (path === "/.well-known/did.json") {
1125
- const didDocument = this.createDIDDocument(identity);
1126
- return {
1127
- status: 200,
1128
- headers: {
1129
- "Content-Type": "application/did+json",
1130
- "Cache-Control":
1131
- this.config.environment === "production"
1132
- ? "public, max-age=300"
1133
- : "no-store",
1134
- },
1135
- body: JSON.stringify(didDocument, null, 2),
1136
- };
1137
- }
1138
-
1139
- if (path === "/.well-known/agent.json") {
1140
- const agentDocument: AgentDocument = {
1141
- id: identity.did,
1142
- capabilities: {
1143
- "mcp-i": ["handshake", "signing", "verification"],
1144
- },
1145
- };
1146
-
1147
- if (config?.serviceName || config?.serviceEndpoint) {
1148
- agentDocument.metadata = {
1149
- ...(config?.serviceName && { name: config.serviceName }),
1150
- ...(config?.serviceEndpoint && {
1151
- serviceEndpoint: config.serviceEndpoint,
1152
- }),
1153
- };
1154
- }
1155
-
1156
- return {
1157
- status: 200,
1158
- headers: {
1159
- "Content-Type": "application/json",
1160
- "Cache-Control":
1161
- this.config.environment === "production"
1162
- ? "public, max-age=300"
1163
- : "no-store",
1164
- },
1165
- body: JSON.stringify(agentDocument, null, 2),
1166
- };
1167
- }
1168
-
1169
- if (path === "/.well-known/mcp-identity") {
1170
- return {
1171
- did: identity.did,
1172
- publicKey: identity.publicKey,
1173
- serviceName: config?.serviceName || "MCP-I Service",
1174
- serviceEndpoint: config?.serviceEndpoint || "https://example.com",
1175
- timestamp: this.clock.now(),
1176
- };
1177
- }
1178
-
1179
- return null;
1180
- };
1181
- }
1182
-
1183
- /**
1184
- * Create debug endpoint (development only)
1185
- */
1186
- createDebugEndpoint(): any {
1187
- if (this.config.environment === "production") {
1188
- return null;
1189
- }
1190
-
1191
- return async () => {
1192
- const identity = await this.getIdentity();
1193
- const session = await this.getCurrentSession();
1194
-
1195
- return {
1196
- identity: {
1197
- did: identity.did,
1198
- publicKey: identity.publicKey,
1199
- },
1200
- session,
1201
- config: {
1202
- environment: this.config.environment,
1203
- timestampSkewSeconds: this.config.session?.timestampSkewSeconds,
1204
- sessionTtlMinutes: this.config.session?.ttlMinutes,
1205
- },
1206
- timestamp: this.clock.now(),
1207
- };
1208
- };
1209
- }
1210
-
1211
- /**
1212
- * Get audit logger
1213
- */
1214
- getAuditLogger(): any {
1215
- return {
1216
- log: (event: string, data: any) => this.logAudit(event, data),
1217
- };
1218
- }
1219
-
1220
- /**
1221
- * Rotate keys
1222
- */
1223
- async rotateKeys(): Promise<AgentIdentity> {
1224
- const oldDid = this.cachedIdentity?.did;
1225
- const newIdentity = await this.identity.rotateKeys();
1226
- this.cachedIdentity = newIdentity;
1227
-
1228
- if (this.config.audit?.enabled) {
1229
- this.logAudit("keys_rotated", {
1230
- oldDid: oldDid,
1231
- newDid: newIdentity.did,
1232
- timestamp: this.clock.now(),
1233
- });
1234
- }
1235
-
1236
- return newIdentity;
1237
- }
1238
-
1239
- // Helper methods
1240
-
1241
- private async signData(data: any): Promise<string> {
1242
- const identity = await this.getIdentity();
1243
- const dataBytes = new TextEncoder().encode(JSON.stringify(data));
1244
- const signature = await this.crypto.sign(dataBytes, identity.privateKey);
1245
- return this.bytesToBase64(signature);
1246
- }
1247
-
1248
- private async generateNonce(): Promise<string> {
1249
- const bytes = await this.crypto.randomBytes(32);
1250
- return this.bytesToBase64(bytes);
1251
- }
1252
-
1253
- private async generateSessionId(): Promise<string> {
1254
- const bytes = await this.crypto.randomBytes(16);
1255
- return this.bytesToHex(bytes);
1256
- }
1257
-
1258
- /**
1259
- * Log structured events in JSON format (NOT frozen audit format).
1260
- *
1261
- * **Important:** This method logs events in JSON format for general runtime events
1262
- * (e.g., "runtime_initialized", "tool_executed", "keys_rotated").
1263
- *
1264
- * **For frozen format audit logs** (MCP-I spec compliance), use `AuditLogger.logAuditRecord()`
1265
- * from `@kya-os/mcp-i/runtime` instead. The frozen format is:
1266
- * ```
1267
- * audit.v1 ts=<unix> session=<id> audience=<host> did=<did> kid=<kid> reqHash=<sha256:..> resHash=<sha256:..> verified=yes|no scope=<scopeId|->
1268
- * ```
1269
- *
1270
- * **Format:** JSON object with `event`, `data`, `timestamp`, and `timestampFormatted` fields.
1271
- *
1272
- * **Privacy:** If `includePayloads` is false (default), the `data` field is omitted.
1273
- *
1274
- * **Use Cases:**
1275
- * - Developer debugging and local logging
1276
- * - Runtime initialization events
1277
- * - Tool execution tracking (non-spec-compliant)
1278
- * - Key rotation events
1279
- *
1280
- * **NOT for:**
1281
- * - MCP-I spec-compliant audit logs (use `AuditLogger`)
1282
- * - Production audit trails (use `AuditLogger`)
1283
- * - Compliance requirements (use `AuditLogger`)
1284
- *
1285
- * @param event - Event name (e.g., "runtime_initialized", "tool_executed")
1286
- * @param data - Event data (only included if `includePayloads` is true)
1287
- *
1288
- * @example
1289
- * ```typescript
1290
- * // Logs: {"event":"runtime_initialized","timestamp":1234567890,"timestampFormatted":"2024-01-01T00:00:00Z"}
1291
- * this.logAudit("runtime_initialized", { did: "did:key:..." });
1292
- * ```
1293
- *
1294
- * @internal This is a private method for internal runtime events.
1295
- * Use AuditLogger for spec-compliant frozen format audit logs.
1296
- */
1297
- private logAudit(event: string, data: any): void {
1298
- if (!this.config.audit?.enabled) return;
1299
-
1300
- const record = {
1301
- event,
1302
- data: this.config.audit.includePayloads ? data : undefined,
1303
- timestamp: this.clock.now(),
1304
- timestampFormatted: this.clock.format(this.clock.now()),
1305
- };
1306
-
1307
- const logLine = JSON.stringify(record);
1308
-
1309
- if (this.config.audit.logFunction) {
1310
- this.config.audit.logFunction(logLine);
1311
- } else {
1312
- console.log("[AUDIT]", logLine);
1313
- }
1314
- }
1315
-
1316
- private createDIDDocument(identity: AgentIdentity): DIDDocument {
1317
- return {
1318
- "@context": ["https://www.w3.org/ns/did/v1"],
1319
- id: identity.did,
1320
- verificationMethod: [
1321
- {
1322
- id: `${identity.did}#key-1`,
1323
- type: "Ed25519VerificationKey2020",
1324
- controller: identity.did,
1325
- // Using base64 for cross-platform compatibility (Cloudflare Workers)
1326
- // W3C DID spec supports both base64 and multibase formats
1327
- publicKeyBase64: identity.publicKey,
1328
- },
1329
- ],
1330
- authentication: [`${identity.did}#key-1`],
1331
- assertionMethod: [`${identity.did}#key-1`],
1332
- };
1333
- }
1334
-
1335
- private extractPublicKey(didDoc: any): string {
1336
- const method = didDoc.verificationMethod?.[0];
1337
- if (method?.publicKeyBase64) {
1338
- return method.publicKeyBase64;
1339
- }
1340
- if (method?.publicKeyMultibase) {
1341
- // Convert multibase to base64
1342
- return method.publicKeyMultibase; // Simplified
1343
- }
1344
- throw new Error("Public key not found in DID document");
1345
- }
1346
-
1347
- /**
1348
- * Extract public key JWK from DID document
1349
- */
1350
- private extractPublicKeyJwk(didDoc: any, kid?: string): Ed25519JWK | null {
1351
- // Try to find Ed25519 public key matching kid if provided
1352
- const verificationMethod =
1353
- didDoc.verificationMethod?.find((vm: any) => {
1354
- const matchesType =
1355
- vm.type === "Ed25519VerificationKey2020" ||
1356
- vm.type === "JsonWebKey2020";
1357
- const matchesKid = !kid || vm.id === kid || vm.id.endsWith(`#${kid}`);
1358
- return matchesType && matchesKid;
1359
- }) || didDoc.verificationMethod?.[0]; // Fallback to first method
1360
-
1361
- if (verificationMethod?.publicKeyJwk) {
1362
- const jwk = verificationMethod.publicKeyJwk;
1363
- // Ensure it's Ed25519 format
1364
- if (jwk.kty === "OKP" && jwk.crv === "Ed25519") {
1365
- return jwk as Ed25519JWK;
1366
- }
1367
- }
1368
-
1369
- // Fallback: try to convert multibase to JWK (simplified)
1370
- if (verificationMethod?.publicKeyMultibase) {
1371
- // This is a simplified conversion - in production, use proper multibase decoding
1372
- console.warn(
1373
- "[MCPIRuntimeBase] Multibase to JWK conversion not fully implemented"
1374
- );
1375
- return null;
1376
- }
1377
-
1378
- return null;
1379
- }
1380
-
1381
- private bytesToBase64(bytes: Uint8Array): string {
1382
- return Buffer.from(bytes).toString("base64");
1383
- }
1384
-
1385
- private base64ToBytes(base64: string): Uint8Array {
1386
- return new Uint8Array(Buffer.from(base64, "base64"));
1387
- }
1388
-
1389
- private bytesToHex(bytes: Uint8Array): string {
1390
- return Buffer.from(bytes).toString("hex");
1391
- }
1392
- }