@kya-os/mcp-i-cloudflare 1.9.7 → 1.10.1

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 (151) hide show
  1. package/dist/adapter.d.ts +2 -2
  2. package/dist/adapter.d.ts.map +1 -1
  3. package/dist/adapter.js +53 -25
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/agent.d.ts +1 -1
  6. package/dist/agent.d.ts.map +1 -1
  7. package/dist/agent.js +204 -59
  8. package/dist/agent.js.map +1 -1
  9. package/dist/app.d.ts.map +1 -1
  10. package/dist/app.js +86 -3
  11. package/dist/app.js.map +1 -1
  12. package/dist/cache/kv-oauth-config-cache.d.ts +1 -1
  13. package/dist/cache/kv-oauth-config-cache.d.ts.map +1 -1
  14. package/dist/cache/kv-tool-protection-cache.d.ts +1 -1
  15. package/dist/cache/kv-tool-protection-cache.d.ts.map +1 -1
  16. package/dist/config.d.ts +54 -0
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +58 -0
  19. package/dist/config.js.map +1 -1
  20. package/dist/constants/storage-keys.d.ts +6 -0
  21. package/dist/constants/storage-keys.d.ts.map +1 -1
  22. package/dist/constants/storage-keys.js +6 -0
  23. package/dist/constants/storage-keys.js.map +1 -1
  24. package/dist/delegation-http/anchor-do.d.ts +84 -0
  25. package/dist/delegation-http/anchor-do.d.ts.map +1 -0
  26. package/dist/delegation-http/anchor-do.js +253 -0
  27. package/dist/delegation-http/anchor-do.js.map +1 -0
  28. package/dist/delegation-http/anchor-routing.d.ts +20 -0
  29. package/dist/delegation-http/anchor-routing.d.ts.map +1 -0
  30. package/dist/delegation-http/anchor-routing.js +27 -0
  31. package/dist/delegation-http/anchor-routing.js.map +1 -0
  32. package/dist/delegation-http/bearer-retry.d.ts +54 -0
  33. package/dist/delegation-http/bearer-retry.d.ts.map +1 -0
  34. package/dist/delegation-http/bearer-retry.js +195 -0
  35. package/dist/delegation-http/bearer-retry.js.map +1 -0
  36. package/dist/delegation-http/challenge-middleware.d.ts +28 -0
  37. package/dist/delegation-http/challenge-middleware.d.ts.map +1 -0
  38. package/dist/delegation-http/challenge-middleware.js +190 -0
  39. package/dist/delegation-http/challenge-middleware.js.map +1 -0
  40. package/dist/delegation-http/challenge.d.ts +86 -0
  41. package/dist/delegation-http/challenge.d.ts.map +1 -0
  42. package/dist/delegation-http/challenge.js +173 -0
  43. package/dist/delegation-http/challenge.js.map +1 -0
  44. package/dist/delegation-http/consent-anchor-bridge.d.ts +35 -0
  45. package/dist/delegation-http/consent-anchor-bridge.d.ts.map +1 -0
  46. package/dist/delegation-http/consent-anchor-bridge.js +355 -0
  47. package/dist/delegation-http/consent-anchor-bridge.js.map +1 -0
  48. package/dist/delegation-http/constants.d.ts +51 -0
  49. package/dist/delegation-http/constants.d.ts.map +1 -0
  50. package/dist/delegation-http/constants.js +60 -0
  51. package/dist/delegation-http/constants.js.map +1 -0
  52. package/dist/delegation-http/hash.d.ts +21 -0
  53. package/dist/delegation-http/hash.d.ts.map +1 -0
  54. package/dist/delegation-http/hash.js +92 -0
  55. package/dist/delegation-http/hash.js.map +1 -0
  56. package/dist/delegation-http/pickup-routes.d.ts +42 -0
  57. package/dist/delegation-http/pickup-routes.d.ts.map +1 -0
  58. package/dist/delegation-http/pickup-routes.js +79 -0
  59. package/dist/delegation-http/pickup-routes.js.map +1 -0
  60. package/dist/delegation-http/register.d.ts +19 -0
  61. package/dist/delegation-http/register.d.ts.map +1 -0
  62. package/dist/delegation-http/register.js +72 -0
  63. package/dist/delegation-http/register.js.map +1 -0
  64. package/dist/delegation-http/sid.d.ts +20 -0
  65. package/dist/delegation-http/sid.d.ts.map +1 -0
  66. package/dist/delegation-http/sid.js +32 -0
  67. package/dist/delegation-http/sid.js.map +1 -0
  68. package/dist/delegation-http/types.d.ts +64 -0
  69. package/dist/delegation-http/types.d.ts.map +1 -0
  70. package/dist/delegation-http/types.js +11 -0
  71. package/dist/delegation-http/types.js.map +1 -0
  72. package/dist/helpers/env-mapper.d.ts.map +1 -1
  73. package/dist/helpers/env-mapper.js +24 -0
  74. package/dist/helpers/env-mapper.js.map +1 -1
  75. package/dist/index.d.ts +7 -3
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +15 -2
  78. package/dist/index.js.map +1 -1
  79. package/dist/needs-authorization-meta.d.ts +35 -0
  80. package/dist/needs-authorization-meta.d.ts.map +1 -0
  81. package/dist/needs-authorization-meta.js +50 -0
  82. package/dist/needs-authorization-meta.js.map +1 -0
  83. package/dist/proof-generator.d.ts +1 -1
  84. package/dist/proof-generator.d.ts.map +1 -1
  85. package/dist/providers/crypto.d.ts +1 -1
  86. package/dist/providers/crypto.d.ts.map +1 -1
  87. package/dist/providers/crypto.js +1 -1
  88. package/dist/providers/crypto.js.map +1 -1
  89. package/dist/providers/kv-identity.d.ts +2 -2
  90. package/dist/providers/kv-identity.d.ts.map +1 -1
  91. package/dist/providers/kv-identity.js +1 -1
  92. package/dist/providers/kv-identity.js.map +1 -1
  93. package/dist/providers/storage.d.ts +1 -1
  94. package/dist/providers/storage.d.ts.map +1 -1
  95. package/dist/providers/storage.js +1 -1
  96. package/dist/providers/storage.js.map +1 -1
  97. package/dist/runtime/audit-logger.d.ts +1 -1
  98. package/dist/runtime/audit-logger.d.ts.map +1 -1
  99. package/dist/runtime/oauth-handler.d.ts.map +1 -1
  100. package/dist/runtime/oauth-handler.js +45 -7
  101. package/dist/runtime/oauth-handler.js.map +1 -1
  102. package/dist/runtime/oidc/authorize-url.d.ts +19 -0
  103. package/dist/runtime/oidc/authorize-url.d.ts.map +1 -0
  104. package/dist/runtime/oidc/authorize-url.js +94 -0
  105. package/dist/runtime/oidc/authorize-url.js.map +1 -0
  106. package/dist/runtime/oidc/token-exchange.d.ts +62 -0
  107. package/dist/runtime/oidc/token-exchange.d.ts.map +1 -0
  108. package/dist/runtime/oidc/token-exchange.js +75 -0
  109. package/dist/runtime/oidc/token-exchange.js.map +1 -0
  110. package/dist/runtime.d.ts +3 -3
  111. package/dist/runtime.d.ts.map +1 -1
  112. package/dist/runtime.js +1 -1
  113. package/dist/runtime.js.map +1 -1
  114. package/dist/server.d.ts.map +1 -1
  115. package/dist/server.js +1 -1
  116. package/dist/server.js.map +1 -1
  117. package/dist/services/admin.service.js +1 -1
  118. package/dist/services/admin.service.js.map +1 -1
  119. package/dist/services/consent-audit.service.d.ts +1 -1
  120. package/dist/services/consent-audit.service.d.ts.map +1 -1
  121. package/dist/services/consent-audit.service.js.map +1 -1
  122. package/dist/services/consent.service.d.ts +5 -5
  123. package/dist/services/consent.service.d.ts.map +1 -1
  124. package/dist/services/consent.service.js +41 -52
  125. package/dist/services/consent.service.js.map +1 -1
  126. package/dist/services/delegation.service.d.ts +9 -0
  127. package/dist/services/delegation.service.d.ts.map +1 -1
  128. package/dist/services/delegation.service.js +21 -7
  129. package/dist/services/delegation.service.js.map +1 -1
  130. package/dist/services/idp-token-storage.d.ts +1 -1
  131. package/dist/services/idp-token-storage.d.ts.map +1 -1
  132. package/dist/services/oauth-security.service.d.ts.map +1 -1
  133. package/dist/services/oauth-security.service.js +6 -10
  134. package/dist/services/oauth-security.service.js.map +1 -1
  135. package/dist/services/proof.service.js.map +1 -1
  136. package/dist/services/provider-loader.service.js +1 -1
  137. package/dist/services/provider-loader.service.js.map +1 -1
  138. package/dist/types.d.ts +5 -1
  139. package/dist/types.d.ts.map +1 -1
  140. package/dist/utils/env-secret-resolver.d.ts +18 -0
  141. package/dist/utils/env-secret-resolver.d.ts.map +1 -0
  142. package/dist/utils/env-secret-resolver.js +46 -0
  143. package/dist/utils/env-secret-resolver.js.map +1 -0
  144. package/dist/utils/known-clients.d.ts.map +1 -1
  145. package/dist/utils/known-clients.js +19 -10
  146. package/dist/utils/known-clients.js.map +1 -1
  147. package/dist/utils/oauth-service-registry.d.ts +1 -1
  148. package/dist/utils/oauth-service-registry.d.ts.map +1 -1
  149. package/dist/utils/oauth-service-registry.js +1 -1
  150. package/dist/utils/oauth-service-registry.js.map +1 -1
  151. package/package.json +5 -3
package/dist/agent.js CHANGED
@@ -10,10 +10,14 @@ 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 { makeEnvSecretResolver } from "./utils/env-secret-resolver.js";
14
+ import { handlePickupInternal } from "./delegation-http/anchor-do";
13
15
  import { STORAGE_KEYS } from "./constants/storage-keys";
14
16
  import { DEFAULT_SESSION_TTL } from "./constants";
17
+ import { buildNeedsAuthorizationMeta } from "./needs-authorization-meta";
15
18
  import { z } from "zod";
16
- import { OAuthService, IdpTokenResolver, ToolContextBuilder, OAuthRequiredError, DelegationRequiredError, DelegationErrorFormatter, SessionRegistrationService, generateDidKeyFromBase64, logger, } from "@kya-os/mcp-i-core";
19
+ import { generateDidKeyFromBase64, logger } from "@kya-os/mcp";
20
+ import { OAuthService, IdpTokenResolver, ToolContextBuilder, OAuthRequiredError, DelegationRequiredError, DelegationErrorFormatter, SessionRegistrationService, SESSION_ID_PREFIX, recordWireForm, generateResumeToken, } from "@kya-os/mcp-i-runtime"; // product layer — stays (C2/C3)
17
21
  import { WebCryptoProvider } from "./providers/crypto";
18
22
  // CRITICAL: Import the OAuth service registry - this triggers globalThis registration
19
23
  // at module load time, preventing esbuild from tree-shaking the OAuth services
@@ -24,11 +28,11 @@ import { OAuthSecurityService } from "./services/oauth-security.service";
24
28
  import { WorkersFetchProvider } from "./providers/storage";
25
29
  import { resolveClientIdentity, } from "./services/kta-client-lookup";
26
30
  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";
31
+ import { shouldUsePopup, buildPopupResource } from "@kya-os/consent";
32
+ import { didKeyFragment } from "@kya-os/mcp";
29
33
  import { calculateDOInstanceId, parseDORoutingStrategy, parseDOShardCount, } from "./utils/do-routing";
30
34
  import { defaultProviderRegistry, } from "@kya-os/provider-registry";
31
- import { hasApiKeyAuthorization, AuthRequiredError } from "@kya-os/contracts/tool-protection";
35
+ import { hasApiKeyAuthorization, AuthRequiredError, } from "@kya-os/contracts/tool-protection";
32
36
  import { VaultResolver } from "./services/vault-resolver";
33
37
  // CRITICAL: Force OAuth registry evaluation at module load time
34
38
  // This creates an observable side effect that esbuild cannot tree-shake
@@ -184,7 +188,9 @@ export class MCPICloudflareAgent extends McpAgent {
184
188
  hasCapabilities: !!capabilities,
185
189
  capabilityKeys: capabilities ? Object.keys(capabilities) : [],
186
190
  hasExtensions: !!capabilities?.extensions,
187
- extensionKeys: capabilities?.extensions ? Object.keys(capabilities.extensions) : [],
191
+ extensionKeys: capabilities?.extensions
192
+ ? Object.keys(capabilities.extensions)
193
+ : [],
188
194
  rawCapabilities: JSON.stringify(capabilities)?.substring(0, 500),
189
195
  });
190
196
  // Store client info for later retrieval (always, not just in dev)
@@ -244,7 +250,10 @@ export class MCPICloudflareAgent extends McpAgent {
244
250
  logger.info(`║ Client DID: ${clientIdentity.did.slice(0, 40).padEnd(42)}║`);
245
251
  logger.info(`║ DID Source: ${clientIdentity.source.padEnd(42)}║`);
246
252
  logger.info("╠════════════════════════════════════════════════════════╣");
247
- logger.info(`║ Your Agent: ${isAgentRegistered ? "✓ REGISTERED" : "○ LOCAL"} ${agentDid.slice(0, 33).padEnd(36)}║`);
253
+ const agentStatus = isAgentRegistered ? "✓ REGISTERED" : "○ LOCAL";
254
+ const agentPrefix = ` Your Agent: ${agentStatus} `;
255
+ const agentPadWidth = 56 - agentPrefix.length;
256
+ logger.info(`║${agentPrefix}${agentDid.slice(0, agentPadWidth).padEnd(agentPadWidth)}║`);
248
257
  logger.info("╚════════════════════════════════════════════════════════╝");
249
258
  logger.info("");
250
259
  }
@@ -382,10 +391,10 @@ export class MCPICloudflareAgent extends McpAgent {
382
391
  : this.env;
383
392
  // ✅ CRITICAL: Use same session ID resolution logic as executeToolWithProof
384
393
  // This ensures session registration uses the same ID that proofs will use
385
- // Priority: getSessionId() (from agents SDK) > DO ID fallback > mcpi_{uuid}
394
+ // Priority: getSessionId() (from agents SDK) > DO ID fallback > kyaos_{uuid}
386
395
  const mcpSessionId = this.getSessionId();
387
396
  const doId = this.ctx.id.toString();
388
- const sessionId = mcpSessionId || doId || `mcpi_${crypto.randomUUID()}`;
397
+ const sessionId = mcpSessionId || doId || `${SESSION_ID_PREFIX}${crypto.randomUUID()}`;
389
398
  // Log session ID source for debugging (matches executeToolWithProof pattern)
390
399
  if (this._environment === "development") {
391
400
  logger.debug("[SessionRegistration] Session ID resolved:", {
@@ -560,7 +569,7 @@ export class MCPICloudflareAgent extends McpAgent {
560
569
  const [extApps, consentMcpApp, extAppsConstants] = await Promise.all([
561
570
  import("@modelcontextprotocol/ext-apps/server").catch(() => null),
562
571
  import("@kya-os/consent/mcp-app").catch(() => null),
563
- import("@kya-os/mcp-i-core/runtime/ext-apps-constants").catch(() => null),
572
+ import("@kya-os/mcp-i-runtime/runtime/ext-apps-constants").catch(() => null),
564
573
  ]);
565
574
  if (!extApps || !consentMcpApp || !extAppsConstants) {
566
575
  logger.info("[MCPICloudflareAgent] MCP Apps consent UI not available (missing optional dependencies)");
@@ -809,7 +818,7 @@ export class MCPICloudflareAgent extends McpAgent {
809
818
  send({
810
819
  jsonrpc: "2.0", id: reqId, method: "tools/call",
811
820
  params: {
812
- name: "_mcpi_credential_auth",
821
+ name: "_kyaos_credential_auth",
813
822
  arguments: {
814
823
  tool: consentData.tool,
815
824
  scopes: consentData.scopes,
@@ -827,7 +836,7 @@ export class MCPICloudflareAgent extends McpAgent {
827
836
  send({
828
837
  jsonrpc: "2.0", id: reqId, method: "tools/call",
829
838
  params: {
830
- name: "_mcpi_approve_consent",
839
+ name: "_kyaos_approve_consent",
831
840
  arguments: {
832
841
  tool: consentData.tool,
833
842
  scopes: consentData.scopes,
@@ -915,14 +924,21 @@ export class MCPICloudflareAgent extends McpAgent {
915
924
  // The sandboxed MCP Apps iframe cannot call fetch() directly, so it
916
925
  // uses tools/call (proxied via postMessage through the host) to invoke
917
926
  // 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.", {
927
+ // C4 (#2918) dual-accept: register the consent-approval tool under the
928
+ // canonical `_kyaos_` name and keep the legacy `_mcpi_` name as a thin
929
+ // alias during the window. The server-rendered iframe already calls the
930
+ // new name; the alias is defensive for an in-flight pre-deploy iframe.
931
+ // Same handler, two names — DIF aliases `_kyaos_handshake` the same way.
932
+ 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.";
933
+ const approveConsentSchema = {
919
934
  tool: z.string(),
920
935
  scopes: z.array(z.string()),
921
936
  session_id: z.string(),
922
937
  agent_did: z.string(),
923
938
  project_id: z.string(),
924
939
  resume_token: z.string(),
925
- }, async (args) => {
940
+ };
941
+ const approveConsentHandler = async (args) => {
926
942
  const envPrefix = this.getEnvPrefix();
927
943
  const mappedEnv = envPrefix
928
944
  ? mapPrefixedEnv(this.env, envPrefix)
@@ -930,7 +946,17 @@ export class MCPICloudflareAgent extends McpAgent {
930
946
  const serverUrl = mappedEnv.MCP_SERVER_URL ??
931
947
  this._autoDetectedOrigin;
932
948
  if (!serverUrl) {
933
- return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "No server URL configured" }) }] };
949
+ return {
950
+ content: [
951
+ {
952
+ type: "text",
953
+ text: JSON.stringify({
954
+ success: false,
955
+ error: "No server URL configured",
956
+ }),
957
+ },
958
+ ],
959
+ };
934
960
  }
935
961
  try {
936
962
  const resp = await fetch(`${serverUrl}/consent/approve`, {
@@ -948,21 +974,48 @@ export class MCPICloudflareAgent extends McpAgent {
948
974
  }),
949
975
  });
950
976
  if (!resp.ok) {
951
- return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Server error: ${resp.status}` }) }] };
977
+ return {
978
+ content: [
979
+ {
980
+ type: "text",
981
+ text: JSON.stringify({
982
+ success: false,
983
+ error: `Server error: ${resp.status}`,
984
+ }),
985
+ },
986
+ ],
987
+ };
952
988
  }
953
989
  const data = await resp.json();
954
990
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
955
991
  }
956
992
  catch (err) {
957
993
  const msg = err instanceof Error ? err.message : "Unknown error";
958
- return { content: [{ type: "text", text: JSON.stringify({ success: false, error: msg }) }] };
994
+ return {
995
+ content: [
996
+ {
997
+ type: "text",
998
+ text: JSON.stringify({ success: false, error: msg }),
999
+ },
1000
+ ],
1001
+ };
959
1002
  }
1003
+ };
1004
+ this.server.tool("_kyaos_approve_consent", approveConsentDescription, approveConsentSchema, approveConsentHandler);
1005
+ this.server.tool("_mcpi_approve_consent", approveConsentDescription, approveConsentSchema, async (args) => {
1006
+ recordWireForm("tool_name", "old", {
1007
+ tool: "_kyaos_approve_consent",
1008
+ });
1009
+ return approveConsentHandler(args);
960
1010
  });
961
1011
  // Register internal tool for iframe-based credential authentication.
962
1012
  // Same sandbox workaround as _mcpi_approve_consent: the iframe calls
963
1013
  // tools/call via postMessage, which the host proxies to this tool.
964
1014
  // 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.", {
1015
+ // C4 (#2918) dual-accept: same accept-both treatment as approve_consent
1016
+ // canonical `_kyaos_credential_auth` plus the legacy `_mcpi_` alias.
1017
+ 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.";
1018
+ const credentialAuthSchema = {
966
1019
  tool: z.string(),
967
1020
  scopes: z.array(z.string()),
968
1021
  session_id: z.string(),
@@ -972,7 +1025,8 @@ export class MCPICloudflareAgent extends McpAgent {
972
1025
  username: z.string(),
973
1026
  password: z.string(),
974
1027
  provider: z.string(),
975
- }, async (args) => {
1028
+ };
1029
+ const credentialAuthHandler = async (args) => {
976
1030
  const envPrefix = this.getEnvPrefix();
977
1031
  const mappedEnv = envPrefix
978
1032
  ? mapPrefixedEnv(this.env, envPrefix)
@@ -1012,15 +1066,39 @@ export class MCPICloudflareAgent extends McpAgent {
1012
1066
  }),
1013
1067
  });
1014
1068
  if (!resp.ok) {
1015
- return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Server error: ${resp.status}` }) }] };
1069
+ return {
1070
+ content: [
1071
+ {
1072
+ type: "text",
1073
+ text: JSON.stringify({
1074
+ success: false,
1075
+ error: `Server error: ${resp.status}`,
1076
+ }),
1077
+ },
1078
+ ],
1079
+ };
1016
1080
  }
1017
1081
  const data = await resp.json();
1018
1082
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
1019
1083
  }
1020
1084
  catch (err) {
1021
1085
  const msg = err instanceof Error ? err.message : "Unknown error";
1022
- return { content: [{ type: "text", text: JSON.stringify({ success: false, error: msg }) }] };
1086
+ return {
1087
+ content: [
1088
+ {
1089
+ type: "text",
1090
+ text: JSON.stringify({ success: false, error: msg }),
1091
+ },
1092
+ ],
1093
+ };
1023
1094
  }
1095
+ };
1096
+ this.server.tool("_kyaos_credential_auth", credentialAuthDescription, credentialAuthSchema, credentialAuthHandler);
1097
+ this.server.tool("_mcpi_credential_auth", credentialAuthDescription, credentialAuthSchema, async (args) => {
1098
+ recordWireForm("tool_name", "old", {
1099
+ tool: "_kyaos_credential_auth",
1100
+ });
1101
+ return credentialAuthHandler(args);
1024
1102
  });
1025
1103
  logger.info("[MCPICloudflareAgent] Registered MCP Apps consent UI resource");
1026
1104
  }
@@ -1128,7 +1206,8 @@ export class MCPICloudflareAgent extends McpAgent {
1128
1206
  // ✅ Automatically extract sessionId from ToolExtraArguments
1129
1207
  // This ensures delegation tokens stored with the original session ID
1130
1208
  // can be retrieved on subsequent tool calls
1131
- const sessionId = extra?.sessionId;
1209
+ const sessionId = extra
1210
+ ?.sessionId;
1132
1211
  return this.executeToolWithProof(name, args, handler, sessionId);
1133
1212
  };
1134
1213
  // Build tool config with optional MCP Apps UI metadata
@@ -1205,7 +1284,7 @@ export class MCPICloudflareAgent extends McpAgent {
1205
1284
  body: JSON.stringify({ mcpSessionId: mcpSdkSessionId }),
1206
1285
  });
1207
1286
  if (response.ok) {
1208
- const result = await response.json();
1287
+ const result = (await response.json());
1209
1288
  if (result.success && result.handshakeSessionId) {
1210
1289
  handshakeSessionId = result.handshakeSessionId;
1211
1290
  if (this._environment === "development") {
@@ -1250,15 +1329,26 @@ export class MCPICloudflareAgent extends McpAgent {
1250
1329
  sessionId: handshakeSessionId.slice(0, 20) + "...",
1251
1330
  reason: validationResult.reason,
1252
1331
  });
1253
- // Fall back to other session sources (don't trust the client's session ID)
1332
+ // Fall back to other session sources (don't trust the client's session ID).
1333
+ // Prefer the stable Durable Object id over an ephemeral mcpi_{uuid} so the
1334
+ // consent→reuse storage key matches across calls to this DO (mirrors the
1335
+ // session-registration path); the random uuid is a last resort only.
1254
1336
  sessionId =
1255
- providedSessionId || mcpSdkSessionId || `mcpi_${crypto.randomUUID()}`;
1337
+ providedSessionId ||
1338
+ mcpSdkSessionId ||
1339
+ this.ctx.id.toString() ||
1340
+ `${SESSION_ID_PREFIX}${crypto.randomUUID()}`;
1256
1341
  }
1257
1342
  }
1258
1343
  else {
1259
- // No handshake session provided - use fallback
1344
+ // No handshake session provided use fallback. Prefer the stable Durable
1345
+ // Object id over an ephemeral mcpi_{uuid} so the consent→reuse storage key
1346
+ // stays stable across calls to this DO; the random uuid is a last resort.
1260
1347
  sessionId =
1261
- providedSessionId || mcpSdkSessionId || `mcpi_${crypto.randomUUID()}`;
1348
+ providedSessionId ||
1349
+ mcpSdkSessionId ||
1350
+ this.ctx.id.toString() ||
1351
+ `${SESSION_ID_PREFIX}${crypto.randomUUID()}`;
1262
1352
  }
1263
1353
  // Log session ID source for debugging
1264
1354
  if (this._environment === "development") {
@@ -1314,8 +1404,8 @@ export class MCPICloudflareAgent extends McpAgent {
1314
1404
  if (delegationStorage) {
1315
1405
  const identityKey = STORAGE_KEYS.sessionIdentity(sessionId);
1316
1406
  const sessionKey = STORAGE_KEYS.session(sessionId);
1317
- const identityData = await delegationStorage.get(identityKey, "json");
1318
- const sessionData = await delegationStorage.get(sessionKey, "json");
1407
+ const identityData = (await delegationStorage.get(identityKey, "json"));
1408
+ const sessionData = (await delegationStorage.get(sessionKey, "json"));
1319
1409
  userDid = identityData?.userDid || sessionData?.userDid;
1320
1410
  clientDid = sessionData?.clientDid;
1321
1411
  }
@@ -1445,7 +1535,10 @@ export class MCPICloudflareAgent extends McpAgent {
1445
1535
  const scopes = credError.toolProtection?.requiredScopes || [
1446
1536
  `${toolName}:execute`,
1447
1537
  ];
1448
- const resumeToken = `resume_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
1538
+ // CSPRNG resume token (capability-bearing) — replaces the prior
1539
+ // Date.now()+Math.random() form which leaked creation time and was
1540
+ // weakly unguessable. See generateResumeToken in @kya-os/mcp-i-core.
1541
+ const resumeToken = generateResumeToken("resume");
1449
1542
  // Try MCP Apps inline credential UI first (preferred path)
1450
1543
  // Note: clientCapabilities not available in this context — pass undefined.
1451
1544
  // buildConsentUIResult gates on isExtAppsAvailable(), not client caps.
@@ -1540,7 +1633,10 @@ export class MCPICloudflareAgent extends McpAgent {
1540
1633
  isCredentialProvider,
1541
1634
  });
1542
1635
  let authUrl;
1543
- const resumeToken = `resume_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
1636
+ // CSPRNG resume token (capability-bearing) — replaces the prior
1637
+ // Date.now()+Math.random() form which leaked creation time and was
1638
+ // weakly unguessable. See generateResumeToken in @kya-os/mcp-i-core.
1639
+ const resumeToken = generateResumeToken("resume");
1544
1640
  if (isCredentialProvider) {
1545
1641
  // Try MCP Apps inline credential UI first
1546
1642
  const inlineResult = this.mcpiRuntime?.buildConsentUIResult(oauthError.toolName, oauthError.requiredScopes, session, resumeToken, session.projectId, session.serverOrigin, undefined, "credentials", oauthError.provider || "credentials");
@@ -1593,7 +1689,9 @@ export class MCPICloudflareAgent extends McpAgent {
1593
1689
  // we MUST re-throw the original error to prevent unauthorized tool execution.
1594
1690
  // Only log and continue for non-auth errors (backward compatibility).
1595
1691
  // Note: pendingInlineResult means auth is handled via inline UI — skip re-throw.
1596
- if (!pendingAuthError && !pendingInlineResult && !pendingAuthRequiredError) {
1692
+ if (!pendingAuthError &&
1693
+ !pendingInlineResult &&
1694
+ !pendingAuthRequiredError) {
1597
1695
  if (originalAuthError) {
1598
1696
  // Auth was required but URL building failed - MUST block execution
1599
1697
  logger.error("[MCPICloudflareAgent] SECURITY: Auth required but URL building failed, blocking tool execution:", { originalError: originalAuthError.message, buildError: error });
@@ -1699,15 +1797,12 @@ export class MCPICloudflareAgent extends McpAgent {
1699
1797
  },
1700
1798
  ],
1701
1799
  isError: true,
1702
- _meta: {
1703
- errorType: "authorization_required",
1704
- toolName: toolNameForError,
1705
- requiredScopes: scopes,
1706
- consentUrl,
1707
- authorizationUrl: consentUrl,
1708
- resumeToken,
1709
- ...(popupResource ? { popupResource } : {}),
1710
- },
1800
+ // C4 (#2918) 3c dual-emit: carry BOTH the ratified DIF
1801
+ // needs_authorization envelope AND the legacy proprietary keys
1802
+ // (`errorType: "authorization_required"` — the kya-http §8.1
1803
+ // challenge trigger). See buildNeedsAuthorizationMeta.
1804
+ _meta: buildNeedsAuthorizationMeta(delegationError, consentUrl, delegationError.interceptedCall?.expiresAt ??
1805
+ Date.now() + 1_800_000, popupResource ? { popupResource } : {}),
1711
1806
  };
1712
1807
  }
1713
1808
  // Handle OAuthRequiredError - use formatOAuth() to preserve provider name
@@ -1962,7 +2057,9 @@ export class MCPICloudflareAgent extends McpAgent {
1962
2057
  if (!this._vaultResolver) {
1963
2058
  const identity = await this.mcpiRuntime.getIdentity();
1964
2059
  // Decode base64/base64url private key to bytes (self-contained, no private method access)
1965
- const b64 = identity.privateKey.replace(/-/g, "+").replace(/_/g, "/");
2060
+ const b64 = identity.privateKey
2061
+ .replace(/-/g, "+")
2062
+ .replace(/_/g, "/");
1966
2063
  const padded = b64 + "=".repeat((4 - (b64.length % 4)) % 4);
1967
2064
  const binaryStr = atob(padded);
1968
2065
  const keyBytes = new Uint8Array(binaryStr.length);
@@ -1979,8 +2076,8 @@ export class MCPICloudflareAgent extends McpAgent {
1979
2076
  : keyBytes;
1980
2077
  // Wrap as PKCS#8 for crypto.subtle.importKey
1981
2078
  const pkcs8Header = new Uint8Array([
1982
- 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05,
1983
- 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,
2079
+ 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65,
2080
+ 0x70, 0x04, 0x22, 0x04, 0x20,
1984
2081
  ]);
1985
2082
  const pkcs8 = new Uint8Array(pkcs8Header.length + rawKey.length);
1986
2083
  pkcs8.set(pkcs8Header);
@@ -2087,6 +2184,21 @@ export class MCPICloudflareAgent extends McpAgent {
2087
2184
  agentShieldApiUrl: env.AGENTSHIELD_API_URL || "https://kya.vouched.id",
2088
2185
  agentShieldApiKey: env.AGENTSHIELD_API_KEY,
2089
2186
  projectId,
2187
+ // WFP fix (see makeEnvSecretResolver): resolve the client secret from the
2188
+ // RAW worker env. We pass `this.env`, NOT the `env` param — `env` here is
2189
+ // `mappedEnv` (mapPrefixedEnv(this.env, …) on prefixed deployments), a
2190
+ // WHITELISTED CloudflareEnv that DROPS dynamically-named secret bindings like
2191
+ // KYA_…_CLIENT_SECRET, so resolving off it would miss the secret. `this.env`
2192
+ // is the unmapped worker bindings and keeps them. (The other options above
2193
+ // read whitelisted keys, so `env` is correct for those.) Injected for SYMMETRY
2194
+ // with the two callback sites; this tool-execution path mainly refreshes IdP
2195
+ // tokens and refreshTokenPKCE sends no client_secret, so it isn't exercised for
2196
+ // its value today, but wiring it correctly keeps all three sites consistent.
2197
+ // Coverage: agent.ts can't load in the node test runtime (cloudflare: imports),
2198
+ // so this site is verified by the source wiring guard in
2199
+ // oauth-callback-env-secret.test.ts; the resolver is covered 100% in
2200
+ // env-secret-resolver*.test.ts.
2201
+ secretResolver: makeEnvSecretResolver(this.env),
2090
2202
  });
2091
2203
  const oauthSecurityService = new OAuthSecurityService(env.DELEGATION_STORAGE, env.OAUTH_ENCRYPTION_SECRET);
2092
2204
  // Only create IdpTokenStorage if DELEGATION_STORAGE is available
@@ -2318,6 +2430,16 @@ export class MCPICloudflareAgent extends McpAgent {
2318
2430
  */
2319
2431
  async fetch(request) {
2320
2432
  const url = new URL(request.url);
2433
+ // draft-kya-http-02 §13 pickup-anchor dispatch.
2434
+ // These requests target dedicated `pickup:<sid>` durable-object instances and
2435
+ // must run BEFORE any origin detection, handshake-session side effects, or
2436
+ // super/PartyServer routing — a pickup-anchor instance must never trip agent
2437
+ // init. Precedent: the `/_internal/delegation/*` routes handled further below.
2438
+ if (url.pathname.startsWith("/_internal/pickup/")) {
2439
+ // `this.ctx` is the McpAgent base's DurableObjectState, typed against the
2440
+ // SDK's bundled @cloudflare/workers-types; bridge it to this package's copy.
2441
+ return handlePickupInternal(this.ctx, request);
2442
+ }
2321
2443
  // Auto-detect and store server origin for consent URL building.
2322
2444
  // CRITICAL: When requests are routed through Cloudflare DO routing, the URL is
2323
2445
  // transformed to an internal URL (e.g., http://dummy-example.cloudflare.com).
@@ -2552,12 +2674,19 @@ export class MCPICloudflareAgent extends McpAgent {
2552
2674
  // This is called by consent.service.ts to store delegation tokens directly in DO storage.
2553
2675
  // Using DO storage instead of KV eliminates eventual consistency issues since
2554
2676
  // both storage and retrieval happen on the same DO instance (strongly consistent).
2555
- if (url.pathname === "/_internal/delegation/store" && request.method === "POST") {
2677
+ if (url.pathname === "/_internal/delegation/store" &&
2678
+ request.method === "POST") {
2556
2679
  logger.info("[MCPICloudflareAgent] Handling internal delegation store request");
2557
2680
  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" } });
2681
+ const body = (await request.json());
2682
+ if (!body.sessionId ||
2683
+ !body.delegationToken ||
2684
+ !body.delegationId ||
2685
+ !body.agentDid) {
2686
+ return new Response(JSON.stringify({
2687
+ success: false,
2688
+ error: "Missing required fields",
2689
+ }), { status: 400, headers: { "Content-Type": "application/json" } });
2561
2690
  }
2562
2691
  await this.storeDelegationToStorage(body);
2563
2692
  logger.info("[MCPICloudflareAgent] ✅ Delegation stored to DO storage:", {
@@ -2565,7 +2694,10 @@ export class MCPICloudflareAgent extends McpAgent {
2565
2694
  delegationId: body.delegationId,
2566
2695
  hasUserDid: !!body.userDid,
2567
2696
  });
2568
- return new Response(JSON.stringify({ success: true }), { status: 200, headers: { "Content-Type": "application/json" } });
2697
+ return new Response(JSON.stringify({ success: true }), {
2698
+ status: 200,
2699
+ headers: { "Content-Type": "application/json" },
2700
+ });
2569
2701
  }
2570
2702
  catch (error) {
2571
2703
  logger.error("[MCPICloudflareAgent] Delegation store request failed:", error);
@@ -2579,10 +2711,11 @@ export class MCPICloudflareAgent extends McpAgent {
2579
2711
  // This is called by getDelegationFromStorage() to read from singleton DO
2580
2712
  // when using singleton routing strategy. This ensures the agent reads from
2581
2713
  // the same DO that consent.service.ts wrote to.
2582
- if (url.pathname === "/_internal/delegation/get" && request.method === "POST") {
2714
+ if (url.pathname === "/_internal/delegation/get" &&
2715
+ request.method === "POST") {
2583
2716
  logger.debug("[MCPICloudflareAgent] Handling internal delegation get request");
2584
2717
  try {
2585
- const body = await request.json();
2718
+ const body = (await request.json());
2586
2719
  if (!body.sessionId) {
2587
2720
  return new Response(JSON.stringify({ success: false, error: "Missing sessionId" }), { status: 400, headers: { "Content-Type": "application/json" } });
2588
2721
  }
@@ -2613,7 +2746,10 @@ export class MCPICloudflareAgent extends McpAgent {
2613
2746
  hasUserDid: !!body.userDid,
2614
2747
  hasAgentDid: !!body.agentDid,
2615
2748
  });
2616
- return new Response(JSON.stringify({ success: true, data: null }), { status: 200, headers: { "Content-Type": "application/json" } });
2749
+ return new Response(JSON.stringify({ success: true, data: null }), {
2750
+ status: 200,
2751
+ headers: { "Content-Type": "application/json" },
2752
+ });
2617
2753
  }
2618
2754
  catch (error) {
2619
2755
  logger.error("[MCPICloudflareAgent] Delegation get request failed:", error);
@@ -2626,12 +2762,16 @@ export class MCPICloudflareAgent extends McpAgent {
2626
2762
  // Handle internal session mapping storage request
2627
2763
  // This is called to store MCP session ID → handshake session ID mapping in DO storage
2628
2764
  // for strong consistency (avoiding KV eventual consistency issues)
2629
- if (url.pathname === "/_internal/session-mapping/store" && request.method === "POST") {
2765
+ if (url.pathname === "/_internal/session-mapping/store" &&
2766
+ request.method === "POST") {
2630
2767
  logger.debug("[MCPICloudflareAgent] Handling internal session mapping store request");
2631
2768
  try {
2632
- const body = await request.json();
2769
+ const body = (await request.json());
2633
2770
  if (!body.mcpSessionId || !body.handshakeSessionId) {
2634
- return new Response(JSON.stringify({ success: false, error: "Missing required fields" }), { status: 400, headers: { "Content-Type": "application/json" } });
2771
+ return new Response(JSON.stringify({
2772
+ success: false,
2773
+ error: "Missing required fields",
2774
+ }), { status: 400, headers: { "Content-Type": "application/json" } });
2635
2775
  }
2636
2776
  // Store in DO storage
2637
2777
  // Note: DO storage doesn't support TTL like KV, but the mapping is cleaned up
@@ -2642,7 +2782,10 @@ export class MCPICloudflareAgent extends McpAgent {
2642
2782
  mcpSessionId: body.mcpSessionId.slice(0, 20) + "...",
2643
2783
  handshakeSessionId: body.handshakeSessionId.slice(0, 20) + "...",
2644
2784
  });
2645
- return new Response(JSON.stringify({ success: true }), { status: 200, headers: { "Content-Type": "application/json" } });
2785
+ return new Response(JSON.stringify({ success: true }), {
2786
+ status: 200,
2787
+ headers: { "Content-Type": "application/json" },
2788
+ });
2646
2789
  }
2647
2790
  catch (error) {
2648
2791
  logger.error("[MCPICloudflareAgent] Session mapping store request failed:", error);
@@ -2654,10 +2797,11 @@ export class MCPICloudflareAgent extends McpAgent {
2654
2797
  }
2655
2798
  // Handle internal session mapping retrieval request
2656
2799
  // 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") {
2800
+ if (url.pathname === "/_internal/session-mapping/get" &&
2801
+ request.method === "POST") {
2658
2802
  logger.debug("[MCPICloudflareAgent] Handling internal session mapping get request");
2659
2803
  try {
2660
- const body = await request.json();
2804
+ const body = (await request.json());
2661
2805
  if (!body.mcpSessionId) {
2662
2806
  return new Response(JSON.stringify({ success: false, error: "Missing mcpSessionId" }), { status: 400, headers: { "Content-Type": "application/json" } });
2663
2807
  }
@@ -2686,7 +2830,8 @@ export class MCPICloudflareAgent extends McpAgent {
2686
2830
  }
2687
2831
  // Handle internal client capabilities request
2688
2832
  // Returns stored MCP client capabilities from the initialize message
2689
- if (url.pathname === "/_internal/client-capabilities" && request.method === "GET") {
2833
+ if (url.pathname === "/_internal/client-capabilities" &&
2834
+ request.method === "GET") {
2690
2835
  try {
2691
2836
  const mcpClientInfo = await this.getMcpClientInfo();
2692
2837
  return new Response(JSON.stringify({
@@ -2922,7 +3067,7 @@ export class MCPICloudflareAgent extends McpAgent {
2922
3067
  body: JSON.stringify({ sessionId, userDid, agentDid }),
2923
3068
  });
2924
3069
  if (response.ok) {
2925
- const result = await response.json();
3070
+ const result = (await response.json());
2926
3071
  if (result.success && result.data) {
2927
3072
  logger.debug("[MCPICloudflareAgent] ✅ Delegation found via DO:", {
2928
3073
  sessionId: sessionId.slice(0, 20) + "...",