@kya-os/mcp-i-core 1.2.3-canary.6 → 1.3.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 (231) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/.turbo/turbo-build.log +4 -0
  3. package/.turbo/turbo-test$colon$coverage.log +4514 -0
  4. package/.turbo/turbo-test.log +2973 -0
  5. package/COMPLIANCE_IMPROVEMENT_REPORT.md +483 -0
  6. package/Composer 3.md +615 -0
  7. package/GPT-5.md +1169 -0
  8. package/OPUS-plan.md +352 -0
  9. package/PHASE_3_AND_4.1_SUMMARY.md +585 -0
  10. package/PHASE_3_SUMMARY.md +317 -0
  11. package/PHASE_4.1.3_SUMMARY.md +428 -0
  12. package/PHASE_4.1_COMPLETE.md +525 -0
  13. package/PHASE_4_USER_DID_IDENTITY_LINKING_PLAN.md +1240 -0
  14. package/SCHEMA_COMPLIANCE_REPORT.md +275 -0
  15. package/TEST_PLAN.md +571 -0
  16. package/coverage/coverage-final.json +57 -0
  17. package/dist/__tests__/utils/mock-providers.d.ts +1 -2
  18. package/dist/__tests__/utils/mock-providers.d.ts.map +1 -1
  19. package/dist/__tests__/utils/mock-providers.js.map +1 -1
  20. package/dist/cache/oauth-config-cache.d.ts +69 -0
  21. package/dist/cache/oauth-config-cache.d.ts.map +1 -0
  22. package/dist/cache/oauth-config-cache.js +76 -0
  23. package/dist/cache/oauth-config-cache.js.map +1 -0
  24. package/dist/identity/idp-token-resolver.d.ts +53 -0
  25. package/dist/identity/idp-token-resolver.d.ts.map +1 -0
  26. package/dist/identity/idp-token-resolver.js +108 -0
  27. package/dist/identity/idp-token-resolver.js.map +1 -0
  28. package/dist/identity/idp-token-storage.interface.d.ts +42 -0
  29. package/dist/identity/idp-token-storage.interface.d.ts.map +1 -0
  30. package/dist/identity/idp-token-storage.interface.js +12 -0
  31. package/dist/identity/idp-token-storage.interface.js.map +1 -0
  32. package/dist/identity/user-did-manager.d.ts +39 -1
  33. package/dist/identity/user-did-manager.d.ts.map +1 -1
  34. package/dist/identity/user-did-manager.js +69 -3
  35. package/dist/identity/user-did-manager.js.map +1 -1
  36. package/dist/index.d.ts +22 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +39 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/runtime/audit-logger.d.ts +37 -0
  41. package/dist/runtime/audit-logger.d.ts.map +1 -0
  42. package/dist/runtime/audit-logger.js +9 -0
  43. package/dist/runtime/audit-logger.js.map +1 -0
  44. package/dist/runtime/base.d.ts +58 -2
  45. package/dist/runtime/base.d.ts.map +1 -1
  46. package/dist/runtime/base.js +266 -11
  47. package/dist/runtime/base.js.map +1 -1
  48. package/dist/services/access-control.service.d.ts.map +1 -1
  49. package/dist/services/access-control.service.js +200 -35
  50. package/dist/services/access-control.service.js.map +1 -1
  51. package/dist/services/authorization/authorization-registry.d.ts +29 -0
  52. package/dist/services/authorization/authorization-registry.d.ts.map +1 -0
  53. package/dist/services/authorization/authorization-registry.js +57 -0
  54. package/dist/services/authorization/authorization-registry.js.map +1 -0
  55. package/dist/services/authorization/types.d.ts +53 -0
  56. package/dist/services/authorization/types.d.ts.map +1 -0
  57. package/dist/services/authorization/types.js +10 -0
  58. package/dist/services/authorization/types.js.map +1 -0
  59. package/dist/services/batch-delegation.service.d.ts +53 -0
  60. package/dist/services/batch-delegation.service.d.ts.map +1 -0
  61. package/dist/services/batch-delegation.service.js +95 -0
  62. package/dist/services/batch-delegation.service.js.map +1 -0
  63. package/dist/services/oauth-config.service.d.ts +53 -0
  64. package/dist/services/oauth-config.service.d.ts.map +1 -0
  65. package/dist/services/oauth-config.service.js +117 -0
  66. package/dist/services/oauth-config.service.js.map +1 -0
  67. package/dist/services/oauth-provider-registry.d.ts +77 -0
  68. package/dist/services/oauth-provider-registry.d.ts.map +1 -0
  69. package/dist/services/oauth-provider-registry.js +112 -0
  70. package/dist/services/oauth-provider-registry.js.map +1 -0
  71. package/dist/services/oauth-service.d.ts +77 -0
  72. package/dist/services/oauth-service.d.ts.map +1 -0
  73. package/dist/services/oauth-service.js +348 -0
  74. package/dist/services/oauth-service.js.map +1 -0
  75. package/dist/services/oauth-token-retrieval.service.d.ts +49 -0
  76. package/dist/services/oauth-token-retrieval.service.d.ts.map +1 -0
  77. package/dist/services/oauth-token-retrieval.service.js +150 -0
  78. package/dist/services/oauth-token-retrieval.service.js.map +1 -0
  79. package/dist/services/provider-resolver.d.ts +48 -0
  80. package/dist/services/provider-resolver.d.ts.map +1 -0
  81. package/dist/services/provider-resolver.js +120 -0
  82. package/dist/services/provider-resolver.js.map +1 -0
  83. package/dist/services/provider-validator.d.ts +55 -0
  84. package/dist/services/provider-validator.d.ts.map +1 -0
  85. package/dist/services/provider-validator.js +135 -0
  86. package/dist/services/provider-validator.js.map +1 -0
  87. package/dist/services/tool-context-builder.d.ts +57 -0
  88. package/dist/services/tool-context-builder.d.ts.map +1 -0
  89. package/dist/services/tool-context-builder.js +125 -0
  90. package/dist/services/tool-context-builder.js.map +1 -0
  91. package/dist/services/tool-protection.service.d.ts +87 -10
  92. package/dist/services/tool-protection.service.d.ts.map +1 -1
  93. package/dist/services/tool-protection.service.js +282 -112
  94. package/dist/services/tool-protection.service.js.map +1 -1
  95. package/dist/types/oauth-required-error.d.ts +40 -0
  96. package/dist/types/oauth-required-error.d.ts.map +1 -0
  97. package/dist/types/oauth-required-error.js +40 -0
  98. package/dist/types/oauth-required-error.js.map +1 -0
  99. package/dist/utils/did-helpers.d.ts +33 -0
  100. package/dist/utils/did-helpers.d.ts.map +1 -1
  101. package/dist/utils/did-helpers.js +40 -0
  102. package/dist/utils/did-helpers.js.map +1 -1
  103. package/dist/utils/index.d.ts +1 -0
  104. package/dist/utils/index.d.ts.map +1 -1
  105. package/dist/utils/index.js +1 -0
  106. package/dist/utils/index.js.map +1 -1
  107. package/docs/API_REFERENCE.md +1362 -0
  108. package/docs/COMPLIANCE_MATRIX.md +691 -0
  109. package/docs/STATUSLIST2021_GUIDE.md +696 -0
  110. package/docs/W3C_VC_DELEGATION_GUIDE.md +710 -0
  111. package/package.json +24 -50
  112. package/scripts/audit-compliance.ts +724 -0
  113. package/src/__tests__/cache/tool-protection-cache.test.ts +640 -0
  114. package/src/__tests__/config/provider-runtime-config.test.ts +309 -0
  115. package/src/__tests__/delegation-e2e.test.ts +690 -0
  116. package/src/__tests__/identity/user-did-manager.test.ts +213 -0
  117. package/src/__tests__/index.test.ts +56 -0
  118. package/src/__tests__/integration/full-flow.test.ts +776 -0
  119. package/src/__tests__/integration.test.ts +281 -0
  120. package/src/__tests__/providers/base.test.ts +173 -0
  121. package/src/__tests__/providers/memory.test.ts +319 -0
  122. package/src/__tests__/regression/phase2-regression.test.ts +427 -0
  123. package/src/__tests__/runtime/audit-logger.test.ts +154 -0
  124. package/src/__tests__/runtime/base-extensions.test.ts +593 -0
  125. package/src/__tests__/runtime/base.test.ts +869 -0
  126. package/src/__tests__/runtime/delegation-flow.test.ts +164 -0
  127. package/src/__tests__/runtime/proof-client-did.test.ts +375 -0
  128. package/src/__tests__/runtime/route-interception.test.ts +686 -0
  129. package/src/__tests__/runtime/tool-protection-enforcement.test.ts +908 -0
  130. package/src/__tests__/services/agentshield-integration.test.ts +784 -0
  131. package/src/__tests__/services/provider-resolver-edge-cases.test.ts +487 -0
  132. package/src/__tests__/services/tool-protection-oauth-provider.test.ts +480 -0
  133. package/src/__tests__/services/tool-protection.service.test.ts +1366 -0
  134. package/src/__tests__/utils/mock-providers.ts +340 -0
  135. package/src/cache/oauth-config-cache.d.ts +69 -0
  136. package/src/cache/oauth-config-cache.d.ts.map +1 -0
  137. package/src/cache/oauth-config-cache.js +71 -0
  138. package/src/cache/oauth-config-cache.js.map +1 -0
  139. package/src/cache/oauth-config-cache.ts +123 -0
  140. package/src/cache/tool-protection-cache.ts +171 -0
  141. package/src/compliance/EXAMPLE.md +412 -0
  142. package/src/compliance/__tests__/schema-verifier.test.ts +797 -0
  143. package/src/compliance/index.ts +8 -0
  144. package/src/compliance/schema-registry.ts +460 -0
  145. package/src/compliance/schema-verifier.ts +708 -0
  146. package/src/config/__tests__/remote-config.spec.ts +268 -0
  147. package/src/config/remote-config.ts +174 -0
  148. package/src/config.ts +309 -0
  149. package/src/delegation/__tests__/audience-validator.test.ts +112 -0
  150. package/src/delegation/__tests__/bitstring.test.ts +346 -0
  151. package/src/delegation/__tests__/cascading-revocation.test.ts +628 -0
  152. package/src/delegation/__tests__/delegation-graph.test.ts +584 -0
  153. package/src/delegation/__tests__/utils.test.ts +152 -0
  154. package/src/delegation/__tests__/vc-issuer.test.ts +442 -0
  155. package/src/delegation/__tests__/vc-verifier.test.ts +922 -0
  156. package/src/delegation/audience-validator.ts +52 -0
  157. package/src/delegation/bitstring.ts +278 -0
  158. package/src/delegation/cascading-revocation.ts +370 -0
  159. package/src/delegation/delegation-graph.ts +299 -0
  160. package/src/delegation/index.ts +14 -0
  161. package/src/delegation/statuslist-manager.ts +353 -0
  162. package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +366 -0
  163. package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +228 -0
  164. package/src/delegation/storage/index.ts +9 -0
  165. package/src/delegation/storage/memory-graph-storage.ts +178 -0
  166. package/src/delegation/storage/memory-statuslist-storage.ts +77 -0
  167. package/src/delegation/utils.ts +42 -0
  168. package/src/delegation/vc-issuer.ts +232 -0
  169. package/src/delegation/vc-verifier.ts +568 -0
  170. package/src/identity/idp-token-resolver.ts +147 -0
  171. package/src/identity/idp-token-storage.interface.ts +59 -0
  172. package/src/identity/user-did-manager.ts +370 -0
  173. package/src/index.ts +260 -0
  174. package/src/providers/base.d.ts +91 -0
  175. package/src/providers/base.d.ts.map +1 -0
  176. package/src/providers/base.js +38 -0
  177. package/src/providers/base.js.map +1 -0
  178. package/src/providers/base.ts +96 -0
  179. package/src/providers/memory.ts +142 -0
  180. package/src/runtime/audit-logger.ts +39 -0
  181. package/src/runtime/base.ts +1329 -0
  182. package/src/services/__tests__/access-control.integration.test.ts +443 -0
  183. package/src/services/__tests__/access-control.proof-response-validation.test.ts +578 -0
  184. package/src/services/__tests__/access-control.service.test.ts +970 -0
  185. package/src/services/__tests__/batch-delegation.service.test.ts +351 -0
  186. package/src/services/__tests__/crypto.service.test.ts +531 -0
  187. package/src/services/__tests__/oauth-provider-registry.test.ts +142 -0
  188. package/src/services/__tests__/proof-verifier.integration.test.ts +485 -0
  189. package/src/services/__tests__/proof-verifier.test.ts +489 -0
  190. package/src/services/__tests__/provider-resolution.integration.test.ts +198 -0
  191. package/src/services/__tests__/provider-resolver.test.ts +217 -0
  192. package/src/services/__tests__/storage.service.test.ts +358 -0
  193. package/src/services/access-control.service.ts +990 -0
  194. package/src/services/authorization/authorization-registry.ts +66 -0
  195. package/src/services/authorization/types.ts +71 -0
  196. package/src/services/batch-delegation.service.ts +137 -0
  197. package/src/services/crypto.service.ts +302 -0
  198. package/src/services/errors.ts +76 -0
  199. package/src/services/index.ts +9 -0
  200. package/src/services/oauth-config.service.d.ts +53 -0
  201. package/src/services/oauth-config.service.d.ts.map +1 -0
  202. package/src/services/oauth-config.service.js +113 -0
  203. package/src/services/oauth-config.service.js.map +1 -0
  204. package/src/services/oauth-config.service.ts +166 -0
  205. package/src/services/oauth-provider-registry.d.ts +57 -0
  206. package/src/services/oauth-provider-registry.d.ts.map +1 -0
  207. package/src/services/oauth-provider-registry.js +73 -0
  208. package/src/services/oauth-provider-registry.js.map +1 -0
  209. package/src/services/oauth-provider-registry.ts +123 -0
  210. package/src/services/oauth-service.ts +510 -0
  211. package/src/services/oauth-token-retrieval.service.ts +245 -0
  212. package/src/services/proof-verifier.ts +478 -0
  213. package/src/services/provider-resolver.d.ts +48 -0
  214. package/src/services/provider-resolver.d.ts.map +1 -0
  215. package/src/services/provider-resolver.js +106 -0
  216. package/src/services/provider-resolver.js.map +1 -0
  217. package/src/services/provider-resolver.ts +144 -0
  218. package/src/services/provider-validator.ts +170 -0
  219. package/src/services/storage.service.ts +566 -0
  220. package/src/services/tool-context-builder.ts +172 -0
  221. package/src/services/tool-protection.service.ts +958 -0
  222. package/src/types/oauth-required-error.ts +63 -0
  223. package/src/types/tool-protection.ts +155 -0
  224. package/src/utils/__tests__/did-helpers.test.ts +101 -0
  225. package/src/utils/base64.ts +148 -0
  226. package/src/utils/cors.ts +83 -0
  227. package/src/utils/did-helpers.ts +150 -0
  228. package/src/utils/index.ts +8 -0
  229. package/src/utils/storage-keys.ts +278 -0
  230. package/tsconfig.json +21 -0
  231. package/vitest.config.ts +56 -0
@@ -0,0 +1,566 @@
1
+ /**
2
+ * Storage Service Factory
3
+ *
4
+ * Auto-selects storage providers based on available configuration.
5
+ * Priority: Redis > Cloudflare KV > Cloudflare Durable Objects > Memory
6
+ *
7
+ * @package @kya-os/mcp-i-core
8
+ */
9
+
10
+ import { StorageProvider, NonceCacheProvider } from "../providers/base.js";
11
+ import {
12
+ MemoryStorageProvider,
13
+ MemoryNonceCacheProvider,
14
+ } from "../providers/memory.js";
15
+
16
+ /**
17
+ * Storage service configuration
18
+ */
19
+ export interface StorageServiceConfig {
20
+ /** Redis URL (e.g., "redis://localhost:6379") */
21
+ redisUrl?: string;
22
+
23
+ /** Cloudflare KV namespace */
24
+ kvNamespace?: {
25
+ get(key: string): Promise<string | null>;
26
+ put(
27
+ key: string,
28
+ value: string,
29
+ options?: { expirationTtl?: number }
30
+ ): Promise<void>;
31
+ delete(key: string): Promise<void>;
32
+ list(options?: {
33
+ prefix?: string;
34
+ }): Promise<{ keys: Array<{ name: string }> }>;
35
+ };
36
+
37
+ /** Cloudflare Durable Object state */
38
+ durableObjectState?: {
39
+ storage: {
40
+ get(key: string): Promise<string | undefined>;
41
+ put(key: string, value: string): Promise<void>;
42
+ delete(key: string): Promise<void>;
43
+ list(options?: { prefix?: string }): Promise<Map<string, string>>;
44
+ };
45
+ };
46
+
47
+ /** Fallback to memory if no external storage configured (default: true) */
48
+ fallbackToMemory?: boolean;
49
+ }
50
+
51
+ /**
52
+ * Storage providers result
53
+ */
54
+ export interface StorageProviders {
55
+ storageProvider: StorageProvider;
56
+ nonceCacheProvider: NonceCacheProvider;
57
+ }
58
+
59
+ /**
60
+ * Key helper functions for consistent key formatting
61
+ */
62
+ export class StorageKeyHelpers {
63
+ /**
64
+ * Build delegation key using composite format
65
+ * Format: delegation:${userDid}:${agentDid}:${projectId}
66
+ */
67
+ static buildDelegationKey(
68
+ userDid: string,
69
+ agentDid: string,
70
+ projectId: string
71
+ ): string {
72
+ return `delegation:${userDid}:${agentDid}:${projectId}`;
73
+ }
74
+
75
+ /**
76
+ * Build session key
77
+ * Format: session:${sessionId}
78
+ */
79
+ static buildSessionKey(sessionId: string): string {
80
+ return `session:${sessionId}`;
81
+ }
82
+
83
+ /**
84
+ * Build nonce key
85
+ * Format: nonce:${agentDid}:${nonce}
86
+ */
87
+ static buildNonceKey(agentDid: string, nonce: string): string {
88
+ return `nonce:${agentDid}:${nonce}`;
89
+ }
90
+
91
+ /**
92
+ * Parse delegation key back into components
93
+ *
94
+ * Format: delegation:${userDid}:${agentDid}:${projectId}
95
+ *
96
+ * Note: DIDs contain colons (e.g., did:key:z123), so we can't simply split by ":"
97
+ * Instead, we:
98
+ * 1. Check that key starts with "delegation:"
99
+ * 2. Take the last part as projectId (doesn't contain colons)
100
+ * 3. Find where agentDid starts (look for "did:" pattern)
101
+ * 4. Everything before agentDid is userDid, everything between agentDid and projectId is agentDid
102
+ *
103
+ * Strategy: Since DIDs always start with "did:", we can find the second occurrence of "did:"
104
+ * to determine where agentDid begins. However, this assumes userDid and agentDid both start with "did:".
105
+ *
106
+ * For keys like "delegation:did:key:user123:did:key:agent456:project-789":
107
+ * - Remove "delegation:" prefix → "did:key:user123:did:key:agent456:project-789"
108
+ * - Find last ":" → separates agentDid from projectId
109
+ * - projectId = "project-789"
110
+ * - Find second occurrence of "did:" → separates userDid from agentDid
111
+ * - userDid = "did:key:user123"
112
+ * - agentDid = "did:key:agent456"
113
+ */
114
+ static parseDelegationKey(
115
+ key: string
116
+ ): { userDid: string; agentDid: string; projectId: string } | null {
117
+ if (!key.startsWith("delegation:")) {
118
+ return null;
119
+ }
120
+
121
+ // Remove "delegation:" prefix
122
+ const rest = key.slice("delegation:".length);
123
+
124
+ // Find the last colon (separates agentDid from projectId)
125
+ const lastColonIndex = rest.lastIndexOf(":");
126
+ if (lastColonIndex === -1) {
127
+ return null; // No colon found, invalid format
128
+ }
129
+
130
+ // Last part is projectId (doesn't contain colons)
131
+ const projectId = rest.slice(lastColonIndex + 1);
132
+ if (!projectId) {
133
+ return null; // Empty projectId
134
+ }
135
+
136
+ // Everything before the last colon is userDid:agentDid
137
+ const userDidAndAgentDid = rest.slice(0, lastColonIndex);
138
+
139
+ // Find the second occurrence of "did:" to separate userDid from agentDid
140
+ // First occurrence is at the start (userDid), second is agentDid
141
+ const firstDidIndex = userDidAndAgentDid.indexOf("did:");
142
+ if (firstDidIndex !== 0) {
143
+ return null; // userDid must start with "did:"
144
+ }
145
+
146
+ // Find where the first DID ends by looking for the pattern "did:method:identifier"
147
+ // A DID has the format: did:method:identifier (where identifier may contain colons)
148
+ // We need to find the next "did:" that appears after a complete DID
149
+ // Strategy: Find the method separator colon (after "did:"), then search for the next "did:" after that
150
+ // This ensures we find the second complete DID, not a substring within the first DID's identifier
151
+ const firstMethodColon = userDidAndAgentDid.indexOf(
152
+ ":",
153
+ firstDidIndex + "did:".length
154
+ );
155
+ if (firstMethodColon === -1) {
156
+ return null; // Invalid DID format - no method separator
157
+ }
158
+
159
+ // Now search for the next "did:" after the first complete DID
160
+ // The first DID ends somewhere after the method colon, so search from there
161
+ const secondDidIndex = userDidAndAgentDid.indexOf(
162
+ "did:",
163
+ firstMethodColon + 1
164
+ );
165
+ if (secondDidIndex === -1) {
166
+ return null; // No second "did:" found, can't separate userDid and agentDid
167
+ }
168
+
169
+ // userDid is everything before the second "did:", agentDid is everything after
170
+ const userDid = userDidAndAgentDid.slice(0, secondDidIndex);
171
+ const agentDid = userDidAndAgentDid.slice(secondDidIndex);
172
+
173
+ // Remove trailing colon from userDid if present
174
+ const cleanUserDid = userDid.endsWith(":") ? userDid.slice(0, -1) : userDid;
175
+
176
+ if (!cleanUserDid || !agentDid) {
177
+ return null; // Empty userDid or agentDid
178
+ }
179
+
180
+ return {
181
+ userDid: cleanUserDid,
182
+ agentDid,
183
+ projectId,
184
+ };
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Durable Object Storage Provider
190
+ * Wraps Cloudflare Durable Object state storage
191
+ */
192
+ class DurableObjectStorageProvider extends StorageProvider {
193
+ private state: NonNullable<StorageServiceConfig["durableObjectState"]>;
194
+
195
+ constructor(state: NonNullable<StorageServiceConfig["durableObjectState"]>) {
196
+ super();
197
+ this.state = state;
198
+ }
199
+
200
+ async get(key: string): Promise<string | null> {
201
+ const value = await this.state.storage.get(key);
202
+ return value ?? null;
203
+ }
204
+
205
+ async set(key: string, value: string): Promise<void> {
206
+ await this.state.storage.put(key, value);
207
+ }
208
+
209
+ async delete(key: string): Promise<void> {
210
+ await this.state.storage.delete(key);
211
+ }
212
+
213
+ async exists(key: string): Promise<boolean> {
214
+ const value = await this.state.storage.get(key);
215
+ return value !== undefined;
216
+ }
217
+
218
+ async list(prefix?: string): Promise<string[]> {
219
+ const result = await this.state.storage.list(
220
+ prefix ? { prefix } : undefined
221
+ );
222
+ return Array.from(result.keys());
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Durable Object Nonce Cache Provider
228
+ */
229
+ class DurableObjectNonceCacheProvider extends NonceCacheProvider {
230
+ private state: NonNullable<StorageServiceConfig["durableObjectState"]>;
231
+
232
+ constructor(state: NonNullable<StorageServiceConfig["durableObjectState"]>) {
233
+ super();
234
+ this.state = state;
235
+ }
236
+
237
+ async has(nonce: string, agentDid?: string): Promise<boolean> {
238
+ const key = agentDid
239
+ ? StorageKeyHelpers.buildNonceKey(agentDid, nonce)
240
+ : `nonce:${nonce}`;
241
+ const value = await this.state.storage.get(key);
242
+ if (!value) {
243
+ return false;
244
+ }
245
+ const expiresAt = parseInt(value, 10);
246
+ return Date.now() < expiresAt;
247
+ }
248
+
249
+ async add(
250
+ nonce: string,
251
+ ttlSeconds: number,
252
+ agentDid?: string
253
+ ): Promise<void> {
254
+ const key = agentDid
255
+ ? StorageKeyHelpers.buildNonceKey(agentDid, nonce)
256
+ : `nonce:${nonce}`;
257
+ // Convert TTL seconds to absolute expiration timestamp for storage
258
+ const expiresAt = Date.now() + ttlSeconds * 1000;
259
+ await this.state.storage.put(key, expiresAt.toString());
260
+ }
261
+
262
+ async cleanup(): Promise<void> {
263
+ // Durable Objects don't support efficient bulk operations
264
+ // Cleanup would require listing all keys, which is expensive
265
+ // For now, rely on TTL checking in has() method
266
+ }
267
+
268
+ async destroy(): Promise<void> {
269
+ // Durable Objects state persists automatically
270
+ // No explicit cleanup needed
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Redis Storage Provider
276
+ * Uses Redis for storage operations
277
+ */
278
+ class RedisStorageProvider extends StorageProvider {
279
+ private redis: any; // Redis client from 'redis' package
280
+
281
+ constructor(redisClient: any) {
282
+ super();
283
+ this.redis = redisClient;
284
+ }
285
+
286
+ async get(key: string): Promise<string | null> {
287
+ return await this.redis.get(key);
288
+ }
289
+
290
+ async set(key: string, value: string): Promise<void> {
291
+ await this.redis.set(key, value);
292
+ }
293
+
294
+ async delete(key: string): Promise<void> {
295
+ await this.redis.del(key);
296
+ }
297
+
298
+ async exists(key: string): Promise<boolean> {
299
+ const result = await this.redis.exists(key);
300
+ return result === 1;
301
+ }
302
+
303
+ async list(prefix?: string): Promise<string[]> {
304
+ const pattern = prefix ? `${prefix}*` : "*";
305
+ const keys = await this.redis.keys(pattern);
306
+ return keys;
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Redis Nonce Cache Provider
312
+ */
313
+ class RedisNonceCacheProvider extends NonceCacheProvider {
314
+ private redis: any;
315
+ private keyPrefix: string;
316
+
317
+ constructor(redisClient: any, keyPrefix = "nonce:") {
318
+ super();
319
+ this.redis = redisClient;
320
+ this.keyPrefix = keyPrefix;
321
+ }
322
+
323
+ async has(nonce: string, agentDid?: string): Promise<boolean> {
324
+ const key = agentDid
325
+ ? StorageKeyHelpers.buildNonceKey(agentDid, nonce)
326
+ : `${this.keyPrefix}${nonce}`;
327
+ const result = await this.redis.exists(key);
328
+ return result === 1;
329
+ }
330
+
331
+ async add(
332
+ nonce: string,
333
+ ttlSeconds: number,
334
+ agentDid?: string
335
+ ): Promise<void> {
336
+ const key = agentDid
337
+ ? StorageKeyHelpers.buildNonceKey(agentDid, nonce)
338
+ : `${this.keyPrefix}${nonce}`;
339
+ // Use TTL directly in seconds (callers now pass TTL, not absolute timestamp)
340
+ await this.redis.setEx(key, ttlSeconds, "1");
341
+ }
342
+
343
+ async cleanup(): Promise<void> {
344
+ // Redis handles TTL automatically, no cleanup needed
345
+ }
346
+
347
+ async destroy(): Promise<void> {
348
+ // Redis connection should be managed by caller
349
+ }
350
+ }
351
+
352
+ /**
353
+ * KV Storage Provider wrapper
354
+ * Uses existing KVStorageProvider from mcp-i-cloudflare
355
+ */
356
+ /**
357
+ * Create Cloudflare KV storage providers
358
+ *
359
+ * This function dynamically imports Cloudflare-specific storage providers.
360
+ * The @ts-ignore is necessary because @kya-os/mcp-i-cloudflare/providers/storage
361
+ * is an optional dependency that may not exist in Node.js environments.
362
+ * This is intentional for platform-specific code - the import will fail at runtime
363
+ * if the module doesn't exist, which is handled by the try-catch block.
364
+ */
365
+ async function createKVStorageProvider(
366
+ kvNamespace: StorageServiceConfig["kvNamespace"]
367
+ ): Promise<{ storage: StorageProvider; nonceCache: NonceCacheProvider }> {
368
+ // Dynamic import to avoid bundling Cloudflare-specific code
369
+ // This is an optional dependency that may not exist in all environments
370
+ try {
371
+ // Use string literal to avoid TypeScript checking the module path at compile time
372
+ const storageModulePath = "@kya-os/mcp-i-cloudflare/providers/storage";
373
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
374
+ // @ts-ignore TS2307 - Optional dependency: @kya-os/mcp-i-cloudflare/providers/storage
375
+ // may not exist in Node.js environments. This is intentional for platform-specific code.
376
+ const { KVStorageProvider, KVNonceCacheProvider } = await import(
377
+ storageModulePath
378
+ );
379
+
380
+ const storage = new KVStorageProvider(kvNamespace as any);
381
+ const nonceCache = new KVNonceCacheProvider(kvNamespace as any);
382
+
383
+ return { storage, nonceCache };
384
+ } catch (error) {
385
+ throw new Error(
386
+ `Failed to import Cloudflare storage providers: ${error instanceof Error ? error.message : String(error)}`
387
+ );
388
+ }
389
+ }
390
+
391
+ /**
392
+ * Create storage providers based on configuration
393
+ *
394
+ * Priority order:
395
+ * 1. Redis (if redisUrl provided)
396
+ * 2. Cloudflare KV (if kvNamespace provided)
397
+ * 3. Cloudflare Durable Objects (if durableObjectState provided)
398
+ * 4. In-memory fallback (if fallbackToMemory is true, default)
399
+ */
400
+ export async function createStorageProviders(
401
+ config: StorageServiceConfig
402
+ ): Promise<StorageProviders> {
403
+ const fallbackToMemory = config.fallbackToMemory !== false;
404
+
405
+ // Priority 1: Redis
406
+ if (config.redisUrl) {
407
+ try {
408
+ // Dynamic import to avoid bundling Redis in environments that don't need it
409
+ // Use string literal to avoid TypeScript checking the module path at compile time
410
+ const redisModulePath = "redis";
411
+
412
+ // @ts-ignore TS2307 - Optional dependency: 'redis' package may not be installed.
413
+ // This is intentional - the import will fail at runtime if Redis is not available,
414
+ // which is handled by the try-catch block.
415
+ const redisModule = await import(redisModulePath).catch(() => null);
416
+ if (!redisModule) {
417
+ throw new Error("Redis package not available");
418
+ }
419
+
420
+ const { createClient } = redisModule;
421
+ // @ts-ignore TS2307 - Type inference from dynamic import
422
+ // Redis client type is inferred from the runtime module
423
+ const redis = createClient({
424
+ url: config.redisUrl,
425
+ socket: {
426
+ connectTimeout: 5000,
427
+ },
428
+ });
429
+
430
+ await redis.connect();
431
+ await redis.ping();
432
+
433
+ const storageProvider = new RedisStorageProvider(redis);
434
+ const nonceCacheProvider = new RedisNonceCacheProvider(redis);
435
+
436
+ return {
437
+ storageProvider,
438
+ nonceCacheProvider,
439
+ };
440
+ } catch (error) {
441
+ if (!fallbackToMemory) {
442
+ throw error;
443
+ }
444
+ console.warn(
445
+ "[StorageService] Failed to connect to Redis, falling back to memory:",
446
+ error instanceof Error ? error.message : String(error)
447
+ );
448
+ }
449
+ }
450
+
451
+ // Priority 2: Cloudflare KV
452
+ if (config.kvNamespace) {
453
+ try {
454
+ const providers = await createKVStorageProvider(config.kvNamespace);
455
+ return {
456
+ storageProvider: providers.storage,
457
+ nonceCacheProvider: providers.nonceCache,
458
+ };
459
+ } catch (error) {
460
+ if (!fallbackToMemory) {
461
+ throw error;
462
+ }
463
+ console.warn(
464
+ "[StorageService] Failed to initialize KV, falling back to memory:",
465
+ error instanceof Error ? error.message : String(error)
466
+ );
467
+ }
468
+ }
469
+
470
+ // Priority 3: Cloudflare Durable Objects
471
+ if (config.durableObjectState) {
472
+ try {
473
+ const storageProvider = new DurableObjectStorageProvider(
474
+ config.durableObjectState as NonNullable<
475
+ StorageServiceConfig["durableObjectState"]
476
+ >
477
+ );
478
+ const nonceCacheProvider = new DurableObjectNonceCacheProvider(
479
+ config.durableObjectState as NonNullable<
480
+ StorageServiceConfig["durableObjectState"]
481
+ >
482
+ );
483
+
484
+ return {
485
+ storageProvider,
486
+ nonceCacheProvider,
487
+ };
488
+ } catch (error) {
489
+ if (!fallbackToMemory) {
490
+ throw error;
491
+ }
492
+ console.warn(
493
+ "[StorageService] Failed to initialize Durable Objects, falling back to memory:",
494
+ error instanceof Error ? error.message : String(error)
495
+ );
496
+ }
497
+ }
498
+
499
+ // Priority 4: In-memory fallback
500
+ if (fallbackToMemory) {
501
+ return {
502
+ storageProvider: new MemoryStorageProvider(),
503
+ nonceCacheProvider: new MemoryNonceCacheProvider(),
504
+ };
505
+ }
506
+
507
+ throw new Error(
508
+ "No storage provider configured and fallbackToMemory is false"
509
+ );
510
+ }
511
+
512
+ /**
513
+ * Migration utility for legacy key formats
514
+ *
515
+ * Migrates keys from old format (e.g., `agent:${did}:delegation`) to new composite format
516
+ */
517
+ export async function migrateLegacyKeys(
518
+ oldKeyPrefix: string,
519
+ newKeyFormat: (oldKey: string) => string | null,
520
+ storageProvider: StorageProvider,
521
+ batchSize = 100
522
+ ): Promise<number> {
523
+ let migrated = 0;
524
+
525
+ try {
526
+ // List all keys with old prefix
527
+ const oldKeys = await storageProvider.list(oldKeyPrefix);
528
+
529
+ for (const oldKey of oldKeys) {
530
+ const newKey = newKeyFormat(oldKey);
531
+ if (!newKey) {
532
+ continue; // Skip if transformation fails
533
+ }
534
+
535
+ // Check if new key already exists
536
+ const existing = await storageProvider.exists(newKey);
537
+ if (existing) {
538
+ continue; // Skip if already migrated
539
+ }
540
+
541
+ // Read old value
542
+ const value = await storageProvider.get(oldKey);
543
+ if (!value) {
544
+ continue; // Skip if old key has no value
545
+ }
546
+
547
+ // Write to new key
548
+ await storageProvider.set(newKey, value);
549
+
550
+ // Optionally delete old key (commented out for safety)
551
+ // await storageProvider.delete(oldKey);
552
+
553
+ migrated++;
554
+
555
+ if (migrated % batchSize === 0) {
556
+ // Yield to event loop for large migrations
557
+ await new Promise((resolve) => setTimeout(resolve, 0));
558
+ }
559
+ }
560
+ } catch (error) {
561
+ console.error("[StorageService] Migration error:", error);
562
+ throw error;
563
+ }
564
+
565
+ return migrated;
566
+ }