@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.
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +47 -16
- package/dist/adapter.js.map +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +181 -50
- package/dist/agent.js.map +1 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +86 -3
- package/dist/app.js.map +1 -1
- package/dist/config.d.ts +54 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +58 -0
- package/dist/config.js.map +1 -1
- package/dist/constants/storage-keys.d.ts +6 -0
- package/dist/constants/storage-keys.d.ts.map +1 -1
- package/dist/constants/storage-keys.js +6 -0
- package/dist/constants/storage-keys.js.map +1 -1
- package/dist/delegation-http/anchor-do.d.ts +84 -0
- package/dist/delegation-http/anchor-do.d.ts.map +1 -0
- package/dist/delegation-http/anchor-do.js +253 -0
- package/dist/delegation-http/anchor-do.js.map +1 -0
- package/dist/delegation-http/anchor-routing.d.ts +20 -0
- package/dist/delegation-http/anchor-routing.d.ts.map +1 -0
- package/dist/delegation-http/anchor-routing.js +27 -0
- package/dist/delegation-http/anchor-routing.js.map +1 -0
- package/dist/delegation-http/bearer-retry.d.ts +54 -0
- package/dist/delegation-http/bearer-retry.d.ts.map +1 -0
- package/dist/delegation-http/bearer-retry.js +195 -0
- package/dist/delegation-http/bearer-retry.js.map +1 -0
- package/dist/delegation-http/challenge-middleware.d.ts +28 -0
- package/dist/delegation-http/challenge-middleware.d.ts.map +1 -0
- package/dist/delegation-http/challenge-middleware.js +190 -0
- package/dist/delegation-http/challenge-middleware.js.map +1 -0
- package/dist/delegation-http/challenge.d.ts +86 -0
- package/dist/delegation-http/challenge.d.ts.map +1 -0
- package/dist/delegation-http/challenge.js +173 -0
- package/dist/delegation-http/challenge.js.map +1 -0
- package/dist/delegation-http/consent-anchor-bridge.d.ts +35 -0
- package/dist/delegation-http/consent-anchor-bridge.d.ts.map +1 -0
- package/dist/delegation-http/consent-anchor-bridge.js +355 -0
- package/dist/delegation-http/consent-anchor-bridge.js.map +1 -0
- package/dist/delegation-http/constants.d.ts +51 -0
- package/dist/delegation-http/constants.d.ts.map +1 -0
- package/dist/delegation-http/constants.js +60 -0
- package/dist/delegation-http/constants.js.map +1 -0
- package/dist/delegation-http/hash.d.ts +21 -0
- package/dist/delegation-http/hash.d.ts.map +1 -0
- package/dist/delegation-http/hash.js +92 -0
- package/dist/delegation-http/hash.js.map +1 -0
- package/dist/delegation-http/pickup-routes.d.ts +42 -0
- package/dist/delegation-http/pickup-routes.d.ts.map +1 -0
- package/dist/delegation-http/pickup-routes.js +79 -0
- package/dist/delegation-http/pickup-routes.js.map +1 -0
- package/dist/delegation-http/register.d.ts +19 -0
- package/dist/delegation-http/register.d.ts.map +1 -0
- package/dist/delegation-http/register.js +72 -0
- package/dist/delegation-http/register.js.map +1 -0
- package/dist/delegation-http/sid.d.ts +20 -0
- package/dist/delegation-http/sid.d.ts.map +1 -0
- package/dist/delegation-http/sid.js +32 -0
- package/dist/delegation-http/sid.js.map +1 -0
- package/dist/delegation-http/types.d.ts +64 -0
- package/dist/delegation-http/types.d.ts.map +1 -0
- package/dist/delegation-http/types.js +11 -0
- package/dist/delegation-http/types.js.map +1 -0
- package/dist/helpers/env-mapper.d.ts.map +1 -1
- package/dist/helpers/env-mapper.js +24 -0
- package/dist/helpers/env-mapper.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/index.js.map +1 -1
- package/dist/providers/crypto.d.ts +1 -1
- package/dist/providers/crypto.d.ts.map +1 -1
- package/dist/providers/crypto.js +1 -1
- package/dist/providers/crypto.js.map +1 -1
- package/dist/providers/kv-identity.d.ts +2 -2
- package/dist/providers/kv-identity.d.ts.map +1 -1
- package/dist/providers/kv-identity.js +1 -1
- package/dist/providers/kv-identity.js.map +1 -1
- package/dist/providers/storage.d.ts +1 -1
- package/dist/providers/storage.d.ts.map +1 -1
- package/dist/providers/storage.js +1 -1
- package/dist/providers/storage.js.map +1 -1
- package/dist/runtime/audit-logger.d.ts +1 -1
- package/dist/runtime/audit-logger.d.ts.map +1 -1
- package/dist/runtime/oauth-handler.d.ts.map +1 -1
- package/dist/runtime/oauth-handler.js +31 -3
- package/dist/runtime/oauth-handler.js.map +1 -1
- package/dist/runtime/oidc/authorize-url.d.ts +19 -0
- package/dist/runtime/oidc/authorize-url.d.ts.map +1 -0
- package/dist/runtime/oidc/authorize-url.js +94 -0
- package/dist/runtime/oidc/authorize-url.js.map +1 -0
- package/dist/runtime/oidc/token-exchange.d.ts +62 -0
- package/dist/runtime/oidc/token-exchange.d.ts.map +1 -0
- package/dist/runtime/oidc/token-exchange.js +75 -0
- package/dist/runtime/oidc/token-exchange.js.map +1 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/services/consent-audit.service.d.ts +1 -1
- package/dist/services/consent-audit.service.d.ts.map +1 -1
- package/dist/services/consent-audit.service.js.map +1 -1
- package/dist/services/consent.service.d.ts +2 -2
- package/dist/services/consent.service.d.ts.map +1 -1
- package/dist/services/consent.service.js +39 -50
- package/dist/services/consent.service.js.map +1 -1
- package/dist/services/delegation.service.d.ts +9 -0
- package/dist/services/delegation.service.d.ts.map +1 -1
- package/dist/services/delegation.service.js +21 -7
- package/dist/services/delegation.service.js.map +1 -1
- package/dist/services/oauth-security.service.d.ts.map +1 -1
- package/dist/services/oauth-security.service.js +6 -10
- package/dist/services/oauth-security.service.js.map +1 -1
- package/dist/services/provider-loader.service.js +1 -1
- package/dist/services/provider-loader.service.js.map +1 -1
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/known-clients.d.ts.map +1 -1
- package/dist/utils/known-clients.js +19 -10
- package/dist/utils/known-clients.js.map +1 -1
- 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 {
|
|
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
|
|
28
|
-
import { didKeyFragment } from "@kya-os/mcp
|
|
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
|
|
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
|
-
|
|
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 >
|
|
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 ||
|
|
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-
|
|
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: "
|
|
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: "
|
|
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
|
-
|
|
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
|
-
}
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
}
|
|
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 {
|
|
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 {
|
|
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
|
|
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 ||
|
|
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
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
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 &&
|
|
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
|
|
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
|
-
|
|
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" &&
|
|
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 ||
|
|
2560
|
-
|
|
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 }), {
|
|
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" &&
|
|
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 }), {
|
|
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" &&
|
|
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({
|
|
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 }), {
|
|
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" &&
|
|
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" &&
|
|
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) + "...",
|