@kya-os/mcp-i-cloudflare 1.5.8-canary.3 → 1.5.8-canary.30

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 (51) hide show
  1. package/dist/__tests__/e2e/test-config.d.ts +37 -0
  2. package/dist/__tests__/e2e/test-config.d.ts.map +1 -0
  3. package/dist/__tests__/e2e/test-config.js +62 -0
  4. package/dist/__tests__/e2e/test-config.js.map +1 -0
  5. package/dist/adapter.d.ts +31 -0
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/adapter.js +416 -58
  8. package/dist/adapter.js.map +1 -1
  9. package/dist/agent.d.ts.map +1 -1
  10. package/dist/agent.js +87 -3
  11. package/dist/agent.js.map +1 -1
  12. package/dist/app.d.ts.map +1 -1
  13. package/dist/app.js +19 -3
  14. package/dist/app.js.map +1 -1
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js +33 -4
  17. package/dist/config.js.map +1 -1
  18. package/dist/helpers/env-mapper.d.ts +60 -1
  19. package/dist/helpers/env-mapper.d.ts.map +1 -1
  20. package/dist/helpers/env-mapper.js +136 -6
  21. package/dist/helpers/env-mapper.js.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +6 -2
  25. package/dist/index.js.map +1 -1
  26. package/dist/runtime/audit-logger.d.ts +96 -0
  27. package/dist/runtime/audit-logger.d.ts.map +1 -0
  28. package/dist/runtime/audit-logger.js +276 -0
  29. package/dist/runtime/audit-logger.js.map +1 -0
  30. package/dist/runtime.d.ts +12 -1
  31. package/dist/runtime.d.ts.map +1 -1
  32. package/dist/runtime.js +30 -2
  33. package/dist/runtime.js.map +1 -1
  34. package/dist/services/admin.service.d.ts.map +1 -1
  35. package/dist/services/admin.service.js +15 -1
  36. package/dist/services/admin.service.js.map +1 -1
  37. package/dist/services/consent-audit.service.d.ts +91 -0
  38. package/dist/services/consent-audit.service.d.ts.map +1 -0
  39. package/dist/services/consent-audit.service.js +243 -0
  40. package/dist/services/consent-audit.service.js.map +1 -0
  41. package/dist/services/consent.service.d.ts +43 -0
  42. package/dist/services/consent.service.d.ts.map +1 -1
  43. package/dist/services/consent.service.js +1420 -20
  44. package/dist/services/consent.service.js.map +1 -1
  45. package/dist/services/proof.service.d.ts +5 -3
  46. package/dist/services/proof.service.d.ts.map +1 -1
  47. package/dist/services/proof.service.js +19 -6
  48. package/dist/services/proof.service.js.map +1 -1
  49. package/dist/types.d.ts +28 -0
  50. package/dist/types.d.ts.map +1 -1
  51. package/package.json +13 -9
package/dist/adapter.js CHANGED
@@ -13,6 +13,7 @@ import { WELL_KNOWN_CORS_HEADERS, MCP_CORS_HEADERS, PREFLIGHT_CORS_HEADERS, } fr
13
13
  import { KVProofArchive } from "./storage/kv-proof-archive";
14
14
  import { STORAGE_KEYS } from "./constants/storage-keys";
15
15
  import { DEFAULT_SESSION_CACHE_TTL } from "./constants";
16
+ import { normalizeCloudflareEnv } from "./helpers/env-mapper";
16
17
  const INITIALIZE_CONTEXT_TTL_MS = 60_000;
17
18
  /**
18
19
  * Lightweight MCP protocol implementation for Cloudflare Workers
@@ -77,81 +78,204 @@ class CloudflareMCPServer {
77
78
  }
78
79
  // Call tool with identity/proof wrapping
79
80
  if (method === "tools/call") {
81
+ // Entry point logging for debugging
82
+ console.log("🔵 [CloudflareMCPServer] handleRequest: tools/call", {
83
+ toolName: params.name,
84
+ hasParams: !!params,
85
+ paramsKeys: params ? Object.keys(params) : [],
86
+ });
80
87
  const toolName = params.name;
81
88
  const tool = this.tools.get(toolName);
82
89
  if (!tool) {
83
90
  throw new Error(`Tool not found: ${toolName}`);
84
91
  }
85
92
  // Get current session if available (stateful environments like Node.js)
86
- let session = await this.runtime.getCurrentSession();
93
+ // Wrap in try-catch to catch any exceptions
94
+ let session = null;
95
+ try {
96
+ session = await this.runtime.getCurrentSession();
97
+ console.log("[CloudflareMCPServer] Session check:", {
98
+ hasSession: !!session,
99
+ sessionId: session?.id?.slice(0, 20) + "..." || "none",
100
+ hasDelegationToken: !!session?.delegationToken,
101
+ hasDelegationStorage: !!this.delegationStorage,
102
+ method: "tools/call",
103
+ toolName,
104
+ });
105
+ }
106
+ catch (error) {
107
+ console.error("[CloudflareMCPServer] Error in getCurrentSession:", error);
108
+ // Continue with null session (will create ephemeral)
109
+ }
87
110
  // For stateless environments (Cloudflare Workers), create ephemeral session
88
111
  if (!session) {
112
+ // Extract session_id from params if available (from consent flow)
113
+ // This allows us to retrieve delegation tokens stored with the original session_id
114
+ const providedSessionId = params.session_id || params.sessionId;
89
115
  const timestamp = Date.now();
90
116
  const randomSuffix = Math.random().toString(36).substring(2, 10);
91
117
  const agentDid = (await this.runtime.getIdentity()).did;
92
- const sessionId = `ephemeral-${timestamp}-${randomSuffix}`;
118
+ // Use provided session_id if available, otherwise create ephemeral
119
+ const sessionId = providedSessionId || `ephemeral-${timestamp}-${randomSuffix}`;
120
+ console.log("[CloudflareMCPServer] Creating session:", {
121
+ sessionId: sessionId.slice(0, 20) + "...",
122
+ isProvided: !!providedSessionId,
123
+ isEphemeral: !providedSessionId,
124
+ });
93
125
  // Check KV storage for stored delegation token if not provided in params
94
126
  let delegationToken = params.delegationToken;
95
127
  if (!delegationToken && this.delegationStorage) {
96
128
  try {
97
- // Get userDID from params or session if available
98
- // TODO: Properly map credentials to userDID for multi-user scenarios
99
- // Currently, we use agent-scoped keys which can cause conflicts when multiple
100
- // users delegate to the same agent. The ideal key structure should be:
101
- // - `user:${userDid}:agent:${agentDid}:delegation` (user+agent scoped - most specific)
102
- // - `agent:${agentDid}:delegation` (agent-scoped fallback for single-user agents)
103
- //
104
- // To implement this properly:
105
- // 1. Capture userDID in consent approval flow (from OAuth or session)
106
- // 2. Store tokens with userDID in the key: `user:${userDid}:agent:${agentDid}:delegation`
107
- // 3. Retrieve tokens by userDID + agentDID combination
108
- // 4. Fall back to agent-scoped key for backward compatibility
109
- const userDid = params.clientDid || params.userDid; // Try to get userDID from request
110
- // Try session-scoped token first (if session ID was persisted)
111
- // Note: Sessions are ephemeral in Cloudflare Workers, so this is mainly
112
- // useful for caching tokens within a single request chain
113
- if (sessionId) {
114
- const sessionToken = await this.delegationStorage.get(`session:${sessionId}`);
115
- if (sessionToken) {
116
- delegationToken = sessionToken;
129
+ // Get userDID from params first
130
+ let userDid = params.clientDid || params.userDid;
131
+ // FIX: If userDid not in params, try to retrieve from session cache
132
+ if (!userDid && sessionId) {
133
+ try {
134
+ const sessionKey = STORAGE_KEYS.session(sessionId);
135
+ const sessionData = (await this.delegationStorage.get(sessionKey, "json"));
136
+ if (sessionData?.userDid) {
137
+ userDid = sessionData.userDid;
138
+ console.log("[CloudflareMCPServer] Retrieved userDid from session cache:", {
139
+ userDid: userDid.slice(0, 20) + "...",
140
+ sessionId: sessionId.slice(0, 20) + "...",
141
+ });
142
+ }
143
+ }
144
+ catch (error) {
145
+ console.warn("[CloudflareMCPServer] Failed to get userDid from session:", error);
146
+ // Non-fatal - continue without userDid
117
147
  }
118
148
  }
119
- // Try user+agent scoped token (if userDID is available)
149
+ console.log("[CloudflareMCPServer] 🔍 Starting delegation token lookup from KV:", {
150
+ agentDid: agentDid.slice(0, 20) + "...",
151
+ userDid: userDid?.slice(0, 20) + "..." || "none",
152
+ sessionId: sessionId?.slice(0, 20) + "..." || "none",
153
+ hasDelegationStorage: !!this.delegationStorage,
154
+ });
155
+ // PRIORITY 1: Try agent-scoped token FIRST (stable across sessions)
156
+ // This is the most reliable lookup since sessions are ephemeral in Cloudflare Workers
157
+ // The consent service stores tokens with this key format
158
+ const agentKey = STORAGE_KEYS.legacyDelegation(agentDid);
159
+ console.log("[CloudflareMCPServer] 🔍 PRIORITY 1: Checking agent-scoped key:", {
160
+ key: agentKey,
161
+ agentDid: agentDid.slice(0, 20) + "...",
162
+ });
163
+ const agentToken = await this.delegationStorage.get(agentKey, "text");
164
+ console.log("[CloudflareMCPServer] 🔍 PRIORITY 1: Agent-scoped lookup result:", {
165
+ key: agentKey,
166
+ found: !!agentToken,
167
+ tokenLength: agentToken?.length || 0,
168
+ tokenPreview: agentToken ? agentToken.substring(0, 20) + "..." : null,
169
+ });
170
+ if (agentToken) {
171
+ delegationToken = agentToken;
172
+ console.log("[CloudflareMCPServer] ✅ Delegation token retrieved from agent-scoped key:", {
173
+ agentDid: agentDid.slice(0, 20) + "...",
174
+ key: agentKey,
175
+ tokenLength: agentToken.length,
176
+ });
177
+ // Cache it for this session for faster future lookups
178
+ // Store full session data object to match consent service format
179
+ if (sessionId) {
180
+ const sessionKey = STORAGE_KEYS.session(sessionId);
181
+ // Read existing session data to preserve userDid and other fields
182
+ const existingSession = (await this.delegationStorage.get(sessionKey, "json"));
183
+ await this.delegationStorage.put(sessionKey, JSON.stringify({
184
+ ...existingSession,
185
+ userDid,
186
+ agentDid,
187
+ delegationToken: agentToken,
188
+ cachedAt: Date.now(),
189
+ }), {
190
+ expirationTtl: 1800, // 30 minutes
191
+ });
192
+ }
193
+ }
194
+ // PRIORITY 2: Try user+agent scoped token (if userDID is available)
120
195
  // This is the preferred approach for multi-user scenarios
121
196
  if (!delegationToken && userDid) {
122
- const userAgentKey = `user:${userDid}:agent:${agentDid}:delegation`;
123
- const userAgentToken = await this.delegationStorage.get(userAgentKey);
197
+ const userAgentKey = STORAGE_KEYS.delegation(userDid, agentDid);
198
+ console.log("[CloudflareMCPServer] 🔍 PRIORITY 2: Checking user+agent scoped key:", {
199
+ key: userAgentKey,
200
+ userDid: userDid.slice(0, 20) + "...",
201
+ agentDid: agentDid.slice(0, 20) + "...",
202
+ });
203
+ const userAgentToken = await this.delegationStorage.get(userAgentKey, "text");
204
+ console.log("[CloudflareMCPServer] 🔍 PRIORITY 2: User+agent scoped lookup result:", {
205
+ key: userAgentKey,
206
+ found: !!userAgentToken,
207
+ tokenLength: userAgentToken?.length || 0,
208
+ tokenPreview: userAgentToken ? userAgentToken.substring(0, 20) + "..." : null,
209
+ });
124
210
  if (userAgentToken) {
125
211
  delegationToken = userAgentToken;
212
+ console.log("[CloudflareMCPServer] ✅ Delegation token retrieved from user+agent scoped key:", {
213
+ userDid: userDid.slice(0, 20) + "...",
214
+ agentDid: agentDid.slice(0, 20) + "...",
215
+ key: userAgentKey,
216
+ tokenLength: userAgentToken.length,
217
+ });
126
218
  // Cache it for this session for faster future lookups
219
+ // Store full session data object to match consent service format
127
220
  if (sessionId) {
128
- await this.delegationStorage.put(`session:${sessionId}`, userAgentToken, {
221
+ const sessionKey = STORAGE_KEYS.session(sessionId);
222
+ // Read existing session data to preserve userDid and other fields
223
+ const existingSession = (await this.delegationStorage.get(sessionKey, "json"));
224
+ await this.delegationStorage.put(sessionKey, JSON.stringify({
225
+ ...existingSession,
226
+ userDid,
227
+ agentDid,
228
+ delegationToken: userAgentToken,
229
+ cachedAt: Date.now(),
230
+ }), {
129
231
  expirationTtl: 1800, // 30 minutes
130
232
  });
131
233
  }
132
234
  }
133
235
  }
134
- // Fallback to agent-scoped token (stable across sessions, but shared across users)
135
- // WARNING: This can cause conflicts when multiple users delegate to the same agent.
136
- // Each user's delegation will overwrite the previous one. This is acceptable for
137
- // single-user agents, but multi-user agents should use user+agent scoped keys above.
138
- if (!delegationToken) {
139
- const agentKey = `agent:${agentDid}:delegation`;
140
- const agentToken = await this.delegationStorage.get(agentKey);
141
- if (agentToken) {
142
- delegationToken = agentToken;
143
- // Cache it for this session for faster future lookups
144
- if (sessionId) {
145
- await this.delegationStorage.put(`session:${sessionId}`, agentToken, {
146
- expirationTtl: 1800, // 30 minutes
147
- });
148
- }
236
+ // PRIORITY 3: Try session-scoped token (if session ID was persisted)
237
+ // Note: Sessions are ephemeral in Cloudflare Workers, so this is mainly
238
+ // useful for caching tokens within a single request chain
239
+ if (!delegationToken && sessionId) {
240
+ const sessionKey = STORAGE_KEYS.session(sessionId);
241
+ console.log("[CloudflareMCPServer] 🔍 PRIORITY 3: Checking session-scoped key:", {
242
+ key: sessionKey,
243
+ sessionId: sessionId.slice(0, 20) + "...",
244
+ });
245
+ const sessionData = (await this.delegationStorage.get(sessionKey, "json"));
246
+ console.log("[CloudflareMCPServer] 🔍 PRIORITY 3: Session-scoped lookup result:", {
247
+ key: sessionKey,
248
+ found: !!sessionData,
249
+ hasDelegationToken: !!sessionData?.delegationToken,
250
+ tokenLength: sessionData?.delegationToken?.length || 0,
251
+ tokenPreview: sessionData?.delegationToken ? sessionData.delegationToken.substring(0, 20) + "..." : null,
252
+ });
253
+ if (sessionData?.delegationToken) {
254
+ delegationToken = sessionData.delegationToken;
255
+ console.log("[CloudflareMCPServer] ✅ Delegation token retrieved from session cache:", {
256
+ sessionId: sessionId.slice(0, 20) + "...",
257
+ tokenLength: delegationToken.length,
258
+ });
149
259
  }
150
260
  }
261
+ if (!delegationToken) {
262
+ console.log("[CloudflareMCPServer] ⚠️ No delegation token found in KV storage:", {
263
+ agentDid: agentDid.slice(0, 20) + "...",
264
+ userDid: userDid?.slice(0, 20) + "..." || "none",
265
+ sessionId: sessionId?.slice(0, 20) + "..." || "none",
266
+ checkedKeys: [
267
+ agentKey,
268
+ userDid
269
+ ? STORAGE_KEYS.delegation(userDid, agentDid)
270
+ : null,
271
+ sessionId ? STORAGE_KEYS.session(sessionId) : null,
272
+ ].filter(Boolean),
273
+ });
274
+ }
151
275
  }
152
276
  catch (error) {
153
277
  // Log but don't fail - delegation lookup is best-effort
154
- console.warn("[CloudflareMCPServer] Failed to retrieve delegation token from KV:", error);
278
+ console.error("[CloudflareMCPServer] Failed to retrieve delegation token from KV:", error);
155
279
  }
156
280
  }
157
281
  session = {
@@ -168,9 +292,207 @@ class CloudflareMCPServer {
168
292
  serverOrigin: this.requestOrigin,
169
293
  // These fields are for ephemeral sessions - won't persist
170
294
  };
295
+ // ✅ CRITICAL: Log delegation token status before processToolCall
296
+ console.log("[CloudflareMCPServer] ✅ Session created with delegation token status:", {
297
+ sessionId: sessionId.slice(0, 20) + "...",
298
+ hasDelegationToken: !!delegationToken,
299
+ delegationTokenLength: delegationToken?.length || 0,
300
+ delegationTokenSource: params.delegationToken ? "params" : (delegationToken ? "kv-storage" : "none"),
301
+ agentDid: agentDid.slice(0, 20) + "...",
302
+ });
171
303
  }
172
304
  else {
173
- // For existing sessions, update delegation fields if provided
305
+ // For existing sessions, check KV storage for delegation token if not already set
306
+ // This ensures newly created delegations are found even for existing sessions
307
+ console.log("[CloudflareMCPServer] Existing session detected, checking KV storage:", {
308
+ hasSession: !!session,
309
+ hasDelegationToken: !!session?.delegationToken,
310
+ hasDelegationStorage: !!this.delegationStorage,
311
+ sessionId: session?.id?.slice(0, 20) + "...",
312
+ sessionKeys: Object.keys(session || {}),
313
+ });
314
+ if (!session.delegationToken && this.delegationStorage) {
315
+ console.log("[CloudflareMCPServer] ⚠️ Session has no delegationToken, attempting KV lookup...");
316
+ try {
317
+ const agentDid = (await this.runtime.getIdentity()).did;
318
+ const userDid = params.clientDid || params.userDid || session.clientDid;
319
+ const sessionId = session.id;
320
+ console.log("[CloudflareMCPServer] 🔍 Looking up delegation token for existing session:", {
321
+ sessionId: sessionId?.slice(0, 20) + "...",
322
+ userDid: userDid?.slice(0, 20) + "...",
323
+ agentDid: agentDid?.slice(0, 20) + "...",
324
+ });
325
+ // PRIORITY 1: Try agent-scoped token FIRST (stable across sessions)
326
+ const agentKey = STORAGE_KEYS.legacyDelegation(agentDid);
327
+ console.log("[CloudflareMCPServer] 🔍 PRIORITY 1: Checking agent-scoped key:", {
328
+ key: agentKey,
329
+ agentDid: agentDid.slice(0, 20) + "...",
330
+ });
331
+ let delegationToken = await this.delegationStorage.get(agentKey, "text");
332
+ console.log("[CloudflareMCPServer] 🔍 PRIORITY 1: Agent-scoped lookup result:", {
333
+ key: agentKey,
334
+ found: !!delegationToken,
335
+ tokenLength: delegationToken?.length || 0,
336
+ });
337
+ // PRIORITY 2: Try user+agent scoped token (if userDID is available)
338
+ if (!delegationToken && userDid) {
339
+ const userAgentKey = STORAGE_KEYS.delegation(userDid, agentDid);
340
+ console.log("[CloudflareMCPServer] 🔍 PRIORITY 2: Checking user+agent scoped key:", {
341
+ key: userAgentKey,
342
+ userDid: userDid.slice(0, 20) + "...",
343
+ agentDid: agentDid.slice(0, 20) + "...",
344
+ });
345
+ const userAgentToken = await this.delegationStorage.get(userAgentKey, "text");
346
+ console.log("[CloudflareMCPServer] 🔍 PRIORITY 2: User+agent scoped lookup result:", {
347
+ key: userAgentKey,
348
+ found: !!userAgentToken,
349
+ tokenLength: userAgentToken?.length || 0,
350
+ });
351
+ if (userAgentToken) {
352
+ delegationToken = userAgentToken;
353
+ }
354
+ }
355
+ // PRIORITY 3: Try session-scoped token (if session ID was persisted)
356
+ if (!delegationToken && sessionId) {
357
+ const sessionKey = STORAGE_KEYS.session(sessionId);
358
+ console.log("[CloudflareMCPServer] 🔍 PRIORITY 3: Looking up session key:", {
359
+ sessionKey,
360
+ sessionId: sessionId.slice(0, 20) + "...",
361
+ });
362
+ try {
363
+ const sessionData = (await this.delegationStorage.get(sessionKey, "json"));
364
+ console.log("[CloudflareMCPServer] 🔍 PRIORITY 3: Session-scoped lookup result:", {
365
+ key: sessionKey,
366
+ found: !!sessionData,
367
+ hasDelegationToken: !!sessionData?.delegationToken,
368
+ tokenLength: sessionData?.delegationToken?.length || 0,
369
+ });
370
+ if (sessionData?.delegationToken) {
371
+ delegationToken = sessionData.delegationToken;
372
+ }
373
+ }
374
+ catch (error) {
375
+ console.error("[CloudflareMCPServer] Error retrieving session data:", error);
376
+ }
377
+ }
378
+ // ✅ CRITICAL: Set delegation token on session if found
379
+ if (delegationToken) {
380
+ session = {
381
+ ...session,
382
+ delegationToken,
383
+ };
384
+ console.log("[CloudflareMCPServer] ✅ Delegation token set on existing session:", {
385
+ sessionId: sessionId?.slice(0, 20) + "..." || "none",
386
+ tokenLength: delegationToken.length,
387
+ sessionHasToken: !!session.delegationToken,
388
+ source: "kv-storage",
389
+ });
390
+ }
391
+ else {
392
+ console.log("[CloudflareMCPServer] ⚠️ No delegation token found in KV storage for existing session:", {
393
+ sessionId: sessionId?.slice(0, 20) + "..." || "none",
394
+ checkedKeys: [
395
+ agentKey,
396
+ userDid ? STORAGE_KEYS.delegation(userDid, agentDid) : null,
397
+ sessionId ? STORAGE_KEYS.session(sessionId) : null,
398
+ ].filter(Boolean),
399
+ });
400
+ }
401
+ // PRIORITY 2: Try user+agent scoped token (if userDID is available)
402
+ if (!session.delegationToken && userDid) {
403
+ const userAgentKey = STORAGE_KEYS.delegation(userDid, agentDid);
404
+ console.log("[CloudflareMCPServer] PRIORITY 2: Looking up user+agent key:", {
405
+ userAgentKey,
406
+ userDid: userDid.slice(0, 20) + "...",
407
+ agentDid: agentDid.slice(0, 20) + "...",
408
+ });
409
+ try {
410
+ const userAgentToken = await this.delegationStorage.get(userAgentKey);
411
+ if (userAgentToken) {
412
+ session = {
413
+ ...session,
414
+ delegationToken: userAgentToken,
415
+ };
416
+ console.log("[CloudflareMCPServer] ✅ Delegation token retrieved from user+agent scoped key (existing session):", {
417
+ userDid: userDid.slice(0, 20) + "...",
418
+ agentDid: agentDid.slice(0, 20) + "...",
419
+ key: userAgentKey,
420
+ tokenLength: userAgentToken.length,
421
+ });
422
+ }
423
+ else {
424
+ console.log("[CloudflareMCPServer] ⚠️ No token found at user+agent key");
425
+ }
426
+ }
427
+ catch (error) {
428
+ console.error("[CloudflareMCPServer] Error retrieving user+agent token:", error);
429
+ }
430
+ }
431
+ else {
432
+ console.log("[CloudflareMCPServer] ⚠️ Skipping PRIORITY 2:", {
433
+ hasToken: !!session.delegationToken,
434
+ hasUserDid: !!userDid,
435
+ });
436
+ }
437
+ // PRIORITY 3: Try agent-scoped token (fallback)
438
+ if (!session.delegationToken) {
439
+ const agentKey = STORAGE_KEYS.legacyDelegation(agentDid);
440
+ console.log("[CloudflareMCPServer] PRIORITY 3: Looking up agent key:", {
441
+ agentKey,
442
+ agentDid: agentDid.slice(0, 20) + "...",
443
+ });
444
+ try {
445
+ const agentToken = await this.delegationStorage.get(agentKey);
446
+ if (agentToken) {
447
+ session = {
448
+ ...session,
449
+ delegationToken: agentToken,
450
+ };
451
+ console.log("[CloudflareMCPServer] ✅ Delegation token retrieved from agent-scoped key (existing session):", {
452
+ agentDid: agentDid.slice(0, 20) + "...",
453
+ key: agentKey,
454
+ tokenLength: agentToken.length,
455
+ });
456
+ }
457
+ else {
458
+ console.log("[CloudflareMCPServer] ⚠️ No token found at agent key");
459
+ }
460
+ }
461
+ catch (error) {
462
+ console.error("[CloudflareMCPServer] Error retrieving agent token:", error);
463
+ }
464
+ }
465
+ else {
466
+ console.log("[CloudflareMCPServer] ✅ Already have token, skipping PRIORITY 3");
467
+ }
468
+ // Final check - log if we still don't have a token
469
+ if (!session.delegationToken) {
470
+ console.log("[CloudflareMCPServer] ❌ FINAL CHECK: Still no delegation token after all KV lookups:", {
471
+ sessionId: session?.id?.slice(0, 20) + "..." || "none",
472
+ userDid: userDid?.slice(0, 20) + "..." || "none",
473
+ agentDid: agentDid.slice(0, 20) + "...",
474
+ checkedKeys: [
475
+ sessionId ? STORAGE_KEYS.session(sessionId) : null,
476
+ userDid
477
+ ? STORAGE_KEYS.delegation(userDid, agentDid)
478
+ : null,
479
+ STORAGE_KEYS.legacyDelegation(agentDid),
480
+ ].filter(Boolean),
481
+ });
482
+ }
483
+ else {
484
+ console.log("[CloudflareMCPServer] ✅ FINAL CHECK: Delegation token found:", {
485
+ tokenLength: session.delegationToken.length,
486
+ sessionHasToken: !!session.delegationToken,
487
+ });
488
+ }
489
+ }
490
+ catch (error) {
491
+ // Log but don't fail - delegation lookup is best-effort
492
+ console.error("[CloudflareMCPServer] Failed to retrieve delegation token from KV (existing session):", error);
493
+ }
494
+ }
495
+ // Update delegation fields if provided in params (takes precedence)
174
496
  if (params.delegationToken) {
175
497
  session.delegationToken = params.delegationToken;
176
498
  }
@@ -202,6 +524,34 @@ class CloudflareMCPServer {
202
524
  session.toolName = toolName;
203
525
  session.toolParams = params.arguments || {};
204
526
  session.scopeId = scopeId; // ✅ ADDED: Pass scopeId for tool auto-discovery
527
+ // ✅ CRITICAL: Verify delegation token is set on session before processToolCall
528
+ // Also do a final KV lookup if token is still missing (defensive check)
529
+ if (!session?.delegationToken && this.delegationStorage) {
530
+ try {
531
+ const agentDid = (await this.runtime.getIdentity()).did;
532
+ const agentKey = STORAGE_KEYS.legacyDelegation(agentDid);
533
+ const finalToken = await this.delegationStorage.get(agentKey, "text");
534
+ if (finalToken) {
535
+ session.delegationToken = finalToken;
536
+ console.log("[CloudflareMCPServer] ✅ Final KV lookup succeeded, token set on session:", {
537
+ toolName,
538
+ agentDid: agentDid.slice(0, 20) + "...",
539
+ tokenLength: finalToken.length,
540
+ });
541
+ }
542
+ }
543
+ catch (error) {
544
+ console.error("[CloudflareMCPServer] Final KV lookup failed:", error);
545
+ }
546
+ }
547
+ console.log("[CloudflareMCPServer] ✅ About to call processToolCall with session:", {
548
+ toolName,
549
+ sessionId: session?.id?.slice(0, 20) + "..." || "none",
550
+ hasDelegationToken: !!session?.delegationToken,
551
+ delegationTokenLength: session?.delegationToken?.length || 0,
552
+ hasConsentProof: !!session?.consentProof,
553
+ agentDid: session?.agentDid?.slice(0, 20) + "..." || "none",
554
+ });
205
555
  // ✅ Use processToolCall which handles delegation checks AND proof generation
206
556
  // This ensures delegation is checked BEFORE tool execution
207
557
  // If delegation is required but not provided, this will throw DelegationRequiredError
@@ -441,8 +791,7 @@ class CloudflareMCPServer {
441
791
  const name = typeof nameValue === "string" && nameValue.trim().length > 0
442
792
  ? nameValue.trim()
443
793
  : "unknown";
444
- const clientId = handshakeClientId ||
445
- crypto.randomUUID();
794
+ const clientId = handshakeClientId || crypto.randomUUID();
446
795
  const capabilities = this.isRecord(request.clientCapabilities)
447
796
  ? request.clientCapabilities
448
797
  : initializeContext?.capabilities;
@@ -557,25 +906,33 @@ function buildRequestMeta(request) {
557
906
  *
558
907
  * Supports SSE (Server-Sent Events) and HTTP JSON-RPC transports for compatibility
559
908
  * with Claude Desktop, Cursor, MCP Inspector, and other MCP clients.
909
+ *
910
+ * Automatically handles prefixed KV bindings via `envPrefix` parameter or auto-detection.
560
911
  */
561
912
  export function createMCPICloudflareAdapter(config) {
562
- // Create the runtime with Cloudflare providers
563
- const runtime = createCloudflareRuntime(config);
913
+ // Normalize environment to handle prefixed KV bindings
914
+ // This ensures consistent KV access regardless of prefix usage
915
+ const mappedEnv = normalizeCloudflareEnv(config.env, config.envPrefix);
916
+ // Create the runtime with normalized environment
917
+ const runtime = createCloudflareRuntime({
918
+ ...config,
919
+ env: mappedEnv,
920
+ });
564
921
  // Server info
565
922
  const serverInfo = config.serverInfo || {
566
923
  name: "MCP-I Cloudflare Server",
567
924
  version: "1.0.0",
568
925
  };
569
926
  // Initialize proof archive if PROOF_ARCHIVE KV is available
570
- const env = config.env;
571
- const proofArchive = env.PROOF_ARCHIVE
572
- ? new KVProofArchive(env.PROOF_ARCHIVE)
927
+ const proofArchive = mappedEnv.PROOF_ARCHIVE
928
+ ? new KVProofArchive(mappedEnv.PROOF_ARCHIVE)
573
929
  : undefined;
574
- // Get delegation storage from env if available
575
- const delegationStorage = env.DELEGATION_STORAGE;
930
+ // Get delegation storage from normalized env if available
931
+ const delegationStorage = mappedEnv.DELEGATION_STORAGE;
576
932
  // Create lightweight MCP server
577
933
  const server = new CloudflareMCPServer(runtime, serverInfo, config.tools || [], proofArchive, delegationStorage);
578
934
  // Return fetch handler
935
+ // Note: mappedEnv is captured in closure for admin endpoints
579
936
  return {
580
937
  server,
581
938
  runtime,
@@ -766,15 +1123,16 @@ export function createMCPICloudflareAdapter(config) {
766
1123
  headers: { "Content-Type": "application/json" },
767
1124
  });
768
1125
  }
769
- const env = config.env;
1126
+ // Use normalized environment (handles prefixed KV bindings)
1127
+ // mappedEnv is already normalized in createMCPICloudflareAdapter
770
1128
  // GET /admin/nonces - List active nonces
771
1129
  if (url.pathname === "/admin/nonces") {
772
1130
  try {
773
1131
  // Use KV list to get nonce keys
774
- const noncesList = await env.NONCE_CACHE.list({ prefix: "nonce:" });
1132
+ const noncesList = await mappedEnv.NONCE_CACHE.list({ prefix: "nonce:" });
775
1133
  const nonces = [];
776
1134
  for (const key of noncesList.keys) {
777
- const value = await env.NONCE_CACHE.get(key.name);
1135
+ const value = await mappedEnv.NONCE_CACHE.get(key.name);
778
1136
  if (value) {
779
1137
  nonces.push({
780
1138
  nonce: key.name.replace("nonce:", ""),
@@ -800,8 +1158,8 @@ export function createMCPICloudflareAdapter(config) {
800
1158
  }
801
1159
  }
802
1160
  // Initialize proof archive if available
803
- const proofArchive = env.PROOF_ARCHIVE
804
- ? new KVProofArchive(env.PROOF_ARCHIVE)
1161
+ const proofArchive = mappedEnv.PROOF_ARCHIVE
1162
+ ? new KVProofArchive(mappedEnv.PROOF_ARCHIVE)
805
1163
  : null;
806
1164
  if (!proofArchive) {
807
1165
  return new Response(JSON.stringify({
@@ -874,7 +1232,7 @@ export function createMCPICloudflareAdapter(config) {
874
1232
  if (url.pathname === "/admin/stats") {
875
1233
  try {
876
1234
  const [noncesList, proofStats] = await Promise.all([
877
- env.NONCE_CACHE.list({ prefix: "nonce:" }),
1235
+ mappedEnv.NONCE_CACHE.list({ prefix: "nonce:" }),
878
1236
  proofArchive.getStats(),
879
1237
  ]);
880
1238
  return new Response(JSON.stringify({