@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.
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test$colon$coverage.log +315 -0
- package/.turbo/turbo-test.log +95 -0
- package/CHANGELOG.md +372 -0
- package/IMPLEMENTATION_SUMMARY.md +108 -0
- package/REMEDIATION_PLAN.md +99 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +252 -0
- package/coverage/config-builder.ts.html +580 -0
- package/coverage/coverage-final.json +7 -0
- package/coverage/favicon.png +0 -0
- package/coverage/fetch-cloudflare-mcpi-template.ts.html +7006 -0
- package/coverage/generate-config.ts.html +436 -0
- package/coverage/generate-identity.ts.html +574 -0
- package/coverage/index.html +191 -0
- package/coverage/install.ts.html +322 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/validate-project-structure.ts.html +466 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/helpers/__tests__/config-builder.spec.d.ts +8 -0
- package/dist/helpers/__tests__/config-builder.spec.d.ts.map +1 -0
- package/dist/helpers/__tests__/config-builder.spec.js +182 -0
- package/dist/helpers/__tests__/config-builder.spec.js.map +1 -0
- package/dist/helpers/config-builder.d.ts +58 -0
- package/dist/helpers/config-builder.d.ts.map +1 -0
- package/dist/helpers/config-builder.js +102 -0
- package/dist/helpers/config-builder.js.map +1 -0
- package/dist/helpers/create.d.ts +1 -0
- package/dist/helpers/create.d.ts.map +1 -1
- package/dist/helpers/create.js +2 -1
- package/dist/helpers/create.js.map +1 -1
- package/dist/helpers/fetch-cloudflare-mcpi-template.d.ts +1 -0
- package/dist/helpers/fetch-cloudflare-mcpi-template.d.ts.map +1 -1
- package/dist/helpers/fetch-cloudflare-mcpi-template.js +209 -174
- package/dist/helpers/fetch-cloudflare-mcpi-template.js.map +1 -1
- package/dist/helpers/fetch-mcpi-template.d.ts.map +1 -1
- package/dist/helpers/fetch-mcpi-template.js +18 -3
- package/dist/helpers/fetch-mcpi-template.js.map +1 -1
- package/dist/helpers/generate-config.d.ts.map +1 -1
- package/dist/helpers/generate-config.js +27 -40
- package/dist/helpers/generate-config.js.map +1 -1
- package/dist/helpers/install.js +5 -0
- package/dist/helpers/install.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +18 -9
- package/scripts/prepare-pack.js +47 -0
- package/scripts/validate-no-workspace.js +79 -0
- package/src/__tests__/cloudflare-template.test.ts +488 -0
- package/src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts +337 -0
- package/src/__tests__/helpers/generate-config.test.ts +312 -0
- package/src/__tests__/helpers/generate-identity.test.ts +271 -0
- package/src/__tests__/helpers/install.test.ts +362 -0
- package/src/__tests__/helpers/validate-project-structure.test.ts +467 -0
- package/src/__tests__.bak/regression.test.ts +434 -0
- package/src/effects/index.ts +80 -0
- package/src/helpers/__tests__/config-builder.spec.ts +231 -0
- package/src/helpers/apply-identity-preset.ts +209 -0
- package/src/helpers/config-builder.ts +165 -0
- package/src/helpers/copy-template.ts +11 -0
- package/src/helpers/create.ts +239 -0
- package/src/helpers/fetch-cloudflare-mcpi-template.ts +2311 -0
- package/src/helpers/fetch-cloudflare-template.ts +361 -0
- package/src/helpers/fetch-mcpi-template.ts +236 -0
- package/src/helpers/fetch-xmcp-template.ts +153 -0
- package/src/helpers/generate-config.ts +117 -0
- package/src/helpers/generate-identity.ts +163 -0
- package/src/helpers/identity-manager.ts +186 -0
- package/src/helpers/install.ts +79 -0
- package/src/helpers/rename.ts +17 -0
- package/src/helpers/validate-project-structure.ts +127 -0
- package/src/index.ts +480 -0
- package/src/utils/check-node.ts +17 -0
- package/src/utils/is-folder-empty.ts +60 -0
- package/src/utils/validate-project-name.ts +132 -0
- package/test-cloudflare/README.md +164 -0
- package/test-cloudflare/package.json +28 -0
- package/test-cloudflare/src/index.ts +340 -0
- package/test-cloudflare/src/tools/greet.ts +19 -0
- package/test-cloudflare/tests/cache-invalidation.test.ts +410 -0
- package/test-cloudflare/tests/cors-security.test.ts +349 -0
- package/test-cloudflare/tests/delegation.test.ts +335 -0
- package/test-cloudflare/tests/do-routing.test.ts +314 -0
- package/test-cloudflare/tests/integration.test.ts +205 -0
- package/test-cloudflare/tests/session-management.test.ts +359 -0
- package/test-cloudflare/tsconfig.json +22 -0
- package/test-cloudflare/vitest.config.ts +9 -0
- package/test-cloudflare/wrangler.toml +37 -0
- package/test-node/README.md +44 -0
- package/test-node/package.json +23 -0
- package/test-node/src/tools/greet.ts +25 -0
- package/test-node/xmcp.config.ts +20 -0
- package/tsconfig.json +26 -0
- 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:
|
|
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:
|
|
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 {
|
|
742
|
-
|
|
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:
|
|
760
|
-
//
|
|
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
|
-
|
|
766
|
-
|
|
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 ||
|
|
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 ===
|
|
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
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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
|
-
|
|
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,
|
|
817
|
-
requiredScopes: [
|
|
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?:
|
|
875
|
+
private mcpiRuntime?: ReturnType<typeof createCloudflareRuntime>;
|
|
910
876
|
private proofArchive?: KVProofArchive;
|
|
911
877
|
private agentShieldConfig?: { apiUrl: string; apiKey: string };
|
|
912
|
-
private env:
|
|
878
|
+
private env: PrefixedCloudflareEnv;
|
|
913
879
|
private mcpServerUrl?: string;
|
|
914
880
|
|
|
915
|
-
constructor(state: DurableObjectState, env:
|
|
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:
|
|
924
|
-
PROOF_ARCHIVE:
|
|
925
|
-
IDENTITY_STORAGE:
|
|
926
|
-
DELEGATION_STORAGE:
|
|
927
|
-
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:
|
|
930
|
-
MCP_IDENTITY_PUBLIC_KEY:
|
|
931
|
-
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:
|
|
934
|
-
AGENTSHIELD_API_KEY:
|
|
935
|
-
AGENTSHIELD_PROJECT_ID:
|
|
936
|
-
MCPI_ENV:
|
|
937
|
-
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
|
-
//
|
|
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 =
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1131
|
-
const apiKey =
|
|
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 =
|
|
1182
|
-
const verificationCache =
|
|
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:
|
|
1214
|
+
session: { id: string },
|
|
1220
1215
|
toolName: string,
|
|
1221
|
-
args:
|
|
1222
|
-
result:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
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
|
-
//
|
|
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
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
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=""
|