@kya-os/create-mcpi-app 1.7.16 → 1.7.19
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/.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 +177 -166
- 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 +5 -3
|
@@ -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,151 +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";
|
|
898
843
|
import { DelegationRequiredError } from "@kya-os/mcp-i-core";
|
|
844
|
+
import type { ToolProtectionService } from "@kya-os/mcp-i-core";
|
|
899
845
|
import { Hono } from "hono";
|
|
900
846
|
import { cors } from "hono/cors";
|
|
901
847
|
import { greetTool } from "./tools/greet";
|
|
902
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
|
+
}
|
|
903
868
|
|
|
904
869
|
export class ${pascalClassName}MCP extends McpAgent {
|
|
905
870
|
server = new McpServer({
|
|
@@ -907,13 +872,13 @@ export class ${pascalClassName}MCP extends McpAgent {
|
|
|
907
872
|
version: "1.0.0"
|
|
908
873
|
});
|
|
909
874
|
|
|
910
|
-
private mcpiRuntime?:
|
|
875
|
+
private mcpiRuntime?: ReturnType<typeof createCloudflareRuntime>;
|
|
911
876
|
private proofArchive?: KVProofArchive;
|
|
912
877
|
private agentShieldConfig?: { apiUrl: string; apiKey: string };
|
|
913
|
-
private env:
|
|
878
|
+
private env: PrefixedCloudflareEnv;
|
|
914
879
|
private mcpServerUrl?: string;
|
|
915
880
|
|
|
916
|
-
constructor(state: DurableObjectState, env:
|
|
881
|
+
constructor(state: DurableObjectState, env: PrefixedCloudflareEnv) {
|
|
917
882
|
super(state, env);
|
|
918
883
|
this.env = env;
|
|
919
884
|
|
|
@@ -921,29 +886,29 @@ export class ${pascalClassName}MCP extends McpAgent {
|
|
|
921
886
|
// This allows multiple agents to be deployed without KV namespace conflicts
|
|
922
887
|
const mappedEnv: CloudflareEnv = {
|
|
923
888
|
// Map prefixed bindings to standard names expected by createCloudflareRuntime
|
|
924
|
-
NONCE_CACHE:
|
|
925
|
-
PROOF_ARCHIVE:
|
|
926
|
-
IDENTITY_STORAGE:
|
|
927
|
-
DELEGATION_STORAGE:
|
|
928
|
-
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,
|
|
929
894
|
// Pass through environment variables unchanged
|
|
930
|
-
MCP_IDENTITY_PRIVATE_KEY:
|
|
931
|
-
MCP_IDENTITY_PUBLIC_KEY:
|
|
932
|
-
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,
|
|
933
898
|
// Pass through other env vars for runtime config
|
|
934
|
-
AGENTSHIELD_API_URL:
|
|
935
|
-
AGENTSHIELD_API_KEY:
|
|
936
|
-
AGENTSHIELD_PROJECT_ID:
|
|
937
|
-
MCPI_ENV:
|
|
938
|
-
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,
|
|
939
904
|
// Pass Durable Object state for identity persistence
|
|
940
905
|
// NOTE: Without this, identity will be ephemeral (new DID every call)!
|
|
941
|
-
//
|
|
906
|
+
// Note: createCloudflareRuntime will automatically use KVIdentityProvider if IDENTITY_STORAGE KV is available
|
|
942
907
|
_durableObjectState: state,
|
|
943
908
|
};
|
|
944
909
|
|
|
945
910
|
// Store MCP server URL for proof submission context
|
|
946
|
-
this.mcpServerUrl =
|
|
911
|
+
this.mcpServerUrl = env.MCP_SERVER_URL;
|
|
947
912
|
if (this.mcpServerUrl) {
|
|
948
913
|
console.log('[MCP-I] MCP Server URL configured:', this.mcpServerUrl);
|
|
949
914
|
} else {
|
|
@@ -954,14 +919,44 @@ export class ${pascalClassName}MCP extends McpAgent {
|
|
|
954
919
|
// Pass mappedEnv so it can access KV bindings with standard names
|
|
955
920
|
const runtimeConfig = getRuntimeConfig(mappedEnv);
|
|
956
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
|
+
|
|
957
951
|
// Initialize MCP-I runtime for cryptographic proofs and identity
|
|
958
952
|
this.mcpiRuntime = createCloudflareRuntime({
|
|
959
953
|
env: mappedEnv,
|
|
954
|
+
environment: runtimeConfig.environment,
|
|
960
955
|
audit: {
|
|
961
956
|
enabled: runtimeConfig.audit?.enabled ?? true,
|
|
962
957
|
logFunction: runtimeConfig.audit?.logFunction || ((record) => console.log('[MCP-I Audit]', record))
|
|
963
958
|
},
|
|
964
|
-
toolProtectionService
|
|
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,10 +1392,10 @@ export class ${pascalClassName}MCP extends McpAgent {
|
|
|
1397
1392
|
}
|
|
1398
1393
|
|
|
1399
1394
|
return result;
|
|
1400
|
-
} catch (error:
|
|
1395
|
+
} catch (error: unknown) {
|
|
1401
1396
|
// If this is a DelegationRequiredError, re-throw it so the MCP framework can handle it properly
|
|
1402
1397
|
// The agents/mcp framework will format it as a proper error response to Claude Desktop
|
|
1403
|
-
if (error instanceof DelegationRequiredError
|
|
1398
|
+
if (error instanceof DelegationRequiredError) {
|
|
1404
1399
|
console.warn('[MCP-I] Delegation required, propagating error:', {
|
|
1405
1400
|
tool: error.toolName,
|
|
1406
1401
|
requiredScopes: error.requiredScopes,
|
|
@@ -1408,6 +1403,16 @@ export class ${pascalClassName}MCP extends McpAgent {
|
|
|
1408
1403
|
});
|
|
1409
1404
|
throw error;
|
|
1410
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
|
+
}
|
|
1411
1416
|
|
|
1412
1417
|
// For other errors, log and fallback to direct execution
|
|
1413
1418
|
console.error('[MCP-I] Failed to process tool with runtime:', error);
|
|
@@ -1472,7 +1477,7 @@ app.get("/health", (c) => c.json({
|
|
|
1472
1477
|
* { "success": true, "message": "Cache cleared", "agent_did": "..." }
|
|
1473
1478
|
*/
|
|
1474
1479
|
app.post("/admin/clear-cache", async (c) => {
|
|
1475
|
-
const env = c.env as
|
|
1480
|
+
const env = c.env as PrefixedCloudflareEnv;
|
|
1476
1481
|
|
|
1477
1482
|
// Parse request body first to get agent_did
|
|
1478
1483
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -1584,7 +1589,7 @@ app.post("/admin/clear-cache", async (c) => {
|
|
|
1584
1589
|
* 4. Returns success page to user
|
|
1585
1590
|
*/
|
|
1586
1591
|
app.get('/oauth/callback', (c) => {
|
|
1587
|
-
const env = c.env as
|
|
1592
|
+
const env = c.env as PrefixedCloudflareEnv;
|
|
1588
1593
|
return createOAuthCallbackHandler({
|
|
1589
1594
|
agentShieldApiUrl: env.AGENTSHIELD_API_URL || 'https://kya.vouched.id',
|
|
1590
1595
|
delegationStorage: env.${className.toUpperCase()}_DELEGATION_STORAGE,
|
|
@@ -1680,9 +1685,11 @@ id = "your_delegation_kv_namespace_id" # Auto-filled by setup script
|
|
|
1680
1685
|
# If you need to recreate it: npm run kv:create-tool-protection
|
|
1681
1686
|
# After deployment, toggle delegation requirements in AgentShield dashboard
|
|
1682
1687
|
#
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
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
|
|
1686
1693
|
|
|
1687
1694
|
[vars]
|
|
1688
1695
|
XMCP_I_TS_SKEW_SEC = "120"
|
|
@@ -1695,12 +1702,12 @@ AGENTSHIELD_API_URL = "https://kya.vouched.id"
|
|
|
1695
1702
|
# Production: wrangler secret put AGENTSHIELD_API_KEY
|
|
1696
1703
|
AGENTSHIELD_API_KEY = ""
|
|
1697
1704
|
# AGENTSHIELD_PROJECT_ID - Your project ID from AgentShield dashboard (e.g., "batman-txh0ae")
|
|
1698
|
-
# Required for project-scoped tool protection configuration
|
|
1705
|
+
# Required for project-scoped tool protection configuration (recommended)
|
|
1699
1706
|
# Find it in your dashboard URL: https://kya.vouched.id/dashboard/projects/{PROJECT_ID}
|
|
1700
1707
|
# Or in your project settings
|
|
1701
|
-
# Development: Add to .dev.vars file
|
|
1708
|
+
# Development: Add to .dev.vars file${projectId ? ` (already configured: ${projectId})` : ""}
|
|
1702
1709
|
# Production: wrangler secret put AGENTSHIELD_PROJECT_ID
|
|
1703
|
-
AGENTSHIELD_PROJECT_ID = ""
|
|
1710
|
+
AGENTSHIELD_PROJECT_ID = "${projectId || ""}"
|
|
1704
1711
|
MCPI_ENV = "development"
|
|
1705
1712
|
|
|
1706
1713
|
# Optional: MCP Server URL for tool discovery
|
|
@@ -1747,7 +1754,9 @@ DO_SHARD_COUNT = "10" # Number of shards if using shard strategy
|
|
|
1747
1754
|
// Build API key declarations only for variables that don't already exist
|
|
1748
1755
|
// Cloudflare Workers REQUIRE variables to be declared in [vars] for .dev.vars to work
|
|
1749
1756
|
const apiKeyVarsParts = [];
|
|
1750
|
-
if (!hasAgentshieldApiKey ||
|
|
1757
|
+
if (!hasAgentshieldApiKey ||
|
|
1758
|
+
!hasAgentshieldProjectId ||
|
|
1759
|
+
!hasAdminApiKey) {
|
|
1751
1760
|
apiKeyVarsParts.push(`# API keys - MUST be declared here for .dev.vars to work!`);
|
|
1752
1761
|
apiKeyVarsParts.push(`# Development: Values in .dev.vars will override these empty strings`);
|
|
1753
1762
|
apiKeyVarsParts.push(`# Production: Use wrangler secret put to set these`);
|
|
@@ -1761,7 +1770,9 @@ DO_SHARD_COUNT = "10" # Number of shards if using shard strategy
|
|
|
1761
1770
|
if (!hasAgentshieldProjectId) {
|
|
1762
1771
|
apiKeyVarsParts.push(`AGENTSHIELD_PROJECT_ID = ""`);
|
|
1763
1772
|
}
|
|
1764
|
-
const apiKeyVars = apiKeyVarsParts.length > 0
|
|
1773
|
+
const apiKeyVars = apiKeyVarsParts.length > 0
|
|
1774
|
+
? `\n${apiKeyVarsParts.join("\n")}\n`
|
|
1775
|
+
: "";
|
|
1765
1776
|
wranglerTomlContent =
|
|
1766
1777
|
wranglerTomlContent.slice(0, insertPosition) +
|
|
1767
1778
|
identityVars +
|
|
@@ -1787,7 +1798,7 @@ MCP_IDENTITY_PUBLIC_KEY="${identity.publicKey}"
|
|
|
1787
1798
|
AGENTSHIELD_API_KEY="${apikey || ""}"${apikey ? " # Provided via --apikey flag" : ""}
|
|
1788
1799
|
|
|
1789
1800
|
# AgentShield Project ID (from dashboard URL: /dashboard/projects/{PROJECT_ID})
|
|
1790
|
-
AGENTSHIELD_PROJECT_ID=""
|
|
1801
|
+
AGENTSHIELD_PROJECT_ID="${projectId || ""}"${projectId ? " # Provided via --project flag" : ""}
|
|
1791
1802
|
|
|
1792
1803
|
# Admin API key for protected endpoints
|
|
1793
1804
|
ADMIN_API_KEY=""
|