@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.
- package/dist/adapter.d.ts +2 -2
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +53 -25
- package/dist/adapter.js.map +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +204 -59
- 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/cache/kv-oauth-config-cache.d.ts +1 -1
- package/dist/cache/kv-oauth-config-cache.d.ts.map +1 -1
- package/dist/cache/kv-tool-protection-cache.d.ts +1 -1
- package/dist/cache/kv-tool-protection-cache.d.ts.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 +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -2
- package/dist/index.js.map +1 -1
- package/dist/needs-authorization-meta.d.ts +35 -0
- package/dist/needs-authorization-meta.d.ts.map +1 -0
- package/dist/needs-authorization-meta.js +50 -0
- package/dist/needs-authorization-meta.js.map +1 -0
- package/dist/proof-generator.d.ts +1 -1
- package/dist/proof-generator.d.ts.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 +45 -7
- 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 +3 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/services/admin.service.js +1 -1
- package/dist/services/admin.service.js.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 +5 -5
- package/dist/services/consent.service.d.ts.map +1 -1
- package/dist/services/consent.service.js +41 -52
- 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/idp-token-storage.d.ts +1 -1
- package/dist/services/idp-token-storage.d.ts.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/proof.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/env-secret-resolver.d.ts +18 -0
- package/dist/utils/env-secret-resolver.d.ts.map +1 -0
- package/dist/utils/env-secret-resolver.js +46 -0
- package/dist/utils/env-secret-resolver.js.map +1 -0
- 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/dist/utils/oauth-service-registry.d.ts +1 -1
- package/dist/utils/oauth-service-registry.d.ts.map +1 -1
- package/dist/utils/oauth-service-registry.js +1 -1
- package/dist/utils/oauth-service-registry.js.map +1 -1
- 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 {
|
|
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
|
|
28
|
-
import { didKeyFragment } from "@kya-os/mcp
|
|
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
|
|
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
|
-
|
|
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 >
|
|
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 ||
|
|
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-
|
|
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: "
|
|
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: "
|
|
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
|
-
|
|
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
|
-
}
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
}
|
|
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 {
|
|
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 {
|
|
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
|
|
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 ||
|
|
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
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
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 &&
|
|
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
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
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
|
|
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
|
-
|
|
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" &&
|
|
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 ||
|
|
2560
|
-
|
|
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 }), {
|
|
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" &&
|
|
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 }), {
|
|
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" &&
|
|
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({
|
|
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 }), {
|
|
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" &&
|
|
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" &&
|
|
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) + "...",
|