@kya-os/mcp-i-cloudflare 1.9.6 → 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 +9 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +203 -53
- 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 +48 -59
- 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,10 +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 {
|
|
29
|
+
import { shouldUsePopup, buildPopupResource } from "@kya-os/consent";
|
|
30
|
+
import { didKeyFragment } from "@kya-os/mcp";
|
|
28
31
|
import { calculateDOInstanceId, parseDORoutingStrategy, parseDOShardCount, } from "./utils/do-routing";
|
|
29
32
|
import { defaultProviderRegistry, } from "@kya-os/provider-registry";
|
|
30
|
-
import { hasApiKeyAuthorization, AuthRequiredError } from "@kya-os/contracts/tool-protection";
|
|
33
|
+
import { hasApiKeyAuthorization, AuthRequiredError, } from "@kya-os/contracts/tool-protection";
|
|
31
34
|
import { VaultResolver } from "./services/vault-resolver";
|
|
32
35
|
// CRITICAL: Force OAuth registry evaluation at module load time
|
|
33
36
|
// This creates an observable side effect that esbuild cannot tree-shake
|
|
@@ -183,7 +186,9 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
183
186
|
hasCapabilities: !!capabilities,
|
|
184
187
|
capabilityKeys: capabilities ? Object.keys(capabilities) : [],
|
|
185
188
|
hasExtensions: !!capabilities?.extensions,
|
|
186
|
-
extensionKeys: capabilities?.extensions
|
|
189
|
+
extensionKeys: capabilities?.extensions
|
|
190
|
+
? Object.keys(capabilities.extensions)
|
|
191
|
+
: [],
|
|
187
192
|
rawCapabilities: JSON.stringify(capabilities)?.substring(0, 500),
|
|
188
193
|
});
|
|
189
194
|
// Store client info for later retrieval (always, not just in dev)
|
|
@@ -243,7 +248,10 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
243
248
|
logger.info(`║ Client DID: ${clientIdentity.did.slice(0, 40).padEnd(42)}║`);
|
|
244
249
|
logger.info(`║ DID Source: ${clientIdentity.source.padEnd(42)}║`);
|
|
245
250
|
logger.info("╠════════════════════════════════════════════════════════╣");
|
|
246
|
-
|
|
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)}║`);
|
|
247
255
|
logger.info("╚════════════════════════════════════════════════════════╝");
|
|
248
256
|
logger.info("");
|
|
249
257
|
}
|
|
@@ -381,10 +389,10 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
381
389
|
: this.env;
|
|
382
390
|
// ✅ CRITICAL: Use same session ID resolution logic as executeToolWithProof
|
|
383
391
|
// This ensures session registration uses the same ID that proofs will use
|
|
384
|
-
// Priority: getSessionId() (from agents SDK) > DO ID fallback >
|
|
392
|
+
// Priority: getSessionId() (from agents SDK) > DO ID fallback > kyaos_{uuid}
|
|
385
393
|
const mcpSessionId = this.getSessionId();
|
|
386
394
|
const doId = this.ctx.id.toString();
|
|
387
|
-
const sessionId = mcpSessionId || doId ||
|
|
395
|
+
const sessionId = mcpSessionId || doId || `${SESSION_ID_PREFIX}${crypto.randomUUID()}`;
|
|
388
396
|
// Log session ID source for debugging (matches executeToolWithProof pattern)
|
|
389
397
|
if (this._environment === "development") {
|
|
390
398
|
logger.debug("[SessionRegistration] Session ID resolved:", {
|
|
@@ -507,6 +515,17 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
507
515
|
setClientMessagesConfig(config) {
|
|
508
516
|
this._clientMessagesConfig = config;
|
|
509
517
|
}
|
|
518
|
+
/**
|
|
519
|
+
* Whether to attach a popup-launch hint when this client supports it.
|
|
520
|
+
* Subclasses may override; defaults to `true` so popup-aware clients
|
|
521
|
+
* (Claude Desktop with `.mcpb`) get the stripped-chrome popup
|
|
522
|
+
* automatically. Operators can disable per-deployment by overriding
|
|
523
|
+
* this method or the `popupEnabled` field in the resolved consent
|
|
524
|
+
* config.
|
|
525
|
+
*/
|
|
526
|
+
get popupEnabledForClient() {
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
510
529
|
/**
|
|
511
530
|
* Initialize the agent
|
|
512
531
|
* Call this after construction to set up the runtime and register tools
|
|
@@ -548,7 +567,7 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
548
567
|
const [extApps, consentMcpApp, extAppsConstants] = await Promise.all([
|
|
549
568
|
import("@modelcontextprotocol/ext-apps/server").catch(() => null),
|
|
550
569
|
import("@kya-os/consent/mcp-app").catch(() => null),
|
|
551
|
-
import("@kya-os/mcp-i-
|
|
570
|
+
import("@kya-os/mcp-i-runtime/runtime/ext-apps-constants").catch(() => null),
|
|
552
571
|
]);
|
|
553
572
|
if (!extApps || !consentMcpApp || !extAppsConstants) {
|
|
554
573
|
logger.info("[MCPICloudflareAgent] MCP Apps consent UI not available (missing optional dependencies)");
|
|
@@ -797,7 +816,7 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
797
816
|
send({
|
|
798
817
|
jsonrpc: "2.0", id: reqId, method: "tools/call",
|
|
799
818
|
params: {
|
|
800
|
-
name: "
|
|
819
|
+
name: "_kyaos_credential_auth",
|
|
801
820
|
arguments: {
|
|
802
821
|
tool: consentData.tool,
|
|
803
822
|
scopes: consentData.scopes,
|
|
@@ -815,7 +834,7 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
815
834
|
send({
|
|
816
835
|
jsonrpc: "2.0", id: reqId, method: "tools/call",
|
|
817
836
|
params: {
|
|
818
|
-
name: "
|
|
837
|
+
name: "_kyaos_approve_consent",
|
|
819
838
|
arguments: {
|
|
820
839
|
tool: consentData.tool,
|
|
821
840
|
scopes: consentData.scopes,
|
|
@@ -903,14 +922,21 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
903
922
|
// The sandboxed MCP Apps iframe cannot call fetch() directly, so it
|
|
904
923
|
// uses tools/call (proxied via postMessage through the host) to invoke
|
|
905
924
|
// this tool which handles the /consent/approve logic server-side.
|
|
906
|
-
|
|
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 = {
|
|
907
932
|
tool: z.string(),
|
|
908
933
|
scopes: z.array(z.string()),
|
|
909
934
|
session_id: z.string(),
|
|
910
935
|
agent_did: z.string(),
|
|
911
936
|
project_id: z.string(),
|
|
912
937
|
resume_token: z.string(),
|
|
913
|
-
}
|
|
938
|
+
};
|
|
939
|
+
const approveConsentHandler = async (args) => {
|
|
914
940
|
const envPrefix = this.getEnvPrefix();
|
|
915
941
|
const mappedEnv = envPrefix
|
|
916
942
|
? mapPrefixedEnv(this.env, envPrefix)
|
|
@@ -918,7 +944,17 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
918
944
|
const serverUrl = mappedEnv.MCP_SERVER_URL ??
|
|
919
945
|
this._autoDetectedOrigin;
|
|
920
946
|
if (!serverUrl) {
|
|
921
|
-
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
|
+
};
|
|
922
958
|
}
|
|
923
959
|
try {
|
|
924
960
|
const resp = await fetch(`${serverUrl}/consent/approve`, {
|
|
@@ -936,21 +972,48 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
936
972
|
}),
|
|
937
973
|
});
|
|
938
974
|
if (!resp.ok) {
|
|
939
|
-
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
|
+
};
|
|
940
986
|
}
|
|
941
987
|
const data = await resp.json();
|
|
942
988
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
943
989
|
}
|
|
944
990
|
catch (err) {
|
|
945
991
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
946
|
-
return {
|
|
992
|
+
return {
|
|
993
|
+
content: [
|
|
994
|
+
{
|
|
995
|
+
type: "text",
|
|
996
|
+
text: JSON.stringify({ success: false, error: msg }),
|
|
997
|
+
},
|
|
998
|
+
],
|
|
999
|
+
};
|
|
947
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);
|
|
948
1008
|
});
|
|
949
1009
|
// Register internal tool for iframe-based credential authentication.
|
|
950
1010
|
// Same sandbox workaround as _mcpi_approve_consent: the iframe calls
|
|
951
1011
|
// tools/call via postMessage, which the host proxies to this tool.
|
|
952
1012
|
// This tool does credential auth + delegation in a single step.
|
|
953
|
-
|
|
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 = {
|
|
954
1017
|
tool: z.string(),
|
|
955
1018
|
scopes: z.array(z.string()),
|
|
956
1019
|
session_id: z.string(),
|
|
@@ -960,7 +1023,8 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
960
1023
|
username: z.string(),
|
|
961
1024
|
password: z.string(),
|
|
962
1025
|
provider: z.string(),
|
|
963
|
-
}
|
|
1026
|
+
};
|
|
1027
|
+
const credentialAuthHandler = async (args) => {
|
|
964
1028
|
const envPrefix = this.getEnvPrefix();
|
|
965
1029
|
const mappedEnv = envPrefix
|
|
966
1030
|
? mapPrefixedEnv(this.env, envPrefix)
|
|
@@ -1000,15 +1064,39 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
1000
1064
|
}),
|
|
1001
1065
|
});
|
|
1002
1066
|
if (!resp.ok) {
|
|
1003
|
-
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
|
+
};
|
|
1004
1078
|
}
|
|
1005
1079
|
const data = await resp.json();
|
|
1006
1080
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
1007
1081
|
}
|
|
1008
1082
|
catch (err) {
|
|
1009
1083
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
1010
|
-
return {
|
|
1084
|
+
return {
|
|
1085
|
+
content: [
|
|
1086
|
+
{
|
|
1087
|
+
type: "text",
|
|
1088
|
+
text: JSON.stringify({ success: false, error: msg }),
|
|
1089
|
+
},
|
|
1090
|
+
],
|
|
1091
|
+
};
|
|
1011
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);
|
|
1012
1100
|
});
|
|
1013
1101
|
logger.info("[MCPICloudflareAgent] Registered MCP Apps consent UI resource");
|
|
1014
1102
|
}
|
|
@@ -1116,7 +1204,8 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
1116
1204
|
// ✅ Automatically extract sessionId from ToolExtraArguments
|
|
1117
1205
|
// This ensures delegation tokens stored with the original session ID
|
|
1118
1206
|
// can be retrieved on subsequent tool calls
|
|
1119
|
-
const sessionId = extra
|
|
1207
|
+
const sessionId = extra
|
|
1208
|
+
?.sessionId;
|
|
1120
1209
|
return this.executeToolWithProof(name, args, handler, sessionId);
|
|
1121
1210
|
};
|
|
1122
1211
|
// Build tool config with optional MCP Apps UI metadata
|
|
@@ -1193,7 +1282,7 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
1193
1282
|
body: JSON.stringify({ mcpSessionId: mcpSdkSessionId }),
|
|
1194
1283
|
});
|
|
1195
1284
|
if (response.ok) {
|
|
1196
|
-
const result = await response.json();
|
|
1285
|
+
const result = (await response.json());
|
|
1197
1286
|
if (result.success && result.handshakeSessionId) {
|
|
1198
1287
|
handshakeSessionId = result.handshakeSessionId;
|
|
1199
1288
|
if (this._environment === "development") {
|
|
@@ -1238,15 +1327,26 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
1238
1327
|
sessionId: handshakeSessionId.slice(0, 20) + "...",
|
|
1239
1328
|
reason: validationResult.reason,
|
|
1240
1329
|
});
|
|
1241
|
-
// 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.
|
|
1242
1334
|
sessionId =
|
|
1243
|
-
providedSessionId ||
|
|
1335
|
+
providedSessionId ||
|
|
1336
|
+
mcpSdkSessionId ||
|
|
1337
|
+
this.ctx.id.toString() ||
|
|
1338
|
+
`${SESSION_ID_PREFIX}${crypto.randomUUID()}`;
|
|
1244
1339
|
}
|
|
1245
1340
|
}
|
|
1246
1341
|
else {
|
|
1247
|
-
// 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.
|
|
1248
1345
|
sessionId =
|
|
1249
|
-
providedSessionId ||
|
|
1346
|
+
providedSessionId ||
|
|
1347
|
+
mcpSdkSessionId ||
|
|
1348
|
+
this.ctx.id.toString() ||
|
|
1349
|
+
`${SESSION_ID_PREFIX}${crypto.randomUUID()}`;
|
|
1250
1350
|
}
|
|
1251
1351
|
// Log session ID source for debugging
|
|
1252
1352
|
if (this._environment === "development") {
|
|
@@ -1302,8 +1402,8 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
1302
1402
|
if (delegationStorage) {
|
|
1303
1403
|
const identityKey = STORAGE_KEYS.sessionIdentity(sessionId);
|
|
1304
1404
|
const sessionKey = STORAGE_KEYS.session(sessionId);
|
|
1305
|
-
const identityData = await delegationStorage.get(identityKey, "json");
|
|
1306
|
-
const sessionData = await delegationStorage.get(sessionKey, "json");
|
|
1405
|
+
const identityData = (await delegationStorage.get(identityKey, "json"));
|
|
1406
|
+
const sessionData = (await delegationStorage.get(sessionKey, "json"));
|
|
1307
1407
|
userDid = identityData?.userDid || sessionData?.userDid;
|
|
1308
1408
|
clientDid = sessionData?.clientDid;
|
|
1309
1409
|
}
|
|
@@ -1433,7 +1533,10 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
1433
1533
|
const scopes = credError.toolProtection?.requiredScopes || [
|
|
1434
1534
|
`${toolName}:execute`,
|
|
1435
1535
|
];
|
|
1436
|
-
|
|
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");
|
|
1437
1540
|
// Try MCP Apps inline credential UI first (preferred path)
|
|
1438
1541
|
// Note: clientCapabilities not available in this context — pass undefined.
|
|
1439
1542
|
// buildConsentUIResult gates on isExtAppsAvailable(), not client caps.
|
|
@@ -1528,7 +1631,10 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
1528
1631
|
isCredentialProvider,
|
|
1529
1632
|
});
|
|
1530
1633
|
let authUrl;
|
|
1531
|
-
|
|
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");
|
|
1532
1638
|
if (isCredentialProvider) {
|
|
1533
1639
|
// Try MCP Apps inline credential UI first
|
|
1534
1640
|
const inlineResult = this.mcpiRuntime?.buildConsentUIResult(oauthError.toolName, oauthError.requiredScopes, session, resumeToken, session.projectId, session.serverOrigin, undefined, "credentials", oauthError.provider || "credentials");
|
|
@@ -1581,7 +1687,9 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
1581
1687
|
// we MUST re-throw the original error to prevent unauthorized tool execution.
|
|
1582
1688
|
// Only log and continue for non-auth errors (backward compatibility).
|
|
1583
1689
|
// Note: pendingInlineResult means auth is handled via inline UI — skip re-throw.
|
|
1584
|
-
if (!pendingAuthError &&
|
|
1690
|
+
if (!pendingAuthError &&
|
|
1691
|
+
!pendingInlineResult &&
|
|
1692
|
+
!pendingAuthRequiredError) {
|
|
1585
1693
|
if (originalAuthError) {
|
|
1586
1694
|
// Auth was required but URL building failed - MUST block execution
|
|
1587
1695
|
logger.error("[MCPICloudflareAgent] SECURITY: Auth required but URL building failed, blocking tool execution:", { originalError: originalAuthError.message, buildError: error });
|
|
@@ -1667,12 +1775,18 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
1667
1775
|
scopes,
|
|
1668
1776
|
clientId,
|
|
1669
1777
|
});
|
|
1778
|
+
// Popup launch — only attached as a hint in `_meta` so popup-aware
|
|
1779
|
+
// clients (today: `.mcpb`-installed Claude Desktop) can open the
|
|
1780
|
+
// consent URL in a stripped-chrome window. Standard clients ignore
|
|
1781
|
+
// the hint and surface the markdown link as before. The popup hint
|
|
1782
|
+
// is gated on `popupEnabled` from the resolved consent config so
|
|
1783
|
+
// operators can opt out per-deployment.
|
|
1784
|
+
const popupEligible = consentUrl && shouldUsePopup(clientId, this.popupEnabledForClient);
|
|
1785
|
+
const popupResource = popupEligible
|
|
1786
|
+
? buildPopupResource(consentUrl)
|
|
1787
|
+
: undefined;
|
|
1670
1788
|
// Return tool result with isError: true so MCP clients (Claude Desktop, Inspector)
|
|
1671
1789
|
// surface the consent URL to users and trigger browser popup for authorization.
|
|
1672
|
-
// TODO(ext-apps): When MCP-UI / ext-apps support lands, make this dynamic:
|
|
1673
|
-
// - isError: false when the client supports inline consent UI (MCP Apps extension)
|
|
1674
|
-
// - isError: true as fallback for standard MCP clients (Claude Desktop, etc.)
|
|
1675
|
-
// See PR #274 for the inline consent UI implementation.
|
|
1676
1790
|
return {
|
|
1677
1791
|
content: [
|
|
1678
1792
|
{
|
|
@@ -1688,6 +1802,7 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
1688
1802
|
consentUrl,
|
|
1689
1803
|
authorizationUrl: consentUrl,
|
|
1690
1804
|
resumeToken,
|
|
1805
|
+
...(popupResource ? { popupResource } : {}),
|
|
1691
1806
|
},
|
|
1692
1807
|
};
|
|
1693
1808
|
}
|
|
@@ -1943,7 +2058,9 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
1943
2058
|
if (!this._vaultResolver) {
|
|
1944
2059
|
const identity = await this.mcpiRuntime.getIdentity();
|
|
1945
2060
|
// Decode base64/base64url private key to bytes (self-contained, no private method access)
|
|
1946
|
-
const b64 = identity.privateKey
|
|
2061
|
+
const b64 = identity.privateKey
|
|
2062
|
+
.replace(/-/g, "+")
|
|
2063
|
+
.replace(/_/g, "/");
|
|
1947
2064
|
const padded = b64 + "=".repeat((4 - (b64.length % 4)) % 4);
|
|
1948
2065
|
const binaryStr = atob(padded);
|
|
1949
2066
|
const keyBytes = new Uint8Array(binaryStr.length);
|
|
@@ -1960,8 +2077,8 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
1960
2077
|
: keyBytes;
|
|
1961
2078
|
// Wrap as PKCS#8 for crypto.subtle.importKey
|
|
1962
2079
|
const pkcs8Header = new Uint8Array([
|
|
1963
|
-
0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05,
|
|
1964
|
-
|
|
2080
|
+
0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65,
|
|
2081
|
+
0x70, 0x04, 0x22, 0x04, 0x20,
|
|
1965
2082
|
]);
|
|
1966
2083
|
const pkcs8 = new Uint8Array(pkcs8Header.length + rawKey.length);
|
|
1967
2084
|
pkcs8.set(pkcs8Header);
|
|
@@ -2299,6 +2416,16 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
2299
2416
|
*/
|
|
2300
2417
|
async fetch(request) {
|
|
2301
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
|
+
}
|
|
2302
2429
|
// Auto-detect and store server origin for consent URL building.
|
|
2303
2430
|
// CRITICAL: When requests are routed through Cloudflare DO routing, the URL is
|
|
2304
2431
|
// transformed to an internal URL (e.g., http://dummy-example.cloudflare.com).
|
|
@@ -2533,12 +2660,19 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
2533
2660
|
// This is called by consent.service.ts to store delegation tokens directly in DO storage.
|
|
2534
2661
|
// Using DO storage instead of KV eliminates eventual consistency issues since
|
|
2535
2662
|
// both storage and retrieval happen on the same DO instance (strongly consistent).
|
|
2536
|
-
if (url.pathname === "/_internal/delegation/store" &&
|
|
2663
|
+
if (url.pathname === "/_internal/delegation/store" &&
|
|
2664
|
+
request.method === "POST") {
|
|
2537
2665
|
logger.info("[MCPICloudflareAgent] Handling internal delegation store request");
|
|
2538
2666
|
try {
|
|
2539
|
-
const body = await request.json();
|
|
2540
|
-
if (!body.sessionId ||
|
|
2541
|
-
|
|
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" } });
|
|
2542
2676
|
}
|
|
2543
2677
|
await this.storeDelegationToStorage(body);
|
|
2544
2678
|
logger.info("[MCPICloudflareAgent] ✅ Delegation stored to DO storage:", {
|
|
@@ -2546,7 +2680,10 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
2546
2680
|
delegationId: body.delegationId,
|
|
2547
2681
|
hasUserDid: !!body.userDid,
|
|
2548
2682
|
});
|
|
2549
|
-
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
|
+
});
|
|
2550
2687
|
}
|
|
2551
2688
|
catch (error) {
|
|
2552
2689
|
logger.error("[MCPICloudflareAgent] Delegation store request failed:", error);
|
|
@@ -2560,10 +2697,11 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
2560
2697
|
// This is called by getDelegationFromStorage() to read from singleton DO
|
|
2561
2698
|
// when using singleton routing strategy. This ensures the agent reads from
|
|
2562
2699
|
// the same DO that consent.service.ts wrote to.
|
|
2563
|
-
if (url.pathname === "/_internal/delegation/get" &&
|
|
2700
|
+
if (url.pathname === "/_internal/delegation/get" &&
|
|
2701
|
+
request.method === "POST") {
|
|
2564
2702
|
logger.debug("[MCPICloudflareAgent] Handling internal delegation get request");
|
|
2565
2703
|
try {
|
|
2566
|
-
const body = await request.json();
|
|
2704
|
+
const body = (await request.json());
|
|
2567
2705
|
if (!body.sessionId) {
|
|
2568
2706
|
return new Response(JSON.stringify({ success: false, error: "Missing sessionId" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
2569
2707
|
}
|
|
@@ -2594,7 +2732,10 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
2594
2732
|
hasUserDid: !!body.userDid,
|
|
2595
2733
|
hasAgentDid: !!body.agentDid,
|
|
2596
2734
|
});
|
|
2597
|
-
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
|
+
});
|
|
2598
2739
|
}
|
|
2599
2740
|
catch (error) {
|
|
2600
2741
|
logger.error("[MCPICloudflareAgent] Delegation get request failed:", error);
|
|
@@ -2607,12 +2748,16 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
2607
2748
|
// Handle internal session mapping storage request
|
|
2608
2749
|
// This is called to store MCP session ID → handshake session ID mapping in DO storage
|
|
2609
2750
|
// for strong consistency (avoiding KV eventual consistency issues)
|
|
2610
|
-
if (url.pathname === "/_internal/session-mapping/store" &&
|
|
2751
|
+
if (url.pathname === "/_internal/session-mapping/store" &&
|
|
2752
|
+
request.method === "POST") {
|
|
2611
2753
|
logger.debug("[MCPICloudflareAgent] Handling internal session mapping store request");
|
|
2612
2754
|
try {
|
|
2613
|
-
const body = await request.json();
|
|
2755
|
+
const body = (await request.json());
|
|
2614
2756
|
if (!body.mcpSessionId || !body.handshakeSessionId) {
|
|
2615
|
-
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" } });
|
|
2616
2761
|
}
|
|
2617
2762
|
// Store in DO storage
|
|
2618
2763
|
// Note: DO storage doesn't support TTL like KV, but the mapping is cleaned up
|
|
@@ -2623,7 +2768,10 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
2623
2768
|
mcpSessionId: body.mcpSessionId.slice(0, 20) + "...",
|
|
2624
2769
|
handshakeSessionId: body.handshakeSessionId.slice(0, 20) + "...",
|
|
2625
2770
|
});
|
|
2626
|
-
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
|
+
});
|
|
2627
2775
|
}
|
|
2628
2776
|
catch (error) {
|
|
2629
2777
|
logger.error("[MCPICloudflareAgent] Session mapping store request failed:", error);
|
|
@@ -2635,10 +2783,11 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
2635
2783
|
}
|
|
2636
2784
|
// Handle internal session mapping retrieval request
|
|
2637
2785
|
// This is called to retrieve MCP session ID → handshake session ID mapping from DO storage
|
|
2638
|
-
if (url.pathname === "/_internal/session-mapping/get" &&
|
|
2786
|
+
if (url.pathname === "/_internal/session-mapping/get" &&
|
|
2787
|
+
request.method === "POST") {
|
|
2639
2788
|
logger.debug("[MCPICloudflareAgent] Handling internal session mapping get request");
|
|
2640
2789
|
try {
|
|
2641
|
-
const body = await request.json();
|
|
2790
|
+
const body = (await request.json());
|
|
2642
2791
|
if (!body.mcpSessionId) {
|
|
2643
2792
|
return new Response(JSON.stringify({ success: false, error: "Missing mcpSessionId" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
2644
2793
|
}
|
|
@@ -2667,7 +2816,8 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
2667
2816
|
}
|
|
2668
2817
|
// Handle internal client capabilities request
|
|
2669
2818
|
// Returns stored MCP client capabilities from the initialize message
|
|
2670
|
-
if (url.pathname === "/_internal/client-capabilities" &&
|
|
2819
|
+
if (url.pathname === "/_internal/client-capabilities" &&
|
|
2820
|
+
request.method === "GET") {
|
|
2671
2821
|
try {
|
|
2672
2822
|
const mcpClientInfo = await this.getMcpClientInfo();
|
|
2673
2823
|
return new Response(JSON.stringify({
|
|
@@ -2903,7 +3053,7 @@ export class MCPICloudflareAgent extends McpAgent {
|
|
|
2903
3053
|
body: JSON.stringify({ sessionId, userDid, agentDid }),
|
|
2904
3054
|
});
|
|
2905
3055
|
if (response.ok) {
|
|
2906
|
-
const result = await response.json();
|
|
3056
|
+
const result = (await response.json());
|
|
2907
3057
|
if (result.success && result.data) {
|
|
2908
3058
|
logger.debug("[MCPICloudflareAgent] ✅ Delegation found via DO:", {
|
|
2909
3059
|
sessionId: sessionId.slice(0, 20) + "...",
|