@kya-os/mcp-i-cloudflare 1.9.7 → 1.10.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 (121) hide show
  1. package/dist/adapter.d.ts.map +1 -1
  2. package/dist/adapter.js +47 -16
  3. package/dist/adapter.js.map +1 -1
  4. package/dist/agent.d.ts.map +1 -1
  5. package/dist/agent.js +181 -50
  6. package/dist/agent.js.map +1 -1
  7. package/dist/app.d.ts.map +1 -1
  8. package/dist/app.js +86 -3
  9. package/dist/app.js.map +1 -1
  10. package/dist/config.d.ts +54 -0
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +58 -0
  13. package/dist/config.js.map +1 -1
  14. package/dist/constants/storage-keys.d.ts +6 -0
  15. package/dist/constants/storage-keys.d.ts.map +1 -1
  16. package/dist/constants/storage-keys.js +6 -0
  17. package/dist/constants/storage-keys.js.map +1 -1
  18. package/dist/delegation-http/anchor-do.d.ts +84 -0
  19. package/dist/delegation-http/anchor-do.d.ts.map +1 -0
  20. package/dist/delegation-http/anchor-do.js +253 -0
  21. package/dist/delegation-http/anchor-do.js.map +1 -0
  22. package/dist/delegation-http/anchor-routing.d.ts +20 -0
  23. package/dist/delegation-http/anchor-routing.d.ts.map +1 -0
  24. package/dist/delegation-http/anchor-routing.js +27 -0
  25. package/dist/delegation-http/anchor-routing.js.map +1 -0
  26. package/dist/delegation-http/bearer-retry.d.ts +54 -0
  27. package/dist/delegation-http/bearer-retry.d.ts.map +1 -0
  28. package/dist/delegation-http/bearer-retry.js +195 -0
  29. package/dist/delegation-http/bearer-retry.js.map +1 -0
  30. package/dist/delegation-http/challenge-middleware.d.ts +28 -0
  31. package/dist/delegation-http/challenge-middleware.d.ts.map +1 -0
  32. package/dist/delegation-http/challenge-middleware.js +190 -0
  33. package/dist/delegation-http/challenge-middleware.js.map +1 -0
  34. package/dist/delegation-http/challenge.d.ts +86 -0
  35. package/dist/delegation-http/challenge.d.ts.map +1 -0
  36. package/dist/delegation-http/challenge.js +173 -0
  37. package/dist/delegation-http/challenge.js.map +1 -0
  38. package/dist/delegation-http/consent-anchor-bridge.d.ts +35 -0
  39. package/dist/delegation-http/consent-anchor-bridge.d.ts.map +1 -0
  40. package/dist/delegation-http/consent-anchor-bridge.js +355 -0
  41. package/dist/delegation-http/consent-anchor-bridge.js.map +1 -0
  42. package/dist/delegation-http/constants.d.ts +51 -0
  43. package/dist/delegation-http/constants.d.ts.map +1 -0
  44. package/dist/delegation-http/constants.js +60 -0
  45. package/dist/delegation-http/constants.js.map +1 -0
  46. package/dist/delegation-http/hash.d.ts +21 -0
  47. package/dist/delegation-http/hash.d.ts.map +1 -0
  48. package/dist/delegation-http/hash.js +92 -0
  49. package/dist/delegation-http/hash.js.map +1 -0
  50. package/dist/delegation-http/pickup-routes.d.ts +42 -0
  51. package/dist/delegation-http/pickup-routes.d.ts.map +1 -0
  52. package/dist/delegation-http/pickup-routes.js +79 -0
  53. package/dist/delegation-http/pickup-routes.js.map +1 -0
  54. package/dist/delegation-http/register.d.ts +19 -0
  55. package/dist/delegation-http/register.d.ts.map +1 -0
  56. package/dist/delegation-http/register.js +72 -0
  57. package/dist/delegation-http/register.js.map +1 -0
  58. package/dist/delegation-http/sid.d.ts +20 -0
  59. package/dist/delegation-http/sid.d.ts.map +1 -0
  60. package/dist/delegation-http/sid.js +32 -0
  61. package/dist/delegation-http/sid.js.map +1 -0
  62. package/dist/delegation-http/types.d.ts +64 -0
  63. package/dist/delegation-http/types.d.ts.map +1 -0
  64. package/dist/delegation-http/types.js +11 -0
  65. package/dist/delegation-http/types.js.map +1 -0
  66. package/dist/helpers/env-mapper.d.ts.map +1 -1
  67. package/dist/helpers/env-mapper.js +24 -0
  68. package/dist/helpers/env-mapper.js.map +1 -1
  69. package/dist/index.d.ts +5 -2
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +10 -2
  72. package/dist/index.js.map +1 -1
  73. package/dist/providers/crypto.d.ts +1 -1
  74. package/dist/providers/crypto.d.ts.map +1 -1
  75. package/dist/providers/crypto.js +1 -1
  76. package/dist/providers/crypto.js.map +1 -1
  77. package/dist/providers/kv-identity.d.ts +2 -2
  78. package/dist/providers/kv-identity.d.ts.map +1 -1
  79. package/dist/providers/kv-identity.js +1 -1
  80. package/dist/providers/kv-identity.js.map +1 -1
  81. package/dist/providers/storage.d.ts +1 -1
  82. package/dist/providers/storage.d.ts.map +1 -1
  83. package/dist/providers/storage.js +1 -1
  84. package/dist/providers/storage.js.map +1 -1
  85. package/dist/runtime/audit-logger.d.ts +1 -1
  86. package/dist/runtime/audit-logger.d.ts.map +1 -1
  87. package/dist/runtime/oauth-handler.d.ts.map +1 -1
  88. package/dist/runtime/oauth-handler.js +31 -3
  89. package/dist/runtime/oauth-handler.js.map +1 -1
  90. package/dist/runtime/oidc/authorize-url.d.ts +19 -0
  91. package/dist/runtime/oidc/authorize-url.d.ts.map +1 -0
  92. package/dist/runtime/oidc/authorize-url.js +94 -0
  93. package/dist/runtime/oidc/authorize-url.js.map +1 -0
  94. package/dist/runtime/oidc/token-exchange.d.ts +62 -0
  95. package/dist/runtime/oidc/token-exchange.d.ts.map +1 -0
  96. package/dist/runtime/oidc/token-exchange.js +75 -0
  97. package/dist/runtime/oidc/token-exchange.js.map +1 -0
  98. package/dist/runtime.d.ts +1 -1
  99. package/dist/runtime.d.ts.map +1 -1
  100. package/dist/services/consent-audit.service.d.ts +1 -1
  101. package/dist/services/consent-audit.service.d.ts.map +1 -1
  102. package/dist/services/consent-audit.service.js.map +1 -1
  103. package/dist/services/consent.service.d.ts +2 -2
  104. package/dist/services/consent.service.d.ts.map +1 -1
  105. package/dist/services/consent.service.js +39 -50
  106. package/dist/services/consent.service.js.map +1 -1
  107. package/dist/services/delegation.service.d.ts +9 -0
  108. package/dist/services/delegation.service.d.ts.map +1 -1
  109. package/dist/services/delegation.service.js +21 -7
  110. package/dist/services/delegation.service.js.map +1 -1
  111. package/dist/services/oauth-security.service.d.ts.map +1 -1
  112. package/dist/services/oauth-security.service.js +6 -10
  113. package/dist/services/oauth-security.service.js.map +1 -1
  114. package/dist/services/provider-loader.service.js +1 -1
  115. package/dist/services/provider-loader.service.js.map +1 -1
  116. package/dist/types.d.ts +5 -1
  117. package/dist/types.d.ts.map +1 -1
  118. package/dist/utils/known-clients.d.ts.map +1 -1
  119. package/dist/utils/known-clients.js +19 -10
  120. package/dist/utils/known-clients.js.map +1 -1
  121. package/package.json +3 -1
package/dist/agent.js CHANGED
@@ -10,10 +10,12 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
10
  import { CloudflareRuntime } from "./runtime";
11
11
  import { createCloudflareRuntime } from "./index";
12
12
  import { mapPrefixedEnv } from "./helpers/env-mapper";
13
+ import { handlePickupInternal } from "./delegation-http/anchor-do";
13
14
  import { STORAGE_KEYS } from "./constants/storage-keys";
14
15
  import { DEFAULT_SESSION_TTL } from "./constants";
15
16
  import { z } from "zod";
16
- import { OAuthService, IdpTokenResolver, ToolContextBuilder, OAuthRequiredError, DelegationRequiredError, DelegationErrorFormatter, SessionRegistrationService, generateDidKeyFromBase64, logger, } from "@kya-os/mcp-i-core";
17
+ import { generateDidKeyFromBase64, logger } from "@kya-os/mcp";
18
+ import { OAuthService, IdpTokenResolver, ToolContextBuilder, OAuthRequiredError, DelegationRequiredError, DelegationErrorFormatter, SessionRegistrationService, SESSION_ID_PREFIX, recordWireForm, generateResumeToken, } from "@kya-os/mcp-i-core"; // product layer — stays (C2/C3)
17
19
  import { WebCryptoProvider } from "./providers/crypto";
18
20
  // CRITICAL: Import the OAuth service registry - this triggers globalThis registration
19
21
  // at module load time, preventing esbuild from tree-shaking the OAuth services
@@ -24,11 +26,11 @@ import { OAuthSecurityService } from "./services/oauth-security.service";
24
26
  import { WorkersFetchProvider } from "./providers/storage";
25
27
  import { resolveClientIdentity, } from "./services/kta-client-lookup";
26
28
  import { findKnownClient } from "./utils/known-clients";
27
- import { shouldUsePopup, buildPopupResource, } from "@kya-os/consent";
28
- import { didKeyFragment } from "@kya-os/mcp-i-core/utils/did-helpers";
29
+ import { shouldUsePopup, buildPopupResource } from "@kya-os/consent";
30
+ import { didKeyFragment } from "@kya-os/mcp";
29
31
  import { calculateDOInstanceId, parseDORoutingStrategy, parseDOShardCount, } from "./utils/do-routing";
30
32
  import { defaultProviderRegistry, } from "@kya-os/provider-registry";
31
- import { hasApiKeyAuthorization, AuthRequiredError } from "@kya-os/contracts/tool-protection";
33
+ import { hasApiKeyAuthorization, AuthRequiredError, } from "@kya-os/contracts/tool-protection";
32
34
  import { VaultResolver } from "./services/vault-resolver";
33
35
  // CRITICAL: Force OAuth registry evaluation at module load time
34
36
  // This creates an observable side effect that esbuild cannot tree-shake
@@ -184,7 +186,9 @@ export class MCPICloudflareAgent extends McpAgent {
184
186
  hasCapabilities: !!capabilities,
185
187
  capabilityKeys: capabilities ? Object.keys(capabilities) : [],
186
188
  hasExtensions: !!capabilities?.extensions,
187
- extensionKeys: capabilities?.extensions ? Object.keys(capabilities.extensions) : [],
189
+ extensionKeys: capabilities?.extensions
190
+ ? Object.keys(capabilities.extensions)
191
+ : [],
188
192
  rawCapabilities: JSON.stringify(capabilities)?.substring(0, 500),
189
193
  });
190
194
  // Store client info for later retrieval (always, not just in dev)
@@ -244,7 +248,10 @@ export class MCPICloudflareAgent extends McpAgent {
244
248
  logger.info(`║ Client DID: ${clientIdentity.did.slice(0, 40).padEnd(42)}║`);
245
249
  logger.info(`║ DID Source: ${clientIdentity.source.padEnd(42)}║`);
246
250
  logger.info("╠════════════════════════════════════════════════════════╣");
247
- logger.info(`║ Your Agent: ${isAgentRegistered ? "✓ REGISTERED" : "○ LOCAL"} ${agentDid.slice(0, 33).padEnd(36)}║`);
251
+ const agentStatus = isAgentRegistered ? "✓ REGISTERED" : "○ LOCAL";
252
+ const agentPrefix = ` Your Agent: ${agentStatus} `;
253
+ const agentPadWidth = 56 - agentPrefix.length;
254
+ logger.info(`║${agentPrefix}${agentDid.slice(0, agentPadWidth).padEnd(agentPadWidth)}║`);
248
255
  logger.info("╚════════════════════════════════════════════════════════╝");
249
256
  logger.info("");
250
257
  }
@@ -382,10 +389,10 @@ export class MCPICloudflareAgent extends McpAgent {
382
389
  : this.env;
383
390
  // ✅ CRITICAL: Use same session ID resolution logic as executeToolWithProof
384
391
  // This ensures session registration uses the same ID that proofs will use
385
- // Priority: getSessionId() (from agents SDK) > DO ID fallback > mcpi_{uuid}
392
+ // Priority: getSessionId() (from agents SDK) > DO ID fallback > kyaos_{uuid}
386
393
  const mcpSessionId = this.getSessionId();
387
394
  const doId = this.ctx.id.toString();
388
- const sessionId = mcpSessionId || doId || `mcpi_${crypto.randomUUID()}`;
395
+ const sessionId = mcpSessionId || doId || `${SESSION_ID_PREFIX}${crypto.randomUUID()}`;
389
396
  // Log session ID source for debugging (matches executeToolWithProof pattern)
390
397
  if (this._environment === "development") {
391
398
  logger.debug("[SessionRegistration] Session ID resolved:", {
@@ -560,7 +567,7 @@ export class MCPICloudflareAgent extends McpAgent {
560
567
  const [extApps, consentMcpApp, extAppsConstants] = await Promise.all([
561
568
  import("@modelcontextprotocol/ext-apps/server").catch(() => null),
562
569
  import("@kya-os/consent/mcp-app").catch(() => null),
563
- import("@kya-os/mcp-i-core/runtime/ext-apps-constants").catch(() => null),
570
+ import("@kya-os/mcp-i-runtime/runtime/ext-apps-constants").catch(() => null),
564
571
  ]);
565
572
  if (!extApps || !consentMcpApp || !extAppsConstants) {
566
573
  logger.info("[MCPICloudflareAgent] MCP Apps consent UI not available (missing optional dependencies)");
@@ -809,7 +816,7 @@ export class MCPICloudflareAgent extends McpAgent {
809
816
  send({
810
817
  jsonrpc: "2.0", id: reqId, method: "tools/call",
811
818
  params: {
812
- name: "_mcpi_credential_auth",
819
+ name: "_kyaos_credential_auth",
813
820
  arguments: {
814
821
  tool: consentData.tool,
815
822
  scopes: consentData.scopes,
@@ -827,7 +834,7 @@ export class MCPICloudflareAgent extends McpAgent {
827
834
  send({
828
835
  jsonrpc: "2.0", id: reqId, method: "tools/call",
829
836
  params: {
830
- name: "_mcpi_approve_consent",
837
+ name: "_kyaos_approve_consent",
831
838
  arguments: {
832
839
  tool: consentData.tool,
833
840
  scopes: consentData.scopes,
@@ -915,14 +922,21 @@ export class MCPICloudflareAgent extends McpAgent {
915
922
  // The sandboxed MCP Apps iframe cannot call fetch() directly, so it
916
923
  // uses tools/call (proxied via postMessage through the host) to invoke
917
924
  // this tool which handles the /consent/approve logic server-side.
918
- this.server.tool("_mcpi_approve_consent", "DO NOT call this tool directly. Internal system tool used by the consent UI iframe to process approval actions. Only the embedded consent form should invoke this.", {
925
+ // C4 (#2918) dual-accept: register the consent-approval tool under the
926
+ // canonical `_kyaos_` name and keep the legacy `_mcpi_` name as a thin
927
+ // alias during the window. The server-rendered iframe already calls the
928
+ // new name; the alias is defensive for an in-flight pre-deploy iframe.
929
+ // Same handler, two names — DIF aliases `_kyaos_handshake` the same way.
930
+ const approveConsentDescription = "DO NOT call this tool directly. Internal system tool used by the consent UI iframe to process approval actions. Only the embedded consent form should invoke this.";
931
+ const approveConsentSchema = {
919
932
  tool: z.string(),
920
933
  scopes: z.array(z.string()),
921
934
  session_id: z.string(),
922
935
  agent_did: z.string(),
923
936
  project_id: z.string(),
924
937
  resume_token: z.string(),
925
- }, async (args) => {
938
+ };
939
+ const approveConsentHandler = async (args) => {
926
940
  const envPrefix = this.getEnvPrefix();
927
941
  const mappedEnv = envPrefix
928
942
  ? mapPrefixedEnv(this.env, envPrefix)
@@ -930,7 +944,17 @@ export class MCPICloudflareAgent extends McpAgent {
930
944
  const serverUrl = mappedEnv.MCP_SERVER_URL ??
931
945
  this._autoDetectedOrigin;
932
946
  if (!serverUrl) {
933
- return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "No server URL configured" }) }] };
947
+ return {
948
+ content: [
949
+ {
950
+ type: "text",
951
+ text: JSON.stringify({
952
+ success: false,
953
+ error: "No server URL configured",
954
+ }),
955
+ },
956
+ ],
957
+ };
934
958
  }
935
959
  try {
936
960
  const resp = await fetch(`${serverUrl}/consent/approve`, {
@@ -948,21 +972,48 @@ export class MCPICloudflareAgent extends McpAgent {
948
972
  }),
949
973
  });
950
974
  if (!resp.ok) {
951
- return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Server error: ${resp.status}` }) }] };
975
+ return {
976
+ content: [
977
+ {
978
+ type: "text",
979
+ text: JSON.stringify({
980
+ success: false,
981
+ error: `Server error: ${resp.status}`,
982
+ }),
983
+ },
984
+ ],
985
+ };
952
986
  }
953
987
  const data = await resp.json();
954
988
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
955
989
  }
956
990
  catch (err) {
957
991
  const msg = err instanceof Error ? err.message : "Unknown error";
958
- return { content: [{ type: "text", text: JSON.stringify({ success: false, error: msg }) }] };
992
+ return {
993
+ content: [
994
+ {
995
+ type: "text",
996
+ text: JSON.stringify({ success: false, error: msg }),
997
+ },
998
+ ],
999
+ };
959
1000
  }
1001
+ };
1002
+ this.server.tool("_kyaos_approve_consent", approveConsentDescription, approveConsentSchema, approveConsentHandler);
1003
+ this.server.tool("_mcpi_approve_consent", approveConsentDescription, approveConsentSchema, async (args) => {
1004
+ recordWireForm("tool_name", "old", {
1005
+ tool: "_kyaos_approve_consent",
1006
+ });
1007
+ return approveConsentHandler(args);
960
1008
  });
961
1009
  // Register internal tool for iframe-based credential authentication.
962
1010
  // Same sandbox workaround as _mcpi_approve_consent: the iframe calls
963
1011
  // tools/call via postMessage, which the host proxies to this tool.
964
1012
  // This tool does credential auth + delegation in a single step.
965
- this.server.tool("_mcpi_credential_auth", "DO NOT call this tool directly. Internal system tool used by the consent UI iframe to process credential authentication. Only the embedded consent form should invoke this.", {
1013
+ // C4 (#2918) dual-accept: same accept-both treatment as approve_consent
1014
+ // canonical `_kyaos_credential_auth` plus the legacy `_mcpi_` alias.
1015
+ const credentialAuthDescription = "DO NOT call this tool directly. Internal system tool used by the consent UI iframe to process credential authentication. Only the embedded consent form should invoke this.";
1016
+ const credentialAuthSchema = {
966
1017
  tool: z.string(),
967
1018
  scopes: z.array(z.string()),
968
1019
  session_id: z.string(),
@@ -972,7 +1023,8 @@ export class MCPICloudflareAgent extends McpAgent {
972
1023
  username: z.string(),
973
1024
  password: z.string(),
974
1025
  provider: z.string(),
975
- }, async (args) => {
1026
+ };
1027
+ const credentialAuthHandler = async (args) => {
976
1028
  const envPrefix = this.getEnvPrefix();
977
1029
  const mappedEnv = envPrefix
978
1030
  ? mapPrefixedEnv(this.env, envPrefix)
@@ -1012,15 +1064,39 @@ export class MCPICloudflareAgent extends McpAgent {
1012
1064
  }),
1013
1065
  });
1014
1066
  if (!resp.ok) {
1015
- return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Server error: ${resp.status}` }) }] };
1067
+ return {
1068
+ content: [
1069
+ {
1070
+ type: "text",
1071
+ text: JSON.stringify({
1072
+ success: false,
1073
+ error: `Server error: ${resp.status}`,
1074
+ }),
1075
+ },
1076
+ ],
1077
+ };
1016
1078
  }
1017
1079
  const data = await resp.json();
1018
1080
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
1019
1081
  }
1020
1082
  catch (err) {
1021
1083
  const msg = err instanceof Error ? err.message : "Unknown error";
1022
- return { content: [{ type: "text", text: JSON.stringify({ success: false, error: msg }) }] };
1084
+ return {
1085
+ content: [
1086
+ {
1087
+ type: "text",
1088
+ text: JSON.stringify({ success: false, error: msg }),
1089
+ },
1090
+ ],
1091
+ };
1023
1092
  }
1093
+ };
1094
+ this.server.tool("_kyaos_credential_auth", credentialAuthDescription, credentialAuthSchema, credentialAuthHandler);
1095
+ this.server.tool("_mcpi_credential_auth", credentialAuthDescription, credentialAuthSchema, async (args) => {
1096
+ recordWireForm("tool_name", "old", {
1097
+ tool: "_kyaos_credential_auth",
1098
+ });
1099
+ return credentialAuthHandler(args);
1024
1100
  });
1025
1101
  logger.info("[MCPICloudflareAgent] Registered MCP Apps consent UI resource");
1026
1102
  }
@@ -1128,7 +1204,8 @@ export class MCPICloudflareAgent extends McpAgent {
1128
1204
  // ✅ Automatically extract sessionId from ToolExtraArguments
1129
1205
  // This ensures delegation tokens stored with the original session ID
1130
1206
  // can be retrieved on subsequent tool calls
1131
- const sessionId = extra?.sessionId;
1207
+ const sessionId = extra
1208
+ ?.sessionId;
1132
1209
  return this.executeToolWithProof(name, args, handler, sessionId);
1133
1210
  };
1134
1211
  // Build tool config with optional MCP Apps UI metadata
@@ -1205,7 +1282,7 @@ export class MCPICloudflareAgent extends McpAgent {
1205
1282
  body: JSON.stringify({ mcpSessionId: mcpSdkSessionId }),
1206
1283
  });
1207
1284
  if (response.ok) {
1208
- const result = await response.json();
1285
+ const result = (await response.json());
1209
1286
  if (result.success && result.handshakeSessionId) {
1210
1287
  handshakeSessionId = result.handshakeSessionId;
1211
1288
  if (this._environment === "development") {
@@ -1250,15 +1327,26 @@ export class MCPICloudflareAgent extends McpAgent {
1250
1327
  sessionId: handshakeSessionId.slice(0, 20) + "...",
1251
1328
  reason: validationResult.reason,
1252
1329
  });
1253
- // Fall back to other session sources (don't trust the client's session ID)
1330
+ // Fall back to other session sources (don't trust the client's session ID).
1331
+ // Prefer the stable Durable Object id over an ephemeral mcpi_{uuid} so the
1332
+ // consent→reuse storage key matches across calls to this DO (mirrors the
1333
+ // session-registration path); the random uuid is a last resort only.
1254
1334
  sessionId =
1255
- providedSessionId || mcpSdkSessionId || `mcpi_${crypto.randomUUID()}`;
1335
+ providedSessionId ||
1336
+ mcpSdkSessionId ||
1337
+ this.ctx.id.toString() ||
1338
+ `${SESSION_ID_PREFIX}${crypto.randomUUID()}`;
1256
1339
  }
1257
1340
  }
1258
1341
  else {
1259
- // No handshake session provided - use fallback
1342
+ // No handshake session provided use fallback. Prefer the stable Durable
1343
+ // Object id over an ephemeral mcpi_{uuid} so the consent→reuse storage key
1344
+ // stays stable across calls to this DO; the random uuid is a last resort.
1260
1345
  sessionId =
1261
- providedSessionId || mcpSdkSessionId || `mcpi_${crypto.randomUUID()}`;
1346
+ providedSessionId ||
1347
+ mcpSdkSessionId ||
1348
+ this.ctx.id.toString() ||
1349
+ `${SESSION_ID_PREFIX}${crypto.randomUUID()}`;
1262
1350
  }
1263
1351
  // Log session ID source for debugging
1264
1352
  if (this._environment === "development") {
@@ -1314,8 +1402,8 @@ export class MCPICloudflareAgent extends McpAgent {
1314
1402
  if (delegationStorage) {
1315
1403
  const identityKey = STORAGE_KEYS.sessionIdentity(sessionId);
1316
1404
  const sessionKey = STORAGE_KEYS.session(sessionId);
1317
- const identityData = await delegationStorage.get(identityKey, "json");
1318
- const sessionData = await delegationStorage.get(sessionKey, "json");
1405
+ const identityData = (await delegationStorage.get(identityKey, "json"));
1406
+ const sessionData = (await delegationStorage.get(sessionKey, "json"));
1319
1407
  userDid = identityData?.userDid || sessionData?.userDid;
1320
1408
  clientDid = sessionData?.clientDid;
1321
1409
  }
@@ -1445,7 +1533,10 @@ export class MCPICloudflareAgent extends McpAgent {
1445
1533
  const scopes = credError.toolProtection?.requiredScopes || [
1446
1534
  `${toolName}:execute`,
1447
1535
  ];
1448
- const resumeToken = `resume_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
1536
+ // CSPRNG resume token (capability-bearing) — replaces the prior
1537
+ // Date.now()+Math.random() form which leaked creation time and was
1538
+ // weakly unguessable. See generateResumeToken in @kya-os/mcp-i-core.
1539
+ const resumeToken = generateResumeToken("resume");
1449
1540
  // Try MCP Apps inline credential UI first (preferred path)
1450
1541
  // Note: clientCapabilities not available in this context — pass undefined.
1451
1542
  // buildConsentUIResult gates on isExtAppsAvailable(), not client caps.
@@ -1540,7 +1631,10 @@ export class MCPICloudflareAgent extends McpAgent {
1540
1631
  isCredentialProvider,
1541
1632
  });
1542
1633
  let authUrl;
1543
- const resumeToken = `resume_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
1634
+ // CSPRNG resume token (capability-bearing) — replaces the prior
1635
+ // Date.now()+Math.random() form which leaked creation time and was
1636
+ // weakly unguessable. See generateResumeToken in @kya-os/mcp-i-core.
1637
+ const resumeToken = generateResumeToken("resume");
1544
1638
  if (isCredentialProvider) {
1545
1639
  // Try MCP Apps inline credential UI first
1546
1640
  const inlineResult = this.mcpiRuntime?.buildConsentUIResult(oauthError.toolName, oauthError.requiredScopes, session, resumeToken, session.projectId, session.serverOrigin, undefined, "credentials", oauthError.provider || "credentials");
@@ -1593,7 +1687,9 @@ export class MCPICloudflareAgent extends McpAgent {
1593
1687
  // we MUST re-throw the original error to prevent unauthorized tool execution.
1594
1688
  // Only log and continue for non-auth errors (backward compatibility).
1595
1689
  // Note: pendingInlineResult means auth is handled via inline UI — skip re-throw.
1596
- if (!pendingAuthError && !pendingInlineResult && !pendingAuthRequiredError) {
1690
+ if (!pendingAuthError &&
1691
+ !pendingInlineResult &&
1692
+ !pendingAuthRequiredError) {
1597
1693
  if (originalAuthError) {
1598
1694
  // Auth was required but URL building failed - MUST block execution
1599
1695
  logger.error("[MCPICloudflareAgent] SECURITY: Auth required but URL building failed, blocking tool execution:", { originalError: originalAuthError.message, buildError: error });
@@ -1962,7 +2058,9 @@ export class MCPICloudflareAgent extends McpAgent {
1962
2058
  if (!this._vaultResolver) {
1963
2059
  const identity = await this.mcpiRuntime.getIdentity();
1964
2060
  // Decode base64/base64url private key to bytes (self-contained, no private method access)
1965
- const b64 = identity.privateKey.replace(/-/g, "+").replace(/_/g, "/");
2061
+ const b64 = identity.privateKey
2062
+ .replace(/-/g, "+")
2063
+ .replace(/_/g, "/");
1966
2064
  const padded = b64 + "=".repeat((4 - (b64.length % 4)) % 4);
1967
2065
  const binaryStr = atob(padded);
1968
2066
  const keyBytes = new Uint8Array(binaryStr.length);
@@ -1979,8 +2077,8 @@ export class MCPICloudflareAgent extends McpAgent {
1979
2077
  : keyBytes;
1980
2078
  // Wrap as PKCS#8 for crypto.subtle.importKey
1981
2079
  const pkcs8Header = new Uint8Array([
1982
- 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05,
1983
- 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,
2080
+ 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65,
2081
+ 0x70, 0x04, 0x22, 0x04, 0x20,
1984
2082
  ]);
1985
2083
  const pkcs8 = new Uint8Array(pkcs8Header.length + rawKey.length);
1986
2084
  pkcs8.set(pkcs8Header);
@@ -2318,6 +2416,16 @@ export class MCPICloudflareAgent extends McpAgent {
2318
2416
  */
2319
2417
  async fetch(request) {
2320
2418
  const url = new URL(request.url);
2419
+ // draft-kya-http-02 §13 pickup-anchor dispatch.
2420
+ // These requests target dedicated `pickup:<sid>` durable-object instances and
2421
+ // must run BEFORE any origin detection, handshake-session side effects, or
2422
+ // super/PartyServer routing — a pickup-anchor instance must never trip agent
2423
+ // init. Precedent: the `/_internal/delegation/*` routes handled further below.
2424
+ if (url.pathname.startsWith("/_internal/pickup/")) {
2425
+ // `this.ctx` is the McpAgent base's DurableObjectState, typed against the
2426
+ // SDK's bundled @cloudflare/workers-types; bridge it to this package's copy.
2427
+ return handlePickupInternal(this.ctx, request);
2428
+ }
2321
2429
  // Auto-detect and store server origin for consent URL building.
2322
2430
  // CRITICAL: When requests are routed through Cloudflare DO routing, the URL is
2323
2431
  // transformed to an internal URL (e.g., http://dummy-example.cloudflare.com).
@@ -2552,12 +2660,19 @@ export class MCPICloudflareAgent extends McpAgent {
2552
2660
  // This is called by consent.service.ts to store delegation tokens directly in DO storage.
2553
2661
  // Using DO storage instead of KV eliminates eventual consistency issues since
2554
2662
  // both storage and retrieval happen on the same DO instance (strongly consistent).
2555
- if (url.pathname === "/_internal/delegation/store" && request.method === "POST") {
2663
+ if (url.pathname === "/_internal/delegation/store" &&
2664
+ request.method === "POST") {
2556
2665
  logger.info("[MCPICloudflareAgent] Handling internal delegation store request");
2557
2666
  try {
2558
- const body = await request.json();
2559
- if (!body.sessionId || !body.delegationToken || !body.delegationId || !body.agentDid) {
2560
- return new Response(JSON.stringify({ success: false, error: "Missing required fields" }), { status: 400, headers: { "Content-Type": "application/json" } });
2667
+ const body = (await request.json());
2668
+ if (!body.sessionId ||
2669
+ !body.delegationToken ||
2670
+ !body.delegationId ||
2671
+ !body.agentDid) {
2672
+ return new Response(JSON.stringify({
2673
+ success: false,
2674
+ error: "Missing required fields",
2675
+ }), { status: 400, headers: { "Content-Type": "application/json" } });
2561
2676
  }
2562
2677
  await this.storeDelegationToStorage(body);
2563
2678
  logger.info("[MCPICloudflareAgent] ✅ Delegation stored to DO storage:", {
@@ -2565,7 +2680,10 @@ export class MCPICloudflareAgent extends McpAgent {
2565
2680
  delegationId: body.delegationId,
2566
2681
  hasUserDid: !!body.userDid,
2567
2682
  });
2568
- return new Response(JSON.stringify({ success: true }), { status: 200, headers: { "Content-Type": "application/json" } });
2683
+ return new Response(JSON.stringify({ success: true }), {
2684
+ status: 200,
2685
+ headers: { "Content-Type": "application/json" },
2686
+ });
2569
2687
  }
2570
2688
  catch (error) {
2571
2689
  logger.error("[MCPICloudflareAgent] Delegation store request failed:", error);
@@ -2579,10 +2697,11 @@ export class MCPICloudflareAgent extends McpAgent {
2579
2697
  // This is called by getDelegationFromStorage() to read from singleton DO
2580
2698
  // when using singleton routing strategy. This ensures the agent reads from
2581
2699
  // the same DO that consent.service.ts wrote to.
2582
- if (url.pathname === "/_internal/delegation/get" && request.method === "POST") {
2700
+ if (url.pathname === "/_internal/delegation/get" &&
2701
+ request.method === "POST") {
2583
2702
  logger.debug("[MCPICloudflareAgent] Handling internal delegation get request");
2584
2703
  try {
2585
- const body = await request.json();
2704
+ const body = (await request.json());
2586
2705
  if (!body.sessionId) {
2587
2706
  return new Response(JSON.stringify({ success: false, error: "Missing sessionId" }), { status: 400, headers: { "Content-Type": "application/json" } });
2588
2707
  }
@@ -2613,7 +2732,10 @@ export class MCPICloudflareAgent extends McpAgent {
2613
2732
  hasUserDid: !!body.userDid,
2614
2733
  hasAgentDid: !!body.agentDid,
2615
2734
  });
2616
- return new Response(JSON.stringify({ success: true, data: null }), { status: 200, headers: { "Content-Type": "application/json" } });
2735
+ return new Response(JSON.stringify({ success: true, data: null }), {
2736
+ status: 200,
2737
+ headers: { "Content-Type": "application/json" },
2738
+ });
2617
2739
  }
2618
2740
  catch (error) {
2619
2741
  logger.error("[MCPICloudflareAgent] Delegation get request failed:", error);
@@ -2626,12 +2748,16 @@ export class MCPICloudflareAgent extends McpAgent {
2626
2748
  // Handle internal session mapping storage request
2627
2749
  // This is called to store MCP session ID → handshake session ID mapping in DO storage
2628
2750
  // for strong consistency (avoiding KV eventual consistency issues)
2629
- if (url.pathname === "/_internal/session-mapping/store" && request.method === "POST") {
2751
+ if (url.pathname === "/_internal/session-mapping/store" &&
2752
+ request.method === "POST") {
2630
2753
  logger.debug("[MCPICloudflareAgent] Handling internal session mapping store request");
2631
2754
  try {
2632
- const body = await request.json();
2755
+ const body = (await request.json());
2633
2756
  if (!body.mcpSessionId || !body.handshakeSessionId) {
2634
- return new Response(JSON.stringify({ success: false, error: "Missing required fields" }), { status: 400, headers: { "Content-Type": "application/json" } });
2757
+ return new Response(JSON.stringify({
2758
+ success: false,
2759
+ error: "Missing required fields",
2760
+ }), { status: 400, headers: { "Content-Type": "application/json" } });
2635
2761
  }
2636
2762
  // Store in DO storage
2637
2763
  // Note: DO storage doesn't support TTL like KV, but the mapping is cleaned up
@@ -2642,7 +2768,10 @@ export class MCPICloudflareAgent extends McpAgent {
2642
2768
  mcpSessionId: body.mcpSessionId.slice(0, 20) + "...",
2643
2769
  handshakeSessionId: body.handshakeSessionId.slice(0, 20) + "...",
2644
2770
  });
2645
- return new Response(JSON.stringify({ success: true }), { status: 200, headers: { "Content-Type": "application/json" } });
2771
+ return new Response(JSON.stringify({ success: true }), {
2772
+ status: 200,
2773
+ headers: { "Content-Type": "application/json" },
2774
+ });
2646
2775
  }
2647
2776
  catch (error) {
2648
2777
  logger.error("[MCPICloudflareAgent] Session mapping store request failed:", error);
@@ -2654,10 +2783,11 @@ export class MCPICloudflareAgent extends McpAgent {
2654
2783
  }
2655
2784
  // Handle internal session mapping retrieval request
2656
2785
  // This is called to retrieve MCP session ID → handshake session ID mapping from DO storage
2657
- if (url.pathname === "/_internal/session-mapping/get" && request.method === "POST") {
2786
+ if (url.pathname === "/_internal/session-mapping/get" &&
2787
+ request.method === "POST") {
2658
2788
  logger.debug("[MCPICloudflareAgent] Handling internal session mapping get request");
2659
2789
  try {
2660
- const body = await request.json();
2790
+ const body = (await request.json());
2661
2791
  if (!body.mcpSessionId) {
2662
2792
  return new Response(JSON.stringify({ success: false, error: "Missing mcpSessionId" }), { status: 400, headers: { "Content-Type": "application/json" } });
2663
2793
  }
@@ -2686,7 +2816,8 @@ export class MCPICloudflareAgent extends McpAgent {
2686
2816
  }
2687
2817
  // Handle internal client capabilities request
2688
2818
  // Returns stored MCP client capabilities from the initialize message
2689
- if (url.pathname === "/_internal/client-capabilities" && request.method === "GET") {
2819
+ if (url.pathname === "/_internal/client-capabilities" &&
2820
+ request.method === "GET") {
2690
2821
  try {
2691
2822
  const mcpClientInfo = await this.getMcpClientInfo();
2692
2823
  return new Response(JSON.stringify({
@@ -2922,7 +3053,7 @@ export class MCPICloudflareAgent extends McpAgent {
2922
3053
  body: JSON.stringify({ sessionId, userDid, agentDid }),
2923
3054
  });
2924
3055
  if (response.ok) {
2925
- const result = await response.json();
3056
+ const result = (await response.json());
2926
3057
  if (result.success && result.data) {
2927
3058
  logger.debug("[MCPICloudflareAgent] ✅ Delegation found via DO:", {
2928
3059
  sessionId: sessionId.slice(0, 20) + "...",