@kya-os/create-mcpi-app 1.7.17 → 1.7.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test$colon$coverage.log +315 -0
  3. package/.turbo/turbo-test.log +95 -0
  4. package/CHANGELOG.md +372 -0
  5. package/IMPLEMENTATION_SUMMARY.md +108 -0
  6. package/REMEDIATION_PLAN.md +99 -0
  7. package/coverage/base.css +224 -0
  8. package/coverage/block-navigation.js +87 -0
  9. package/coverage/clover.xml +252 -0
  10. package/coverage/config-builder.ts.html +580 -0
  11. package/coverage/coverage-final.json +7 -0
  12. package/coverage/favicon.png +0 -0
  13. package/coverage/fetch-cloudflare-mcpi-template.ts.html +7006 -0
  14. package/coverage/generate-config.ts.html +436 -0
  15. package/coverage/generate-identity.ts.html +574 -0
  16. package/coverage/index.html +191 -0
  17. package/coverage/install.ts.html +322 -0
  18. package/coverage/prettify.css +1 -0
  19. package/coverage/prettify.js +2 -0
  20. package/coverage/sort-arrow-sprite.png +0 -0
  21. package/coverage/sorter.js +210 -0
  22. package/coverage/validate-project-structure.ts.html +466 -0
  23. package/dist/.tsbuildinfo +1 -1
  24. package/dist/helpers/__tests__/config-builder.spec.d.ts +8 -0
  25. package/dist/helpers/__tests__/config-builder.spec.d.ts.map +1 -0
  26. package/dist/helpers/__tests__/config-builder.spec.js +182 -0
  27. package/dist/helpers/__tests__/config-builder.spec.js.map +1 -0
  28. package/dist/helpers/config-builder.d.ts +58 -0
  29. package/dist/helpers/config-builder.d.ts.map +1 -0
  30. package/dist/helpers/config-builder.js +102 -0
  31. package/dist/helpers/config-builder.js.map +1 -0
  32. package/dist/helpers/create.d.ts +1 -0
  33. package/dist/helpers/create.d.ts.map +1 -1
  34. package/dist/helpers/create.js +2 -1
  35. package/dist/helpers/create.js.map +1 -1
  36. package/dist/helpers/fetch-cloudflare-mcpi-template.d.ts +1 -0
  37. package/dist/helpers/fetch-cloudflare-mcpi-template.d.ts.map +1 -1
  38. package/dist/helpers/fetch-cloudflare-mcpi-template.js +209 -174
  39. package/dist/helpers/fetch-cloudflare-mcpi-template.js.map +1 -1
  40. package/dist/helpers/fetch-mcpi-template.d.ts.map +1 -1
  41. package/dist/helpers/fetch-mcpi-template.js +18 -3
  42. package/dist/helpers/fetch-mcpi-template.js.map +1 -1
  43. package/dist/helpers/generate-config.d.ts.map +1 -1
  44. package/dist/helpers/generate-config.js +27 -40
  45. package/dist/helpers/generate-config.js.map +1 -1
  46. package/dist/helpers/install.js +5 -0
  47. package/dist/helpers/install.js.map +1 -1
  48. package/dist/index.js +2 -0
  49. package/dist/index.js.map +1 -1
  50. package/package.json +18 -9
  51. package/scripts/prepare-pack.js +47 -0
  52. package/scripts/validate-no-workspace.js +79 -0
  53. package/src/__tests__/cloudflare-template.test.ts +488 -0
  54. package/src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts +337 -0
  55. package/src/__tests__/helpers/generate-config.test.ts +312 -0
  56. package/src/__tests__/helpers/generate-identity.test.ts +271 -0
  57. package/src/__tests__/helpers/install.test.ts +362 -0
  58. package/src/__tests__/helpers/validate-project-structure.test.ts +467 -0
  59. package/src/__tests__.bak/regression.test.ts +434 -0
  60. package/src/effects/index.ts +80 -0
  61. package/src/helpers/__tests__/config-builder.spec.ts +231 -0
  62. package/src/helpers/apply-identity-preset.ts +209 -0
  63. package/src/helpers/config-builder.ts +165 -0
  64. package/src/helpers/copy-template.ts +11 -0
  65. package/src/helpers/create.ts +239 -0
  66. package/src/helpers/fetch-cloudflare-mcpi-template.ts +2311 -0
  67. package/src/helpers/fetch-cloudflare-template.ts +361 -0
  68. package/src/helpers/fetch-mcpi-template.ts +236 -0
  69. package/src/helpers/fetch-xmcp-template.ts +153 -0
  70. package/src/helpers/generate-config.ts +117 -0
  71. package/src/helpers/generate-identity.ts +163 -0
  72. package/src/helpers/identity-manager.ts +186 -0
  73. package/src/helpers/install.ts +79 -0
  74. package/src/helpers/rename.ts +17 -0
  75. package/src/helpers/validate-project-structure.ts +127 -0
  76. package/src/index.ts +480 -0
  77. package/src/utils/check-node.ts +17 -0
  78. package/src/utils/is-folder-empty.ts +60 -0
  79. package/src/utils/validate-project-name.ts +132 -0
  80. package/test-cloudflare/README.md +164 -0
  81. package/test-cloudflare/package.json +28 -0
  82. package/test-cloudflare/src/index.ts +340 -0
  83. package/test-cloudflare/src/tools/greet.ts +19 -0
  84. package/test-cloudflare/tests/cache-invalidation.test.ts +410 -0
  85. package/test-cloudflare/tests/cors-security.test.ts +349 -0
  86. package/test-cloudflare/tests/delegation.test.ts +335 -0
  87. package/test-cloudflare/tests/do-routing.test.ts +314 -0
  88. package/test-cloudflare/tests/integration.test.ts +205 -0
  89. package/test-cloudflare/tests/session-management.test.ts +359 -0
  90. package/test-cloudflare/tsconfig.json +22 -0
  91. package/test-cloudflare/vitest.config.ts +9 -0
  92. package/test-cloudflare/wrangler.toml +37 -0
  93. package/test-node/README.md +44 -0
  94. package/test-node/package.json +23 -0
  95. package/test-node/src/tools/greet.ts +25 -0
  96. package/test-node/xmcp.config.ts +20 -0
  97. package/tsconfig.json +26 -0
  98. package/vitest.config.ts +14 -0
@@ -7,7 +7,7 @@ import { generateIdentity } from "./generate-identity.js";
7
7
  * Uses McpAgent from agents/mcp for MCP protocol support
8
8
  */
9
9
  export async function fetchCloudflareMcpiTemplate(projectPath, options = {}) {
10
- const { packageManager = "npm", projectName = path.basename(projectPath), apikey, skipIdentity = false, } = options;
10
+ const { packageManager = "npm", projectName = path.basename(projectPath), apikey, projectId, skipIdentity = false, } = options;
11
11
  // Sanitize project name for class names
12
12
  const className = projectName
13
13
  .replace(/[^a-zA-Z0-9]/g, "")
@@ -518,7 +518,7 @@ describe('Delegation Management', () => {
518
518
  */
519
519
  describe('DO Multi-Instance Routing', () => {
520
520
 
521
- function getDoInstanceId(request: Request, env: any): string {
521
+ function getDoInstanceId(request: Request, env: { DO_ROUTING_STRATEGY?: string; DO_SHARD_COUNT?: string }): string {
522
522
  const strategy = env.DO_ROUTING_STRATEGY || 'session';
523
523
  const headers = request.headers;
524
524
 
@@ -602,7 +602,7 @@ describe('DO Multi-Instance Routing', () => {
602
602
  */
603
603
  describe('Security Configuration', () => {
604
604
 
605
- function getCorsOrigin(requestOrigin: string | null, env: any): string | null {
605
+ function getCorsOrigin(requestOrigin: string | null, env: { ALLOWED_ORIGINS?: string; MCPI_ENV?: string }): string | null {
606
606
  const allowedOrigins = env.ALLOWED_ORIGINS?.split(',').map((o: string) => o.trim()) || [
607
607
  'https://claude.ai',
608
608
  'https://app.anthropic.com'
@@ -738,8 +738,12 @@ export const greetTool = {
738
738
  `;
739
739
  fs.writeFileSync(path.join(toolsDir, "greet.ts"), greetToolContent);
740
740
  // Create mcpi-runtime-config.ts for AgentShield integration
741
- const runtimeConfigContent = `import type { MCPIRuntimeConfig } from "@kya-os/mcp-i-cloudflare";
742
- ${apikey ? 'import { CloudflareRuntime } from "@kya-os/mcp-i-cloudflare"; // Auto-enabled: Tool Protection Service' : '// import { CloudflareRuntime } from "@kya-os/mcp-i-cloudflare"; // Uncomment to enable Tool Protection Service'}
741
+ const runtimeConfigContent = `import type { MCPIConfig } from '@kya-os/contracts/config';
742
+ import type { CloudflareRuntimeConfig } from '@kya-os/mcp-i-cloudflare/config';
743
+ import type { CloudflareEnv } from '@kya-os/mcp-i-cloudflare';
744
+ import { buildBaseConfig } from '@kya-os/create-mcpi-app/config-builder';
745
+ // Always import CloudflareRuntime for tool protection service creation
746
+ import { CloudflareRuntime } from "@kya-os/mcp-i-cloudflare";
743
747
 
744
748
  /**
745
749
  * Runtime configuration for MCP-I server
@@ -755,150 +759,112 @@ ${apikey ? 'import { CloudflareRuntime } from "@kya-os/mcp-i-cloudflare"; // Au
755
759
  *
756
760
  * Note: The service fetches tool protection config by agent DID automatically.
757
761
  * No project ID configuration needed!
762
+ *
763
+ * This config uses the unified MCPIConfig architecture, ensuring the same
764
+ * base configuration shape across all platforms (Cloudflare, Node.js, Vercel).
758
765
  */
759
- export function getRuntimeConfig(env: any): MCPIRuntimeConfig {
760
- // Accept env parameter to work in both Node.js (process.env) and Cloudflare Workers (worker env)
766
+ export function getRuntimeConfig(env: CloudflareEnv): CloudflareRuntimeConfig {
767
+ // Build base config that works across all platforms
768
+ const baseConfig = buildBaseConfig(env);
761
769
 
770
+ // Extend with Cloudflare-specific properties
762
771
  return {
772
+ ...baseConfig,
763
773
  // Identity configuration
764
774
  identity: {
765
- environment: (env.MCPI_ENV as "development" | "production") || "development",
766
- devIdentityPath: ".mcpi/identity.json"
775
+ ...baseConfig.identity,
776
+ enabled: true,
777
+ environment: (env.ENVIRONMENT || env.MCPI_ENV || 'development') as 'development' | 'production'
767
778
  },
768
-
769
- // Proof submission configuration
779
+ // Proofing configuration
770
780
  proofing: {
771
781
  enabled: true,
772
782
  batchQueue: {
773
783
  destinations: [
774
784
  {
775
785
  type: "agentshield" as const,
776
- apiUrl: env.AGENTSHIELD_API_URL || "https://kya.vouched.id",
777
- apiKey: env.AGENTSHIELD_API_KEY || ""
786
+ apiUrl: env.AGENTSHIELD_API_URL || 'https://kya.vouched.id',
787
+ apiKey: env.AGENTSHIELD_API_KEY || ''
778
788
  }
779
789
  ],
780
790
  maxBatchSize: 10,
781
791
  flushIntervalMs: 5000,
782
792
  maxRetries: 3,
783
- debug: env.MCPI_ENV === "development"
793
+ debug: (env.ENVIRONMENT || env.MCPI_ENV || 'development') === 'development'
784
794
  }
785
795
  },
786
-
787
- // Delegation verification (AgentShield API)
796
+ // Delegation configuration
788
797
  delegation: {
789
- enabled: true,
790
- enforceDelegations: true, // ✅ NEW: Enable enforcement of delegation requirements
791
- verifier: {
792
- type: "agentshield-api",
793
- agentshield: {
794
- apiUrl: env.AGENTSHIELD_API_URL || "https://kya.vouched.id",
795
- apiKey: env.AGENTSHIELD_API_KEY || ""
796
- },
797
- cacheTtl: 60000, // 1 minute cache
798
- debug: env.MCPI_ENV === "development"
799
- },
800
- authorization: {
801
- authorizationUrl: env.AUTHORIZATION_URL || \`\${env.AGENTSHIELD_API_URL}/authorize\`,
802
- resumeTokenTtl: 600000, // 10 minutes
803
- minReputationScore: 76
798
+ ...baseConfig.delegation
799
+ },
800
+ // Audit configuration
801
+ audit: {
802
+ ...baseConfig.audit
803
+ },
804
+ // Cloudflare Workers-specific configuration
805
+ workers: {
806
+ cpuMs: 50,
807
+ memoryMb: 128
808
+ },
809
+ // KV namespace bindings
810
+ kv: env.TOOL_PROTECTION_KV ? [{
811
+ name: 'TOOL_PROTECTION_KV',
812
+ purpose: 'cache' as const
813
+ }] : [],
814
+ // Environment variable bindings
815
+ vars: {
816
+ ENVIRONMENT: env.ENVIRONMENT || env.MCPI_ENV || 'development',
817
+ AGENTSHIELD_API_KEY: env.AGENTSHIELD_API_KEY
818
+ },
819
+ // Tool protection service (Cloudflare-specific)
820
+ toolProtection: env.TOOL_PROTECTION_KV && env.AGENTSHIELD_API_KEY ? {
821
+ source: 'agentshield' as const,
822
+ agentShield: {
823
+ apiUrl: env.AGENTSHIELD_API_URL || 'https://kya.vouched.id',
824
+ apiKey: env.AGENTSHIELD_API_KEY,
825
+ projectId: env.AGENTSHIELD_PROJECT_ID,
826
+ cacheTtl: 300000 // 5 minutes
804
827
  },
805
- // ⚠️ DEPRECATED (if using dynamic tool protection):
806
- // Tool protection rules - Configure scopes for auto-discovery in AgentShield
807
- //
808
- // NOTE: These are now managed via AgentShield dashboard and fetched dynamically
809
- // when you enable the Tool Protection Service (see below). This fallback config
810
- // is only used if:
811
- // 1. Tool Protection Service is not configured, OR
812
- // 2. AgentShield API is unavailable
813
- toolProtections: {
814
- // Example: Public tool with execution scope
828
+ fallback: {
815
829
  greet: {
816
- requiresDelegation: false, // No delegation needed for low-risk tool
817
- requiredScopes: ["greet:execute"] // ✅ Enables tool discovery in AgentShield
830
+ requiresDelegation: false,
831
+ requiredScopes: ['greet:execute']
818
832
  }
819
- // Add more tools as needed:
820
- // High-risk tool requiring delegation:
821
- // checkout: {
822
- // requiresDelegation: true, // User must explicitly delegate
823
- // requiredScopes: ["checkout:execute"] // Scope for tool discovery
824
- // },
825
- // delete_file: {
826
- // requiresDelegation: true,
827
- // requiredScopes: ["files:delete"] // Action-based scope for better categorization
828
- // }
829
833
  }
830
- },
831
-
832
- // 🆕 Dynamic Tool Protection Service (Dashboard-Controlled)
833
- //
834
- // When AgentShield API key is configured, tool delegation requirements
835
- // are fetched from the dashboard in real-time (5-minute cache).
836
- //
837
- // Benefits:
838
- // - Toggle "Require Delegation" for any tool in dashboard - no code changes
839
- // - Changes apply in real-time (5-minute cache via KV)
840
- // - Automatic tool discovery from proof submissions
841
- //
842
- // Performance: 1 API call per 5 minutes, all other requests use KV cache
843
- ${apikey
844
- ? `toolProtectionService: env.TOOL_PROTECTION_KV
845
- ? CloudflareRuntime.createToolProtectionService(
846
- env.TOOL_PROTECTION_KV, // KV namespace from wrangler.toml
847
- {
848
- apiUrl: env.AGENTSHIELD_API_URL || "https://kya.vouched.id",
849
- apiKey: env.AGENTSHIELD_API_KEY || "",
850
- projectId: env.AGENTSHIELD_PROJECT_ID, // Optional: Use projectId endpoint (preferred) if available
851
- cacheTtl: 300000, // 5 minutes (in milliseconds)
852
- debug: env.MCPI_ENV === "development",
853
- // Fallback to local config if API unavailable
854
- fallbackConfig: {
855
- toolProtections: {
856
- greet: {
857
- requiresDelegation: false,
858
- requiredScopes: ["greet:execute"],
859
- },
860
- },
861
- },
862
- }
863
- )
864
- : undefined,`
865
- : `// toolProtectionService: env.TOOL_PROTECTION_KV
866
- // ? CloudflareRuntime.createToolProtectionService(
867
- // env.TOOL_PROTECTION_KV,
868
- // {
869
- // apiUrl: env.AGENTSHIELD_API_URL || "https://kya.vouched.id",
870
- // apiKey: env.AGENTSHIELD_API_KEY || "",
871
- // projectId: env.AGENTSHIELD_PROJECT_ID, // Optional: Use projectId endpoint (preferred) if available
872
- // cacheTtl: 300000, // 5 minutes
873
- // debug: env.MCPI_ENV === "development",
874
- // fallbackConfig: {
875
- // toolProtections: {
876
- // greet: {
877
- // requiresDelegation: false,
878
- // requiredScopes: ["greet:execute"],
879
- // },
880
- // },
881
- // },
882
- // }
883
- // )
884
- // : undefined,`}
885
-
886
- // Audit logging
887
- audit: {
888
- enabled: true
889
- }
890
- };
834
+ } : undefined
835
+ } as CloudflareRuntimeConfig;
891
836
  }
892
837
  `;
893
838
  fs.writeFileSync(path.join(srcDir, "mcpi-runtime-config.ts"), runtimeConfigContent);
894
839
  // Create main index.ts using McpAgent with MCP-I runtime
895
840
  const indexContent = `import { McpAgent } from "agents/mcp";
896
841
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
897
- import { createCloudflareRuntime, type CloudflareEnv, KVProofArchive, type DetachedProof, createOAuthCallbackHandler } from "@kya-os/mcp-i-cloudflare";
842
+ import { createCloudflareRuntime, type CloudflareEnv, KVProofArchive, type DetachedProof, createOAuthCallbackHandler, CloudflareRuntime } from "@kya-os/mcp-i-cloudflare";
843
+ import { DelegationRequiredError } from "@kya-os/mcp-i-core";
844
+ import type { ToolProtectionService } from "@kya-os/mcp-i-core";
898
845
  import { Hono } from "hono";
899
846
  import { cors } from "hono/cors";
900
847
  import { greetTool } from "./tools/greet";
901
848
  import { getRuntimeConfig } from "./mcpi-runtime-config";
849
+ import type { CloudflareRuntimeConfig } from "@kya-os/mcp-i-cloudflare/config";
850
+
851
+ /**
852
+ * Extended CloudflareEnv with prefixed KV bindings for multi-agent deployments
853
+ * This allows multiple agents to share the same Cloudflare account without conflicts
854
+ */
855
+ interface PrefixedCloudflareEnv extends CloudflareEnv {
856
+ // Prefixed KV bindings (e.g., MYAGENT_NONCE_CACHE, MYAGENT_PROOF_ARCHIVE)
857
+ [key: string]: KVNamespace | string | DurableObjectState | undefined;
858
+ // Optional routing configuration
859
+ DO_ROUTING_STRATEGY?: string;
860
+ DO_SHARD_COUNT?: string;
861
+ // Prefixed KV namespaces (dynamically accessed)
862
+ ${className.toUpperCase()}_NONCE_CACHE?: KVNamespace;
863
+ ${className.toUpperCase()}_PROOF_ARCHIVE?: KVNamespace;
864
+ ${className.toUpperCase()}_IDENTITY_STORAGE?: KVNamespace;
865
+ ${className.toUpperCase()}_DELEGATION_STORAGE?: KVNamespace;
866
+ ${className.toUpperCase()}_TOOL_PROTECTION_KV?: KVNamespace;
867
+ }
902
868
 
903
869
  export class ${pascalClassName}MCP extends McpAgent {
904
870
  server = new McpServer({
@@ -906,13 +872,13 @@ export class ${pascalClassName}MCP extends McpAgent {
906
872
  version: "1.0.0"
907
873
  });
908
874
 
909
- private mcpiRuntime?: any;
875
+ private mcpiRuntime?: ReturnType<typeof createCloudflareRuntime>;
910
876
  private proofArchive?: KVProofArchive;
911
877
  private agentShieldConfig?: { apiUrl: string; apiKey: string };
912
- private env: CloudflareEnv;
878
+ private env: PrefixedCloudflareEnv;
913
879
  private mcpServerUrl?: string;
914
880
 
915
- constructor(state: DurableObjectState, env: CloudflareEnv) {
881
+ constructor(state: DurableObjectState, env: PrefixedCloudflareEnv) {
916
882
  super(state, env);
917
883
  this.env = env;
918
884
 
@@ -920,29 +886,29 @@ export class ${pascalClassName}MCP extends McpAgent {
920
886
  // This allows multiple agents to be deployed without KV namespace conflicts
921
887
  const mappedEnv: CloudflareEnv = {
922
888
  // Map prefixed bindings to standard names expected by createCloudflareRuntime
923
- NONCE_CACHE: (env as any).${className.toUpperCase()}_NONCE_CACHE,
924
- PROOF_ARCHIVE: (env as any).${className.toUpperCase()}_PROOF_ARCHIVE,
925
- IDENTITY_STORAGE: (env as any).${className.toUpperCase()}_IDENTITY_STORAGE,
926
- DELEGATION_STORAGE: (env as any).${className.toUpperCase()}_DELEGATION_STORAGE,
927
- TOOL_PROTECTION_KV: (env as any).${className.toUpperCase()}_TOOL_PROTECTION_KV,
889
+ NONCE_CACHE: env.${className.toUpperCase()}_NONCE_CACHE,
890
+ PROOF_ARCHIVE: env.${className.toUpperCase()}_PROOF_ARCHIVE,
891
+ IDENTITY_STORAGE: env.${className.toUpperCase()}_IDENTITY_STORAGE,
892
+ DELEGATION_STORAGE: env.${className.toUpperCase()}_DELEGATION_STORAGE,
893
+ TOOL_PROTECTION_KV: env.${className.toUpperCase()}_TOOL_PROTECTION_KV,
928
894
  // Pass through environment variables unchanged
929
- MCP_IDENTITY_PRIVATE_KEY: (env as any).MCP_IDENTITY_PRIVATE_KEY,
930
- MCP_IDENTITY_PUBLIC_KEY: (env as any).MCP_IDENTITY_PUBLIC_KEY,
931
- MCP_IDENTITY_AGENT_DID: (env as any).MCP_IDENTITY_AGENT_DID,
895
+ MCP_IDENTITY_PRIVATE_KEY: env.MCP_IDENTITY_PRIVATE_KEY,
896
+ MCP_IDENTITY_PUBLIC_KEY: env.MCP_IDENTITY_PUBLIC_KEY,
897
+ MCP_IDENTITY_AGENT_DID: env.MCP_IDENTITY_AGENT_DID,
932
898
  // Pass through other env vars for runtime config
933
- AGENTSHIELD_API_URL: (env as any).AGENTSHIELD_API_URL,
934
- AGENTSHIELD_API_KEY: (env as any).AGENTSHIELD_API_KEY,
935
- AGENTSHIELD_PROJECT_ID: (env as any).AGENTSHIELD_PROJECT_ID,
936
- MCPI_ENV: (env as any).MCPI_ENV,
937
- MCP_SERVER_URL: (env as any).MCP_SERVER_URL,
899
+ AGENTSHIELD_API_URL: env.AGENTSHIELD_API_URL,
900
+ AGENTSHIELD_API_KEY: env.AGENTSHIELD_API_KEY,
901
+ AGENTSHIELD_PROJECT_ID: env.AGENTSHIELD_PROJECT_ID,
902
+ MCPI_ENV: env.MCPI_ENV,
903
+ MCP_SERVER_URL: env.MCP_SERVER_URL,
938
904
  // Pass Durable Object state for identity persistence
939
905
  // NOTE: Without this, identity will be ephemeral (new DID every call)!
940
- // TODO: Implement KV-based identity provider for shared identity across DO instances
906
+ // Note: createCloudflareRuntime will automatically use KVIdentityProvider if IDENTITY_STORAGE KV is available
941
907
  _durableObjectState: state,
942
908
  };
943
909
 
944
910
  // Store MCP server URL for proof submission context
945
- this.mcpServerUrl = (env as any).MCP_SERVER_URL;
911
+ this.mcpServerUrl = env.MCP_SERVER_URL;
946
912
  if (this.mcpServerUrl) {
947
913
  console.log('[MCP-I] MCP Server URL configured:', this.mcpServerUrl);
948
914
  } else {
@@ -953,15 +919,44 @@ export class ${pascalClassName}MCP extends McpAgent {
953
919
  // Pass mappedEnv so it can access KV bindings with standard names
954
920
  const runtimeConfig = getRuntimeConfig(mappedEnv);
955
921
 
922
+ // ✅ Create tool protection service helper function
923
+ // Always import CloudflareRuntime but conditionally instantiate
924
+ function createToolProtectionService(env: CloudflareEnv, runtimeConfig: CloudflareRuntimeConfig): ToolProtectionService | undefined {
925
+ if (!runtimeConfig.toolProtection) {
926
+ return undefined;
927
+ }
928
+
929
+ if (!env.TOOL_PROTECTION_KV || !env.AGENTSHIELD_API_KEY) {
930
+ console.log('[MCP-I] Tool protection disabled - configure TOOL_PROTECTION_KV and AGENTSHIELD_API_KEY to enable');
931
+ return undefined;
932
+ }
933
+
934
+ return CloudflareRuntime.createToolProtectionService(
935
+ env.TOOL_PROTECTION_KV,
936
+ {
937
+ apiUrl: runtimeConfig.toolProtection.agentShield?.apiUrl || env.AGENTSHIELD_API_URL || 'https://kya.vouched.id',
938
+ apiKey: env.AGENTSHIELD_API_KEY,
939
+ projectId: runtimeConfig.toolProtection.agentShield?.projectId || env.AGENTSHIELD_PROJECT_ID,
940
+ cacheTtl: runtimeConfig.toolProtection.agentShield?.cacheTtl || 300000,
941
+ debug: runtimeConfig.environment === 'development',
942
+ fallbackConfig: runtimeConfig.toolProtection.fallback
943
+ }
944
+ );
945
+ }
946
+
947
+ // Create tool protection service if configured
948
+ // Note: createCloudflareRuntime will automatically use KVIdentityProvider if IDENTITY_STORAGE KV is available
949
+ const toolProtectionService = createToolProtectionService(mappedEnv, runtimeConfig);
950
+
956
951
  // Initialize MCP-I runtime for cryptographic proofs and identity
957
952
  this.mcpiRuntime = createCloudflareRuntime({
958
953
  env: mappedEnv,
954
+ environment: runtimeConfig.environment,
959
955
  audit: {
960
956
  enabled: runtimeConfig.audit?.enabled ?? true,
961
957
  logFunction: runtimeConfig.audit?.logFunction || ((record) => console.log('[MCP-I Audit]', record))
962
958
  },
963
- toolProtectionService: runtimeConfig.toolProtectionService,
964
- authorizationUrl: runtimeConfig.delegation?.authorization?.authorizationUrl
959
+ toolProtectionService
965
960
  });
966
961
 
967
962
  // Initialize proof archive if PROOF_ARCHIVE KV is available
@@ -1001,7 +996,7 @@ export class ${pascalClassName}MCP extends McpAgent {
1001
996
  const sessionId = this.getSessionId();
1002
997
 
1003
998
  // Get routing strategy from environment (default: session)
1004
- const strategy = (this.env as any).DO_ROUTING_STRATEGY || 'session';
999
+ const strategy = this.env.DO_ROUTING_STRATEGY || 'session';
1005
1000
 
1006
1001
  if (strategy === 'session') {
1007
1002
  // One DO instance per MCP session (recommended for most use cases)
@@ -1010,7 +1005,7 @@ export class ${pascalClassName}MCP extends McpAgent {
1010
1005
  } else if (strategy === 'shard') {
1011
1006
  // Hash-based sharding across N DO instances (for high load)
1012
1007
  // Distributes load evenly while maintaining session affinity
1013
- const shardCount = parseInt((this.env as any).DO_SHARD_COUNT || '10');
1008
+ const shardCount = parseInt(this.env.DO_SHARD_COUNT || '10');
1014
1009
  // Validate shard count - must be a valid positive number
1015
1010
  const validShardCount = (!isNaN(shardCount) && shardCount > 0) ? shardCount : 10;
1016
1011
 
@@ -1042,7 +1037,7 @@ export class ${pascalClassName}MCP extends McpAgent {
1042
1037
  * @returns Delegation token if found, null otherwise
1043
1038
  */
1044
1039
  private async getDelegationToken(sessionId?: string): Promise<string | null> {
1045
- const delegationStorage = (this.env as any).${className.toUpperCase()}_DELEGATION_STORAGE;
1040
+ const delegationStorage = this.env.${className.toUpperCase()}_DELEGATION_STORAGE;
1046
1041
 
1047
1042
  if (!delegationStorage) {
1048
1043
  console.log('[Delegation] No delegation storage configured');
@@ -1116,7 +1111,7 @@ export class ${pascalClassName}MCP extends McpAgent {
1116
1111
  */
1117
1112
  private async verifyDelegationWithAgentShield(token: string): Promise<boolean> {
1118
1113
  // Check verification cache first (1 minute TTL for verified tokens)
1119
- const verificationCache = (this.env as any).TOOL_PROTECTION_KV;
1114
+ const verificationCache = this.env.TOOL_PROTECTION_KV;
1120
1115
  if (verificationCache) {
1121
1116
  const cacheKey = \`verified:\${token.substring(0, 16)}\`; // Use prefix to avoid key size issues
1122
1117
  const cached = await verificationCache.get(cacheKey);
@@ -1127,8 +1122,8 @@ export class ${pascalClassName}MCP extends McpAgent {
1127
1122
  }
1128
1123
 
1129
1124
  try {
1130
- const agentShieldUrl = (this.env as any).AGENTSHIELD_API_URL || 'https://hobbs.work';
1131
- const apiKey = (this.env as any).AGENTSHIELD_API_KEY;
1125
+ const agentShieldUrl = this.env.AGENTSHIELD_API_URL || 'https://kya.vouched.id';
1126
+ const apiKey = this.env.AGENTSHIELD_API_KEY;
1132
1127
 
1133
1128
  if (!apiKey) {
1134
1129
  console.warn('[Delegation] No AgentShield API key configured, skipping verification');
@@ -1178,8 +1173,8 @@ export class ${pascalClassName}MCP extends McpAgent {
1178
1173
  * @param agentDid - Agent DID to clear
1179
1174
  */
1180
1175
  private async invalidateDelegationCache(sessionId?: string, token?: string, agentDid?: string): Promise<void> {
1181
- const delegationStorage = (this.env as any).${className.toUpperCase()}_DELEGATION_STORAGE;
1182
- const verificationCache = (this.env as any).TOOL_PROTECTION_KV;
1176
+ const delegationStorage = this.env.${className.toUpperCase()}_DELEGATION_STORAGE;
1177
+ const verificationCache = this.env.TOOL_PROTECTION_KV;
1183
1178
 
1184
1179
  if (!delegationStorage) return;
1185
1180
 
@@ -1216,10 +1211,10 @@ export class ${pascalClassName}MCP extends McpAgent {
1216
1211
  */
1217
1212
  private async submitProofToAgentShield(
1218
1213
  proof: DetachedProof,
1219
- session: any,
1214
+ session: { id: string },
1220
1215
  toolName: string,
1221
- args: any,
1222
- result: any
1216
+ args: Record<string, unknown>,
1217
+ result: unknown
1223
1218
  ): Promise<void> {
1224
1219
  if (!this.agentShieldConfig || !proof.jws || !proof.meta) return;
1225
1220
 
@@ -1275,7 +1270,7 @@ export class ${pascalClassName}MCP extends McpAgent {
1275
1270
  throw new Error(\`AgentShield error: \${response.status}\`);
1276
1271
  }
1277
1272
 
1278
- const responseData = await response.json() as any;
1273
+ const responseData = await response.json() as { success?: boolean; received?: number; processed?: number; accepted?: number; rejected?: number; errors?: Array<{ proofId: string; error: string }> };
1279
1274
  console.log('[AgentShield] Response:', responseData);
1280
1275
 
1281
1276
  if (responseData.accepted) {
@@ -1297,7 +1292,7 @@ export class ${pascalClassName}MCP extends McpAgent {
1297
1292
  greetTool.name,
1298
1293
  greetTool.description,
1299
1294
  greetTool.inputSchema.shape,
1300
- async (args: any) => {
1295
+ async (args: { name: string }) => {
1301
1296
  // Use MCP-I runtime's processToolCall for automatic proof generation
1302
1297
  if (this.mcpiRuntime) {
1303
1298
  try {
@@ -1358,8 +1353,8 @@ export class ${pascalClassName}MCP extends McpAgent {
1358
1353
  toolName: greetTool.name
1359
1354
  }).then(() => {
1360
1355
  console.log('[MCP-I] Proof stored in archive');
1361
- }).catch((archiveError: any) => {
1362
- console.error('[MCP-I] Archive error:', archiveError);
1356
+ }).catch((archiveError: unknown) => {
1357
+ console.error('[MCP-I] Archive error:', archiveError instanceof Error ? archiveError.message : String(archiveError));
1363
1358
  })
1364
1359
  );
1365
1360
  }
@@ -1368,8 +1363,8 @@ export class ${pascalClassName}MCP extends McpAgent {
1368
1363
  if (this.agentShieldConfig) {
1369
1364
  proofOperations.push(
1370
1365
  this.submitProofToAgentShield(proof, session, greetTool.name, args, result)
1371
- .catch((err: any) => {
1372
- console.error('[MCP-I] AgentShield failed:', err.message);
1366
+ .catch((err: unknown) => {
1367
+ console.error('[MCP-I] AgentShield failed:', err instanceof Error ? err.message : String(err));
1373
1368
  })
1374
1369
  );
1375
1370
  }
@@ -1380,8 +1375,8 @@ export class ${pascalClassName}MCP extends McpAgent {
1380
1375
  }
1381
1376
 
1382
1377
  // Attach proof to result for MCP Inspector
1383
- if (result && typeof result === 'object') {
1384
- (result as any)._meta = {
1378
+ if (result && typeof result === 'object' && result !== null) {
1379
+ (result as Record<string, unknown>)._meta = {
1385
1380
  proof: {
1386
1381
  jws: proof.jws,
1387
1382
  did: proof.meta.did,
@@ -1397,7 +1392,29 @@ export class ${pascalClassName}MCP extends McpAgent {
1397
1392
  }
1398
1393
 
1399
1394
  return result;
1400
- } catch (error) {
1395
+ } catch (error: unknown) {
1396
+ // If this is a DelegationRequiredError, re-throw it so the MCP framework can handle it properly
1397
+ // The agents/mcp framework will format it as a proper error response to Claude Desktop
1398
+ if (error instanceof DelegationRequiredError) {
1399
+ console.warn('[MCP-I] Delegation required, propagating error:', {
1400
+ tool: error.toolName,
1401
+ requiredScopes: error.requiredScopes,
1402
+ consentUrl: error.consentUrl
1403
+ });
1404
+ throw error;
1405
+ }
1406
+ // Check for DelegationRequiredError by name (for cases where error is not instanceof)
1407
+ if (error && typeof error === 'object' && 'name' in error && error.name === 'DelegationRequiredError') {
1408
+ const delegationError = error as DelegationRequiredError;
1409
+ console.warn('[MCP-I] Delegation required, propagating error:', {
1410
+ tool: delegationError.toolName,
1411
+ requiredScopes: delegationError.requiredScopes,
1412
+ consentUrl: delegationError.consentUrl
1413
+ });
1414
+ throw error;
1415
+ }
1416
+
1417
+ // For other errors, log and fallback to direct execution
1401
1418
  console.error('[MCP-I] Failed to process tool with runtime:', error);
1402
1419
  // Fallback to direct execution
1403
1420
  return await greetTool.handler(args);
@@ -1460,7 +1477,7 @@ app.get("/health", (c) => c.json({
1460
1477
  * { "success": true, "message": "Cache cleared", "agent_did": "..." }
1461
1478
  */
1462
1479
  app.post("/admin/clear-cache", async (c) => {
1463
- const env = c.env as any;
1480
+ const env = c.env as PrefixedCloudflareEnv;
1464
1481
 
1465
1482
  // Parse request body first to get agent_did
1466
1483
  const body = await c.req.json().catch(() => ({}));
@@ -1572,7 +1589,7 @@ app.post("/admin/clear-cache", async (c) => {
1572
1589
  * 4. Returns success page to user
1573
1590
  */
1574
1591
  app.get('/oauth/callback', (c) => {
1575
- const env = c.env as any;
1592
+ const env = c.env as PrefixedCloudflareEnv;
1576
1593
  return createOAuthCallbackHandler({
1577
1594
  agentShieldApiUrl: env.AGENTSHIELD_API_URL || 'https://kya.vouched.id',
1578
1595
  delegationStorage: env.${className.toUpperCase()}_DELEGATION_STORAGE,
@@ -1668,9 +1685,11 @@ id = "your_delegation_kv_namespace_id" # Auto-filled by setup script
1668
1685
  # If you need to recreate it: npm run kv:create-tool-protection
1669
1686
  # After deployment, toggle delegation requirements in AgentShield dashboard
1670
1687
  #
1671
- ${apikey ? "" : "# "}[[kv_namespaces]]
1672
- ${apikey ? "" : "# "}binding = "${className.toUpperCase()}_TOOL_PROTECTION_KV"
1673
- ${apikey ? "" : "# "}id = "your_tool_protection_kv_id" # Auto-filled by setup script
1688
+ # Note: This namespace is REQUIRED when using AgentShield API key (--apikey)
1689
+ # It will be automatically created by the setup script (npm run setup)
1690
+ [[kv_namespaces]]
1691
+ binding = "${className.toUpperCase()}_TOOL_PROTECTION_KV"
1692
+ id = "your_tool_protection_kv_id" # Auto-filled by setup script
1674
1693
 
1675
1694
  [vars]
1676
1695
  XMCP_I_TS_SKEW_SEC = "120"
@@ -1683,12 +1702,12 @@ AGENTSHIELD_API_URL = "https://kya.vouched.id"
1683
1702
  # Production: wrangler secret put AGENTSHIELD_API_KEY
1684
1703
  AGENTSHIELD_API_KEY = ""
1685
1704
  # AGENTSHIELD_PROJECT_ID - Your project ID from AgentShield dashboard (e.g., "batman-txh0ae")
1686
- # Required for project-scoped tool protection configuration
1705
+ # Required for project-scoped tool protection configuration (recommended)
1687
1706
  # Find it in your dashboard URL: https://kya.vouched.id/dashboard/projects/{PROJECT_ID}
1688
1707
  # Or in your project settings
1689
- # Development: Add to .dev.vars file
1708
+ # Development: Add to .dev.vars file${projectId ? ` (already configured: ${projectId})` : ""}
1690
1709
  # Production: wrangler secret put AGENTSHIELD_PROJECT_ID
1691
- AGENTSHIELD_PROJECT_ID = ""
1710
+ AGENTSHIELD_PROJECT_ID = "${projectId || ""}"
1692
1711
  MCPI_ENV = "development"
1693
1712
 
1694
1713
  # Optional: MCP Server URL for tool discovery
@@ -1727,17 +1746,33 @@ DO_ROUTING_STRATEGY = "session"
1727
1746
  DO_SHARD_COUNT = "10" # Number of shards if using shard strategy
1728
1747
 
1729
1748
  `;
1730
- // Always add API key declarations (even if apikey not provided)
1749
+ // Check if API key variables already exist in wrangler.toml
1750
+ // (They may have been added by the initial template)
1751
+ const hasAgentshieldApiKey = /AGENTSHIELD_API_KEY\s*=/.test(wranglerTomlContent);
1752
+ const hasAgentshieldProjectId = /AGENTSHIELD_PROJECT_ID\s*=/.test(wranglerTomlContent);
1753
+ const hasAdminApiKey = /ADMIN_API_KEY\s*=/.test(wranglerTomlContent);
1754
+ // Build API key declarations only for variables that don't already exist
1731
1755
  // Cloudflare Workers REQUIRE variables to be declared in [vars] for .dev.vars to work
1732
- const apiKeyVars = `
1733
- # API keys - MUST be declared here for .dev.vars to work!
1734
- # Development: Values in .dev.vars will override these empty strings
1735
- # Production: Use wrangler secret put to set these
1736
- AGENTSHIELD_API_KEY = ""
1737
- ADMIN_API_KEY = ""
1738
- AGENTSHIELD_PROJECT_ID = ""
1739
-
1740
- `;
1756
+ const apiKeyVarsParts = [];
1757
+ if (!hasAgentshieldApiKey ||
1758
+ !hasAgentshieldProjectId ||
1759
+ !hasAdminApiKey) {
1760
+ apiKeyVarsParts.push(`# API keys - MUST be declared here for .dev.vars to work!`);
1761
+ apiKeyVarsParts.push(`# Development: Values in .dev.vars will override these empty strings`);
1762
+ apiKeyVarsParts.push(`# Production: Use wrangler secret put to set these`);
1763
+ }
1764
+ if (!hasAgentshieldApiKey) {
1765
+ apiKeyVarsParts.push(`AGENTSHIELD_API_KEY = ""`);
1766
+ }
1767
+ if (!hasAdminApiKey) {
1768
+ apiKeyVarsParts.push(`ADMIN_API_KEY = ""`);
1769
+ }
1770
+ if (!hasAgentshieldProjectId) {
1771
+ apiKeyVarsParts.push(`AGENTSHIELD_PROJECT_ID = ""`);
1772
+ }
1773
+ const apiKeyVars = apiKeyVarsParts.length > 0
1774
+ ? `\n${apiKeyVarsParts.join("\n")}\n`
1775
+ : "";
1741
1776
  wranglerTomlContent =
1742
1777
  wranglerTomlContent.slice(0, insertPosition) +
1743
1778
  identityVars +
@@ -1763,7 +1798,7 @@ MCP_IDENTITY_PUBLIC_KEY="${identity.publicKey}"
1763
1798
  AGENTSHIELD_API_KEY="${apikey || ""}"${apikey ? " # Provided via --apikey flag" : ""}
1764
1799
 
1765
1800
  # AgentShield Project ID (from dashboard URL: /dashboard/projects/{PROJECT_ID})
1766
- AGENTSHIELD_PROJECT_ID=""
1801
+ AGENTSHIELD_PROJECT_ID="${projectId || ""}"${projectId ? " # Provided via --project flag" : ""}
1767
1802
 
1768
1803
  # Admin API key for protected endpoints
1769
1804
  ADMIN_API_KEY=""