@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
@@ -0,0 +1,153 @@
1
+ import { execSync } from "child_process";
2
+ import fs from "fs-extra";
3
+ import path from "path";
4
+ import os from "os";
5
+ import chalk from "chalk";
6
+
7
+ interface ScaffoldTemplateOptions {
8
+ xmcpVersion?: string;
9
+ xmcpChannel?: string;
10
+ packageManager?: string;
11
+ }
12
+
13
+ /**
14
+ * Scaffold XMCP project using the upstream xmcp npm package
15
+ * This follows the requirements specification to use the xmcp package
16
+ * with default caret ^0.3.1 and support for dist-tags
17
+ */
18
+ export async function fetchXMCPTemplate(
19
+ projectPath: string,
20
+ options: ScaffoldTemplateOptions = {}
21
+ ): Promise<void> {
22
+ const { xmcpVersion, xmcpChannel = "latest", packageManager = "npm" } = options;
23
+
24
+ // Get XMCP package configuration from environment or use defaults
25
+ const xmcpPackageName = process.env.XMCP_PACKAGE_NAME || "xmcp";
26
+ const defaultCaret = process.env.XMCP_DEFAULT_CARET || "^0.3.1";
27
+
28
+ // Determine which version to use
29
+ let targetVersion = xmcpVersion || defaultCaret;
30
+
31
+ // If using a channel, check if the dist-tag exists
32
+ if (xmcpChannel === "next") {
33
+ try {
34
+ // Check if 'next' dist-tag exists
35
+ execSync(`npm view ${xmcpPackageName}@next version`, {
36
+ stdio: 'pipe'
37
+ });
38
+ targetVersion = "next";
39
+ } catch (error) {
40
+ console.warn(chalk.yellow(`⚠️ dist-tag 'next' not available for ${xmcpPackageName}, falling back to ${defaultCaret}`));
41
+ targetVersion = defaultCaret;
42
+ }
43
+ }
44
+
45
+ try {
46
+ console.log(
47
+ chalk.blue(`📦 Setting up XMCP project with ${xmcpPackageName}@${targetVersion}...`)
48
+ );
49
+
50
+ // Create package.json with xmcp dependency
51
+ // NOTE: Do NOT add "type": "module" as xmcp framework generates CommonJS code
52
+ const packageJson = {
53
+ name: path.basename(projectPath),
54
+ version: "0.1.0",
55
+ dependencies: {
56
+ [xmcpPackageName]: targetVersion,
57
+ // Other dependencies will be added by apply-identity-preset
58
+ },
59
+ scripts: {
60
+ // Basic scripts - will be enhanced by identity preset
61
+ dev: "xmcp dev",
62
+ build: "xmcp build",
63
+ start: "xmcp start"
64
+ }
65
+ };
66
+
67
+ // Write package.json
68
+ fs.ensureDirSync(projectPath);
69
+ fs.writeJsonSync(path.join(projectPath, "package.json"), packageJson, { spaces: 2 });
70
+
71
+ // Create basic XMCP project structure
72
+ const srcDir = path.join(projectPath, "src");
73
+ const toolsDir = path.join(srcDir, "tools");
74
+ const promptsDir = path.join(srcDir, "prompts");
75
+ const resourcesDir = path.join(srcDir, "resources");
76
+
77
+ fs.ensureDirSync(toolsDir);
78
+ fs.ensureDirSync(promptsDir);
79
+ fs.ensureDirSync(resourcesDir);
80
+
81
+ // Create a basic greet tool (XMCP default)
82
+ const greetToolContent = `import { z } from "zod";
83
+ import { type InferSchema, type ToolMetadata } from "${xmcpPackageName}";
84
+
85
+ // Define the schema for tool parameters
86
+ export const schema = {
87
+ name: z.string().describe("The name of the user to greet"),
88
+ };
89
+
90
+ // Define tool metadata
91
+ export const metadata: ToolMetadata = {
92
+ name: "greet",
93
+ description: "Greet the user",
94
+ };
95
+
96
+ // Tool implementation
97
+ export default async function greet({ name }: InferSchema<typeof schema>) {
98
+ return {
99
+ content: [
100
+ {
101
+ type: "text" as const,
102
+ text: \`Hello, \${name}!\`,
103
+ },
104
+ ],
105
+ };
106
+ }
107
+ `;
108
+
109
+ fs.writeFileSync(path.join(toolsDir, "greet.ts"), greetToolContent);
110
+
111
+ // Create tools index (no .js extension needed for TypeScript imports)
112
+ const toolsIndexContent = `export * from "./greet";\n`;
113
+ fs.writeFileSync(path.join(toolsDir, "index.ts"), toolsIndexContent);
114
+
115
+ // Create prompts index (placeholder for xmcp)
116
+ const promptsIndexContent = `// Prompts can be defined here if needed\nexport {};\n`;
117
+ fs.writeFileSync(path.join(promptsDir, "index.ts"), promptsIndexContent);
118
+
119
+ // Create resources index with a comment
120
+ // Note: We don't export anything to avoid "Invalid file path format" warnings
121
+ // Users can add resources here when needed
122
+ const resourcesIndexContent = `// Resources can be defined here if needed
123
+ // Example:
124
+ // export const myResource = {
125
+ // name: "my-resource",
126
+ // description: "A sample resource",
127
+ // uri: "file:///path/to/resource"
128
+ // };
129
+ `;
130
+ fs.writeFileSync(path.join(resourcesDir, "index.ts"), resourcesIndexContent);
131
+
132
+ // Create basic xmcp.config.ts (will be enhanced by generate-config)
133
+ const configContent = `export default {
134
+ tools: "./src/tools",
135
+ transport: "stdio",
136
+ };
137
+ `;
138
+ fs.writeFileSync(path.join(projectPath, "xmcp.config.ts"), configContent);
139
+
140
+ // Create .gitignore
141
+ const gitignoreContent = `node_modules/
142
+ dist/
143
+ .env
144
+ .env.local
145
+ `;
146
+ fs.writeFileSync(path.join(projectPath, ".gitignore"), gitignoreContent);
147
+
148
+ console.log(chalk.green("✅ XMCP project structure created"));
149
+ } catch (error) {
150
+ console.error(chalk.red("Failed to set up XMCP project:"), error);
151
+ throw error;
152
+ }
153
+ }
@@ -0,0 +1,117 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import type { NodeRuntimeConfig } from "@kya-os/mcp-i";
4
+
5
+ export function generateConfig(
6
+ projectPath: string,
7
+ transports: string[],
8
+ skipIdentity = false
9
+ ): void {
10
+ const hasHttp = transports.includes("http");
11
+ const hasStdio = transports.includes("stdio");
12
+
13
+ let configContent = `import type { XmcpConfig } from "@kya-os/mcp-i";
14
+
15
+ const config: XmcpConfig = {
16
+ // Point to the tools directory
17
+ paths: {
18
+ tools: "./src/tools",
19
+ },`;
20
+
21
+ // Only add identity config if not skipped
22
+ if (!skipIdentity) {
23
+ configContent += `
24
+
25
+ // Enable MCP-I identity features
26
+ identity: {
27
+ enabled: true,
28
+ environment: "development",
29
+ },`;
30
+ }
31
+
32
+ if (hasHttp) {
33
+ configContent += `
34
+
35
+ // Enable HTTP transport
36
+ http: true,`;
37
+ }
38
+
39
+ if (hasStdio) {
40
+ configContent += `
41
+
42
+ // Enable STDIO transport
43
+ stdio: true,`;
44
+ }
45
+
46
+ configContent += `
47
+ };
48
+
49
+ export default config;
50
+ `;
51
+
52
+ const configPath = path.join(projectPath, "xmcp.config.ts");
53
+ fs.writeFileSync(configPath, configContent);
54
+
55
+ // Generate runtime config for proof submission and delegation
56
+ generateRuntimeConfig(projectPath, skipIdentity);
57
+ }
58
+
59
+ /**
60
+ * Generate runtime config file for ProofBatchQueue and delegation
61
+ */
62
+ function generateRuntimeConfig(projectPath: string, skipIdentity: boolean): void {
63
+ const runtimeConfigContent = `import type { NodeRuntimeConfig } from "@kya-os/mcp-i";
64
+ import { buildBaseConfig } from "@kya-os/create-mcpi-app/config-builder";
65
+
66
+ /**
67
+ * Runtime configuration for MCP-I server
68
+ *
69
+ * This file configures runtime features like proof submission to AgentShield,
70
+ * delegation verification, and audit logging.
71
+ *
72
+ * Note: Environment variables are automatically injected by the MCP-I compiler
73
+ * from wrangler.toml (Cloudflare) or .env (Node.js). Configure them there:
74
+ * - AGENTSHIELD_API_URL: AgentShield API base URL
75
+ * - AGENTSHIELD_API_KEY: Your AgentShield API key
76
+ * - MCPI_ENV: "development" or "production"
77
+ *
78
+ * This config uses the unified MCPIConfig architecture, ensuring the same
79
+ * base configuration shape across all platforms (Cloudflare, Node.js, Vercel).
80
+ */
81
+ export function getRuntimeConfig(): NodeRuntimeConfig {
82
+ // Access environment variables (works in both Node.js and Cloudflare Workers)
83
+ // The compiler injects these from wrangler.toml or .env
84
+ const env = process.env;
85
+
86
+ // Build base config that works across all platforms
87
+ const baseConfig = buildBaseConfig(env);
88
+
89
+ // Extend with Node.js-specific properties
90
+ return {
91
+ ...baseConfig,
92
+ // Node.js-specific server configuration
93
+ server: {
94
+ port: parseInt(env.PORT || "3000", 10),
95
+ host: env.HOST || "0.0.0.0",
96
+ cors: true,
97
+ timeout: 30000
98
+ },
99
+ // Node.js-specific storage configuration
100
+ storage: {
101
+ type: "memory" as const
102
+ },
103
+ // Node.js-specific environment configuration
104
+ nodeEnv: {
105
+ environment: (env.NODE_ENV as "development" | "production" | "test") || "development",
106
+ debug: env.MCPI_ENV === "development"
107
+ }
108
+ } as NodeRuntimeConfig;
109
+ }
110
+
111
+ export default getRuntimeConfig();
112
+ `;
113
+
114
+ const runtimeConfigPath = path.join(projectPath, "src", "mcpi-runtime-config.ts");
115
+ fs.ensureDirSync(path.join(projectPath, "src"));
116
+ fs.writeFileSync(runtimeConfigPath, runtimeConfigContent);
117
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Identity Generation for MCP-I Applications
3
+ *
4
+ * Generates Ed25519 keypairs and DID:key identifiers
5
+ * for persistent agent identity across deployments.
6
+ */
7
+
8
+ import { webcrypto } from 'crypto';
9
+ import baseX from 'base-x';
10
+
11
+ /**
12
+ * Type for Web Crypto API key pair
13
+ */
14
+ interface WebCryptoKeyPair {
15
+ privateKey: webcrypto.CryptoKey;
16
+ publicKey: webcrypto.CryptoKey;
17
+ }
18
+
19
+ /**
20
+ * Generated identity structure
21
+ */
22
+ export interface GeneratedIdentity {
23
+ did: string;
24
+ kid: string;
25
+ privateKey: string;
26
+ publicKey: string;
27
+ createdAt: string;
28
+ type: 'development' | 'production';
29
+ }
30
+
31
+ /**
32
+ * Generate a new Ed25519 identity with DID:key
33
+ *
34
+ * @returns Promise<GeneratedIdentity> - Complete identity with keys and DID
35
+ */
36
+ export async function generateIdentity(): Promise<GeneratedIdentity> {
37
+ // 1. Generate Ed25519 keypair using Web Crypto API
38
+ const keyPair = (await webcrypto.subtle.generateKey(
39
+ {
40
+ name: 'Ed25519',
41
+ },
42
+ true, // extractable
43
+ ['sign', 'verify']
44
+ )) as WebCryptoKeyPair;
45
+
46
+ // 2. Export keys in PKCS#8 (private) and SPKI (public) formats
47
+ const privateKeyPKCS8 = await webcrypto.subtle.exportKey('pkcs8', keyPair.privateKey);
48
+ const publicKeySPKI = await webcrypto.subtle.exportKey('spki', keyPair.publicKey);
49
+
50
+ // 3. Extract RAW keys from PKCS#8/SPKI wrappers
51
+ // This matches WebCryptoProvider.generateKeyPair() behavior
52
+ const privateKeyRaw = extractEd25519PrivateKey(new Uint8Array(privateKeyPKCS8 as ArrayBuffer));
53
+ const publicKeyRaw = extractEd25519PublicKey(Buffer.from(publicKeySPKI as ArrayBuffer).toString('base64'));
54
+
55
+ // 4. Convert to base64 strings for storage
56
+ const privateKey = Buffer.from(privateKeyRaw).toString('base64');
57
+ const publicKey = Buffer.from(publicKeyRaw).toString('base64');
58
+
59
+ // 5. Generate DID:key from raw public key bytes
60
+ const did = generateDIDFromPublicKeyBytes(publicKeyRaw);
61
+
62
+ // 5. Return complete identity
63
+ return {
64
+ did,
65
+ kid: `${did}#key-1`,
66
+ privateKey,
67
+ publicKey,
68
+ createdAt: new Date().toISOString(),
69
+ type: 'development'
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Generate a DID:key identifier from raw Ed25519 public key bytes
75
+ *
76
+ * Following the DID:key specification:
77
+ * https://w3c-ccg.github.io/did-method-key/
78
+ *
79
+ * Format: did:key:z<multibase-base58btc(<multicodec-ed25519-pub><publicKey>)>
80
+ *
81
+ * @param publicKeyBytes - Raw 32-byte Ed25519 public key
82
+ * @returns DID:key string (e.g., "did:key:z6Mk...")
83
+ */
84
+ function generateDIDFromPublicKeyBytes(publicKeyBytes: Uint8Array): string {
85
+ // 1. Ed25519 multicodec prefix (0xed 0x01)
86
+ const multicodecPrefix = new Uint8Array([0xed, 0x01]);
87
+
88
+ // 2. Combine multicodec prefix + public key
89
+ const multicodecKey = new Uint8Array(multicodecPrefix.length + publicKeyBytes.length);
90
+ multicodecKey.set(multicodecPrefix);
91
+ multicodecKey.set(publicKeyBytes, multicodecPrefix.length);
92
+
93
+ // 3. Encode with base58-btc (Bitcoin alphabet)
94
+ const base58 = baseX('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
95
+ const base58Encoded = base58.encode(multicodecKey);
96
+
97
+ // 4. Add multibase prefix 'z' for base58-btc
98
+ const multibaseEncoded = 'z' + base58Encoded;
99
+
100
+ return `did:key:${multibaseEncoded}`;
101
+ }
102
+
103
+ /**
104
+ * Extract the raw 32-byte Ed25519 private key from PKCS#8 format
105
+ *
106
+ * PKCS#8 format for Ed25519 (48 bytes total):
107
+ * - 16-byte header (algorithm identifier, version, etc.)
108
+ * - 32-byte raw Ed25519 private key
109
+ *
110
+ * @param pkcs8 - PKCS#8 private key as Uint8Array
111
+ * @returns Uint8Array - Raw 32-byte Ed25519 private key
112
+ */
113
+ function extractEd25519PrivateKey(pkcs8: Uint8Array): Uint8Array {
114
+ // Extract bytes 16-48 (the raw 32-byte key)
115
+ // This matches WebCryptoProvider.extractRawPrivateKey()
116
+ return pkcs8.slice(16, 48);
117
+ }
118
+
119
+ /**
120
+ * Extract the raw 32-byte Ed25519 public key from SPKI format
121
+ *
122
+ * SPKI (SubjectPublicKeyInfo) format:
123
+ * - 12-byte header (algorithm identifier)
124
+ * - 32-byte raw Ed25519 public key
125
+ *
126
+ * @param publicKey - Base64-encoded SPKI public key
127
+ * @returns Uint8Array - Raw 32-byte Ed25519 public key
128
+ */
129
+ function extractEd25519PublicKey(publicKey: string): Uint8Array {
130
+ const spkiBytes = Buffer.from(publicKey, 'base64');
131
+
132
+ // SPKI format: 12-byte header + 32-byte public key
133
+ // We extract the last 32 bytes
134
+ // This matches WebCryptoProvider.extractRawPublicKey()
135
+ return spkiBytes.slice(-32);
136
+ }
137
+
138
+ /**
139
+ * Validate that a string is a valid DID:key identifier
140
+ *
141
+ * @param did - String to validate
142
+ * @returns boolean - True if valid DID:key format
143
+ */
144
+ export function isValidDIDKey(did: string): boolean {
145
+ // DID:key format: did:key:z<base58-encoded-multicodec-key>
146
+ const didKeyRegex = /^did:key:z[1-9A-HJ-NP-Za-km-z]{47,}$/;
147
+ return didKeyRegex.test(did);
148
+ }
149
+
150
+ /**
151
+ * Extract the base58-encoded multicodec key from a DID:key
152
+ *
153
+ * @param did - DID:key identifier
154
+ * @returns string - Base58-encoded multicodec key (without 'z' prefix)
155
+ */
156
+ export function extractMultibaseFromDID(did: string): string | null {
157
+ if (!isValidDIDKey(did)) {
158
+ return null;
159
+ }
160
+
161
+ // Remove "did:key:z" prefix
162
+ return did.slice('did:key:z'.length);
163
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Identity Management Utilities
3
+ *
4
+ * Provides functions for resetting and regenerating MCP-I identities
5
+ * for both Node.js (.mcpi/identity.json) and Cloudflare Workers (wrangler.toml).
6
+ */
7
+
8
+ import fs from 'fs-extra';
9
+ import path from 'path';
10
+ import chalk from 'chalk';
11
+ import { generateIdentity } from './generate-identity.js';
12
+
13
+ /**
14
+ * Check if a directory is a Node.js MCP-I project
15
+ */
16
+ export function isNodeMcpiProject(projectPath: string): boolean {
17
+ const identityPath = path.join(projectPath, '.mcpi', 'identity.json');
18
+ const configPath = path.join(projectPath, 'xmcp.config.ts');
19
+ return fs.existsSync(identityPath) || fs.existsSync(configPath);
20
+ }
21
+
22
+ /**
23
+ * Check if a directory is a Cloudflare Worker MCP-I project
24
+ */
25
+ export function isCloudflareProject(projectPath: string): boolean {
26
+ const wranglerPath = path.join(projectPath, 'wrangler.toml');
27
+ if (!fs.existsSync(wranglerPath)) {
28
+ return false;
29
+ }
30
+ // Check if wrangler.toml contains MCP-I or MCP-related content
31
+ const content = fs.readFileSync(wranglerPath, 'utf8');
32
+ return (
33
+ content.includes('MCP_IDENTITY_AGENT_DID') ||
34
+ content.includes('[[durable_objects.bindings]]') ||
35
+ content.includes('AGENTSHIELD_API_KEY')
36
+ );
37
+ }
38
+
39
+ /**
40
+ * Reset identity by deleting existing identity files
41
+ */
42
+ export async function resetIdentity(projectPath: string = process.cwd()): Promise<void> {
43
+ console.log(chalk.cyan('\n🔄 Resetting identity...'));
44
+
45
+ let identityFound = false;
46
+
47
+ // Check for Node.js project
48
+ const mcpiDir = path.join(projectPath, '.mcpi');
49
+ const identityPath = path.join(mcpiDir, 'identity.json');
50
+
51
+ if (fs.existsSync(identityPath)) {
52
+ fs.removeSync(identityPath);
53
+ console.log(chalk.green('✅ Removed Node.js identity file'));
54
+ console.log(chalk.dim(` Deleted: ${identityPath}`));
55
+ identityFound = true;
56
+ }
57
+
58
+ // Check for Cloudflare project
59
+ const wranglerPath = path.join(projectPath, 'wrangler.toml');
60
+ const devVarsPath = path.join(projectPath, '.dev.vars');
61
+
62
+ if (fs.existsSync(wranglerPath)) {
63
+ let content = fs.readFileSync(wranglerPath, 'utf8');
64
+
65
+ if (content.includes('MCP_IDENTITY_AGENT_DID')) {
66
+ // Remove identity variables from wrangler.toml (handles both old and new formats)
67
+ content = content.replace(
68
+ /# Persistent Identity.*?\nMCP_IDENTITY_AGENT_DID = ".*?"\nMCP_IDENTITY_PRIVATE_KEY = ".*?"\nMCP_IDENTITY_PUBLIC_KEY = ".*?"\n/gs,
69
+ ''
70
+ );
71
+ content = content.replace(
72
+ /# Persistent Identity.*?\nMCP_IDENTITY_AGENT_DID = ".*?"\n/gs,
73
+ ''
74
+ );
75
+
76
+ fs.writeFileSync(wranglerPath, content);
77
+ console.log(chalk.green('✅ Removed Cloudflare Worker identity variables'));
78
+ console.log(chalk.dim(` Updated: ${wranglerPath}`));
79
+ identityFound = true;
80
+ }
81
+
82
+ // Remove .dev.vars if it exists
83
+ if (fs.existsSync(devVarsPath)) {
84
+ fs.removeSync(devVarsPath);
85
+ console.log(chalk.green('✅ Removed .dev.vars file'));
86
+ console.log(chalk.dim(` Deleted: ${devVarsPath}`));
87
+ identityFound = true;
88
+ }
89
+ }
90
+
91
+ // Check if any identity was found
92
+ if (!identityFound) {
93
+ console.log(chalk.yellow('⚠️ No identity found to reset'));
94
+ return;
95
+ }
96
+
97
+ console.log(chalk.green('\n✨ Identity reset complete'));
98
+ console.log(chalk.dim(' Run regenerate command to create a new identity'));
99
+ }
100
+
101
+ /**
102
+ * Regenerate identity by creating a new one
103
+ */
104
+ export async function regenerateIdentity(projectPath: string = process.cwd()): Promise<void> {
105
+ console.log(chalk.cyan('\n🔄 Regenerating identity...'));
106
+
107
+ const isNode = isNodeMcpiProject(projectPath);
108
+ const isCloudflare = isCloudflareProject(projectPath);
109
+
110
+ if (!isNode && !isCloudflare) {
111
+ console.log(chalk.red('❌ Not an MCP-I project'));
112
+ console.log(chalk.dim(' Could not detect .mcpi/identity.json or wrangler.toml with MCP_IDENTITY variables'));
113
+ process.exit(1);
114
+ }
115
+
116
+ // Generate new identity
117
+ const identity = await generateIdentity();
118
+
119
+ // Node.js project
120
+ if (isNode) {
121
+ const mcpiDir = path.join(projectPath, '.mcpi');
122
+ fs.ensureDirSync(mcpiDir);
123
+
124
+ const identityPath = path.join(mcpiDir, 'identity.json');
125
+ fs.writeJsonSync(identityPath, identity, { spaces: 2 });
126
+
127
+ console.log(chalk.green('✅ Generated new Node.js identity'));
128
+ console.log(chalk.dim(` DID: ${identity.did}`));
129
+ console.log(chalk.dim(` Saved to: ${identityPath}`));
130
+ }
131
+
132
+ // Cloudflare Worker project
133
+ if (isCloudflare) {
134
+ const wranglerPath = path.join(projectPath, 'wrangler.toml');
135
+ const devVarsPath = path.join(projectPath, '.dev.vars');
136
+ let wranglerContent = fs.readFileSync(wranglerPath, 'utf8');
137
+
138
+ // Remove old identity if exists from wrangler.toml
139
+ wranglerContent = wranglerContent.replace(
140
+ /# Persistent Identity.*?\nMCP_IDENTITY_AGENT_DID = ".*?"\nMCP_IDENTITY_PRIVATE_KEY = ".*?"\nMCP_IDENTITY_PUBLIC_KEY = ".*?"\n/gs,
141
+ ''
142
+ );
143
+
144
+ // Remove old DID-only format if exists
145
+ wranglerContent = wranglerContent.replace(
146
+ /# Persistent Identity.*?\nMCP_IDENTITY_AGENT_DID = ".*?"\n/gs,
147
+ ''
148
+ );
149
+
150
+ // Write private keys to .dev.vars (git-ignored)
151
+ const devVarsContent = `# MCP-I Identity Private Keys (NEVER COMMIT THIS FILE)
152
+ # Generated by create-mcpi-app
153
+ MCP_IDENTITY_PRIVATE_KEY="${identity.privateKey}"
154
+ MCP_IDENTITY_PUBLIC_KEY="${identity.publicKey}"
155
+ `;
156
+ fs.writeFileSync(devVarsPath, devVarsContent);
157
+
158
+ // Add only public DID to wrangler.toml
159
+ const varsMatch = wranglerContent.match(/\[vars\]/);
160
+ if (varsMatch) {
161
+ const insertPosition = varsMatch.index! + varsMatch[0].length;
162
+ const identityVars = `
163
+ # Persistent Identity (generated by create-mcpi-app)
164
+ # Public DID - safe to commit
165
+ MCP_IDENTITY_AGENT_DID = "${identity.did}"
166
+ `;
167
+
168
+ wranglerContent =
169
+ wranglerContent.slice(0, insertPosition) +
170
+ identityVars +
171
+ wranglerContent.slice(insertPosition);
172
+
173
+ fs.writeFileSync(wranglerPath, wranglerContent);
174
+
175
+ console.log(chalk.green('✅ Generated new Cloudflare Worker identity'));
176
+ console.log(chalk.dim(` DID: ${identity.did}`));
177
+ console.log(chalk.dim(` Public DID saved to: ${wranglerPath}`));
178
+ console.log(chalk.dim(` Private keys saved to: ${devVarsPath}`));
179
+ console.log(chalk.yellow(' ⚠️ NEVER commit .dev.vars to version control!'));
180
+ } else {
181
+ console.log(chalk.yellow('⚠️ Could not find [vars] section in wrangler.toml'));
182
+ }
183
+ }
184
+
185
+ console.log(chalk.green('\n✨ Identity regeneration complete'));
186
+ }
@@ -0,0 +1,79 @@
1
+ import { execSync } from "child_process";
2
+ import fs from "fs-extra";
3
+ import path from "path";
4
+ import chalk from "chalk";
5
+
6
+ export function detectPackageManager(
7
+ projectPath: string
8
+ ): "npm" | "yarn" | "pnpm" | null {
9
+ if (fs.existsSync(path.join(projectPath, "pnpm-lock.yaml"))) {
10
+ return "pnpm";
11
+ }
12
+ if (fs.existsSync(path.join(projectPath, "yarn.lock"))) {
13
+ return "yarn";
14
+ }
15
+ if (fs.existsSync(path.join(projectPath, "package-lock.json"))) {
16
+ return "npm";
17
+ }
18
+ return null;
19
+ }
20
+
21
+ export function install(
22
+ projectPath: string,
23
+ packageManager: string,
24
+ packageVersion: string
25
+ ): void {
26
+ console.log(`\n📦 Installing dependencies with ${packageManager}...`);
27
+
28
+ const command =
29
+ packageManager === "yarn"
30
+ ? "yarn install"
31
+ : packageManager === "pnpm"
32
+ ? "pnpm install"
33
+ : "npm install";
34
+
35
+ try {
36
+ execSync(command, {
37
+ cwd: projectPath,
38
+ stdio: "inherit",
39
+ });
40
+
41
+ // Check for lockfile and provide guidance
42
+ checkLockfile(projectPath, packageManager);
43
+ } catch (error) {
44
+ console.error(`Failed to install dependencies with ${packageManager}.`);
45
+ throw error;
46
+ }
47
+ }
48
+
49
+ function checkLockfile(projectPath: string, packageManager: string): void {
50
+ const lockfiles = {
51
+ npm: "package-lock.json",
52
+ yarn: "yarn.lock",
53
+ pnpm: "pnpm-lock.yaml",
54
+ };
55
+
56
+ const expectedLockfile = lockfiles[packageManager as keyof typeof lockfiles];
57
+
58
+ // Handle unknown package managers gracefully
59
+ if (!expectedLockfile) {
60
+ console.warn(
61
+ chalk.yellow(`⚠️ Warning: Unknown package manager "${packageManager}", cannot check lockfile`)
62
+ );
63
+ return;
64
+ }
65
+
66
+ const lockfilePath = path.join(projectPath, expectedLockfile);
67
+
68
+ if (fs.existsSync(lockfilePath)) {
69
+ console.log(
70
+ chalk.gray(
71
+ `✓ Lockfile created (${expectedLockfile}) - remember to commit it`
72
+ )
73
+ );
74
+ } else {
75
+ console.warn(
76
+ chalk.yellow(`⚠️ Warning: No lockfile generated (${expectedLockfile})`)
77
+ );
78
+ }
79
+ }