@kya-os/create-mcpi-app 1.7.42-canary.4 → 1.7.42-canary.40

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.
@@ -1,617 +1,104 @@
1
1
  import fs from "fs-extra";
2
2
  import path from "path";
3
3
  import chalk from "chalk";
4
+ import { spawn } from "child_process";
5
+ import crypto from "crypto";
4
6
  import { generateIdentity } from "./generate-identity.js";
5
7
  /**
6
- * Scaffold Cloudflare Worker MCP server
7
- * Uses McpAgent from agents/mcp for MCP protocol support
8
- */
9
- export async function fetchCloudflareMcpiTemplate(projectPath, options = {}) {
10
- const { packageManager = "npm", projectName = path.basename(projectPath), apikey, projectId, skipIdentity = false, } = options;
11
- // Sanitize project name for class names
12
- let className = projectName
13
- .replace(/[^a-zA-Z0-9]/g, "")
14
- .replace(/^[0-9]/, "_$&");
15
- // Fallback to prevent empty class names
16
- if (!className || className.length === 0) {
17
- className = "Project";
18
- }
19
- const pascalClassName = className.charAt(0).toUpperCase() + className.slice(1);
20
- // Sanitize project name for wrangler.toml (alphanumeric, lowercase, dashes only)
21
- let wranglerName = projectName
22
- .toLowerCase()
23
- .replace(/[^a-z0-9-]/g, "-") // Replace invalid chars with dashes
24
- .replace(/-+/g, "-") // Replace multiple dashes with single dash
25
- .replace(/^-|-$/g, ""); // Remove leading/trailing dashes
26
- // Fallback to prevent empty wrangler names
27
- if (!wranglerName || wranglerName.length === 0) {
28
- wranglerName = "worker";
29
- }
30
- try {
31
- // Create package.json
32
- const packageJson = {
33
- name: projectName,
34
- version: "0.1.0",
35
- private: true,
36
- scripts: {
37
- setup: "node scripts/setup.js",
38
- postinstall: "npm run setup",
39
- deploy: "wrangler deploy",
40
- dev: "wrangler dev",
41
- start: "wrangler dev",
42
- "kv:create": "npm run kv:create-nonce && npm run kv:create-proof && npm run kv:create-identity && npm run kv:create-delegation && npm run kv:create-tool-protection",
43
- "kv:create-nonce": `wrangler kv namespace create ${className.toUpperCase()}_NONCE_CACHE`,
44
- "kv:create-proof": `wrangler kv namespace create ${className.toUpperCase()}_PROOF_ARCHIVE`,
45
- "kv:create-identity": `wrangler kv namespace create ${className.toUpperCase()}_IDENTITY_STORAGE`,
46
- "kv:create-delegation": `wrangler kv namespace create ${className.toUpperCase()}_DELEGATION_STORAGE`,
47
- "kv:create-tool-protection": `wrangler kv namespace create ${className.toUpperCase()}_TOOL_PROTECTION_KV`,
48
- "kv:list": "wrangler kv namespace list | grep -E '(NONCE|PROOF|IDENTITY|DELEGATION|TOOL_PROTECTION|MCPI)' || wrangler kv namespace list",
49
- "kv:keys-nonce": `wrangler kv key list --binding=${className.toUpperCase()}_NONCE_CACHE`,
50
- "kv:keys-proof": `wrangler kv key list --binding=${className.toUpperCase()}_PROOF_ARCHIVE`,
51
- "kv:keys-identity": `wrangler kv key list --binding=${className.toUpperCase()}_IDENTITY_STORAGE`,
52
- "kv:keys-delegation": `wrangler kv key list --binding=${className.toUpperCase()}_DELEGATION_STORAGE`,
53
- "kv:keys-tool-protection": `wrangler kv key list --binding=${className.toUpperCase()}_TOOL_PROTECTION_KV`,
54
- "kv:delete-nonce": `wrangler kv namespace delete --binding=${className.toUpperCase()}_NONCE_CACHE`,
55
- "kv:delete-proof": `wrangler kv namespace delete --binding=${className.toUpperCase()}_PROOF_ARCHIVE`,
56
- "kv:delete-identity": `wrangler kv namespace delete --binding=${className.toUpperCase()}_IDENTITY_STORAGE`,
57
- "kv:delete-delegation": `wrangler kv namespace delete --binding=${className.toUpperCase()}_DELEGATION_STORAGE`,
58
- "kv:delete-tool-protection": `wrangler kv namespace delete --binding=${className.toUpperCase()}_TOOL_PROTECTION_KV`,
59
- "kv:delete": "npm run kv:delete-nonce && npm run kv:delete-proof && npm run kv:delete-identity && npm run kv:delete-delegation && npm run kv:delete-tool-protection",
60
- "kv:reset": "npm run kv:delete && npm run kv:create",
61
- "kv:setup": "echo 'KV Commands: kv:create (create all), kv:list (list all), kv:keys-* (view keys), kv:delete (delete all), kv:reset (delete+recreate)'",
62
- "cf-typegen": "wrangler types",
63
- "type-check": "tsc --noEmit",
64
- test: "vitest",
65
- "test:watch": "vitest --watch",
66
- "test:coverage": "vitest run --coverage",
67
- },
68
- dependencies: {
69
- "@kya-os/contracts": "^1.5.2-canary.5",
70
- "@kya-os/mcp-i-cloudflare": "1.5.1-canary.5",
71
- "@modelcontextprotocol/sdk": "^1.19.1",
72
- agents: "^0.2.21", // Use latest version - constructor now only accepts 2 parameters
73
- hono: "^4.9.10",
74
- zod: "^3.25.76",
75
- },
76
- devDependencies: {
77
- "@cloudflare/workers-types": "^4.20251109.0",
78
- "@kya-os/create-mcpi-app": "^1.7.23",
79
- "@vitest/coverage-v8": "^3.2.4",
80
- miniflare: "^3.0.0",
81
- typescript: "^5.6.2",
82
- vitest: "^3.2.4",
83
- wrangler: "^4.42.2",
84
- },
85
- };
86
- fs.ensureDirSync(projectPath); // Ensure directory exists before writing
87
- fs.writeJsonSync(path.join(projectPath, "package.json"), packageJson, {
88
- spaces: 2,
89
- });
90
- // Create src directory and tools
91
- const srcDir = path.join(projectPath, "src");
92
- const toolsDir = path.join(srcDir, "tools");
93
- fs.ensureDirSync(toolsDir);
94
- // Create scripts directory
95
- const scriptsDir = path.join(projectPath, "scripts");
96
- fs.ensureDirSync(scriptsDir);
97
- // Create setup.js automation script
98
- const setupScriptContent = `#!/usr/bin/env node
99
-
100
- /**
101
- * Automated Setup Script for ${projectName}
102
- *
103
- * This script automates the tedious process of:
104
- * 1. Checking/installing wrangler
105
- * 2. Creating KV namespaces
106
- * 3. Extracting namespace IDs
107
- * 4. Updating wrangler.toml automatically
108
- * 5. Setting up local development environment
109
- */
110
-
111
- const { execSync } = require('child_process');
112
- const fs = require('fs');
113
- const path = require('path');
114
- const readline = require('readline');
115
-
116
- const rl = readline.createInterface({
117
- input: process.stdin,
118
- output: process.stdout
119
- });
120
-
121
- // Colors for terminal output
122
- const colors = {
123
- reset: '\\x1b[0m',
124
- bright: '\\x1b[1m',
125
- green: '\\x1b[32m',
126
- yellow: '\\x1b[33m',
127
- blue: '\\x1b[36m',
128
- red: '\\x1b[31m'
129
- };
130
-
131
- function log(message, color = colors.reset) {
132
- console.log(color + message + colors.reset);
133
- }
134
-
135
- function skipToPostKVSetup() {
136
- // Skip KV creation but continue with other setup steps
137
- const devVarsPath = path.join(__dirname, '..', '.dev.vars');
138
- const devVarsExamplePath = path.join(__dirname, '..', '.dev.vars.example');
139
-
140
- // Create .dev.vars from example if it doesn't exist
141
- if (!fs.existsSync(devVarsPath) && fs.existsSync(devVarsExamplePath)) {
142
- log('\\nšŸ“‹ Creating .dev.vars from example...', colors.blue);
143
- fs.copyFileSync(devVarsExamplePath, devVarsPath);
144
- log('āœ… Created .dev.vars - Please update with your values', colors.green);
145
- }
146
-
147
- // Check if identity needs to be generated
148
- if (fs.existsSync(devVarsPath)) {
149
- const devVarsContent = fs.readFileSync(devVarsPath, 'utf-8');
150
- if (devVarsContent.includes('your-private-key-here')) {
151
- log('\\nšŸ”‘ Generating agent identity...', colors.blue);
152
- try {
153
- execSync('npx @kya-os/create-mcpi-app regenerate-identity', { stdio: 'inherit' });
154
- log('āœ… Identity generated successfully', colors.green);
155
- } catch {
156
- log('āš ļø Could not generate identity automatically. Run: npx @kya-os/create-mcpi-app regenerate-identity', colors.yellow);
157
- }
158
- }
159
- }
160
-
161
- // Show modified next steps
162
- log('\\n✨ Setup partially complete! Next steps:\\n', colors.bright + colors.green);
163
- log('1. Set your Cloudflare account ID (required for KV namespaces):', colors.blue);
164
- log(' export CLOUDFLARE_ACCOUNT_ID=your-account-id', colors.reset);
165
- log(' OR add to wrangler.toml: account_id = "your-account-id"\\n', colors.reset);
166
- log('2. Create KV namespaces: npm run kv:create', colors.blue);
167
- log('3. Review .dev.vars and add any missing values', colors.blue);
168
- log('4. Start development server: npm run dev', colors.blue);
169
- log('5. Deploy to production: npm run deploy', colors.blue);
170
- log('\\nUseful commands:', colors.bright);
171
- log(' npm run dev - Start local development server');
172
- log(' npm run deploy - Deploy to Cloudflare Workers');
173
- log(' npm run kv:list - List all KV namespaces');
174
- log(' wrangler secret put <KEY> - Set production secrets');
175
- log('\\nFor more information, see the README.md file.\\n');
176
-
177
- rl.close();
178
- }
179
-
180
- async function setup() {
181
- log('\\nšŸš€ Starting automated setup for ${projectName}...\\n', colors.bright + colors.blue);
182
-
183
- // 1. Check wrangler installation
184
- try {
185
- const wranglerVersion = execSync('wrangler --version', { encoding: 'utf-8' });
186
- log('āœ… Wrangler CLI detected: ' + wranglerVersion.trim(), colors.green);
187
- } catch {
188
- log('šŸ“¦ Wrangler CLI not found. Installing...', colors.yellow);
189
- try {
190
- execSync('npm install -g wrangler', { stdio: 'inherit' });
191
- log('āœ… Wrangler CLI installed successfully', colors.green);
192
- } catch (error) {
193
- log('āŒ Failed to install Wrangler. Please install manually: npm install -g wrangler', colors.red);
194
- process.exit(1);
195
- }
196
- }
197
-
198
- // 2. Check if user is logged in to Cloudflare
199
- try {
200
- execSync('wrangler whoami', { encoding: 'utf-8' });
201
- log('āœ… Logged in to Cloudflare', colors.green);
202
- } catch {
203
- log('šŸ”‘ Please log in to Cloudflare:', colors.yellow);
204
- try {
205
- execSync('wrangler login', { stdio: 'inherit' });
206
- } catch (error) {
207
- log('āŒ Login failed. Please run: wrangler login', colors.red);
208
- process.exit(1);
209
- }
210
- }
211
-
212
- // 2.5. Set up wrangler.toml path for later use
213
- const wranglerTomlPath = path.join(__dirname, '..', 'wrangler.toml');
214
- let wranglerContent = '';
215
-
216
- try {
217
- wranglerContent = fs.readFileSync(wranglerTomlPath, 'utf-8');
218
- } catch (error) {
219
- log('āš ļø Could not read wrangler.toml', colors.yellow);
220
- }
221
-
222
- // 3. Create KV namespaces
223
- log('\\nšŸ“š Creating KV namespaces...\\n', colors.bright);
224
-
225
- const namespaces = [
226
- { binding: '${className.toUpperCase()}_NONCE_CACHE', name: 'Nonce Cache', purpose: 'Replay attack prevention' },
227
- { binding: '${className.toUpperCase()}_PROOF_ARCHIVE', name: 'Proof Archive', purpose: 'Cryptographic proof storage' },
228
- { binding: '${className.toUpperCase()}_IDENTITY_STORAGE', name: 'Identity Storage', purpose: 'Agent identity persistence' },
229
- { binding: '${className.toUpperCase()}_DELEGATION_STORAGE', name: 'Delegation Storage', purpose: 'OAuth token storage' },
230
- { binding: '${className.toUpperCase()}_TOOL_PROTECTION_KV', name: 'Tool Protection', purpose: 'Permission caching' }
231
- ];
232
-
233
- const kvIds = {};
234
- let multipleAccountsDetected = false;
235
-
236
- for (const ns of namespaces) {
237
- // If we already detected multiple accounts, skip remaining namespaces
238
- if (multipleAccountsDetected) {
239
- break;
240
- }
241
-
242
- log(\`Creating \${ns.name} (\${ns.purpose})...\`, colors.blue);
243
-
244
- // First, check if namespace already exists
245
- let existingNamespace = null;
246
- try {
247
- const listOutput = execSync('wrangler kv namespace list', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
248
-
249
- try {
250
- const allNamespaces = JSON.parse(listOutput);
251
- existingNamespace = allNamespaces.find(n => n.title === ns.binding);
252
- } catch (parseError) {
253
- // Fallback to regex if JSON parsing fails
254
- const existingMatch = listOutput.match(new RegExp(\`"title":\\s*"\${ns.binding}"[^}]*"id":\\s*"([^"]+)"\`));
255
- if (existingMatch && existingMatch[1]) {
256
- existingNamespace = { id: existingMatch[1], title: ns.binding };
257
- }
258
- }
259
- } catch (listError) {
260
- // If we can't list namespaces, continue to try creating
261
- }
262
-
263
- if (existingNamespace && existingNamespace.id) {
264
- kvIds[ns.binding] = existingNamespace.id;
265
- log(\` āœ… Using existing namespace with ID: \${existingNamespace.id}\`, colors.green);
266
- continue;
267
- }
268
-
269
- // Namespace doesn't exist, try to create it
270
- try {
271
- // Suppress stderr to avoid noisy error messages
272
- const output = execSync(\`wrangler kv namespace create "\${ns.binding}"\`, {
273
- encoding: 'utf-8',
274
- stdio: ['pipe', 'pipe', 'pipe']
275
- });
276
-
277
- // Extract the ID from output
278
- const idMatch = output.match(/id = "([^"]+)"/);
279
-
280
- if (idMatch && idMatch[1]) {
281
- kvIds[ns.binding] = idMatch[1];
282
- log(\` āœ… Created with ID: \${idMatch[1]}\`, colors.green);
283
- } else {
284
- log(\` āš ļø Created but could not extract ID. Checking existing namespaces...\`, colors.yellow);
285
- // Fallback: try to find it in the list
286
- const listOutput = execSync('wrangler kv namespace list', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
287
- try {
288
- const allNamespaces = JSON.parse(listOutput);
289
- const found = allNamespaces.find(n => n.title === ns.binding);
290
- if (found && found.id) {
291
- kvIds[ns.binding] = found.id;
292
- log(\` āœ… Found ID: \${found.id}\`, colors.green);
293
- }
294
- } catch {
295
- // Ignore parse errors
296
- }
297
- }
298
- } catch (error) {
299
- const errorMessage = error.message || error.toString();
300
- const errorOutput = error.stdout || error.stderr || '';
301
-
302
- // Check if this is a multiple accounts error
303
- if (errorMessage.includes('More than one account') || errorMessage.includes('multiple accounts') || errorOutput.includes('More than one account')) {
304
- multipleAccountsDetected = true;
305
- log('\\nāš ļø Multiple Cloudflare accounts detected!\\n', colors.yellow);
306
- log('Wrangler cannot automatically select an account in non-interactive mode.\\n', colors.yellow);
307
- log('To fix this, choose one of these options:\\n', colors.bright);
308
- log('Option 1: Set environment variable (recommended):', colors.blue);
309
- log(' export CLOUDFLARE_ACCOUNT_ID=your-account-id', colors.reset);
310
- log(' npm run setup\\n', colors.reset);
311
- log('Option 2: Add to wrangler.toml (permanent):', colors.blue);
312
- log(' Edit wrangler.toml and add:', colors.reset);
313
- log(' account_id = "your-account-id"\\n', colors.reset);
314
- log('Find your account IDs in the error above or run:', colors.blue);
315
- log(' wrangler whoami\\n', colors.reset);
316
- log('ā­ļø Skipping remaining KV namespace creation.', colors.yellow);
317
- log('After setting account_id, run: npm run setup\\n', colors.yellow);
318
- break;
319
- }
320
-
321
- // Check if namespace already exists (common case - suppress noisy error)
322
- if (errorMessage.includes('already exists') || errorOutput.includes('already exists') || errorOutput.includes('code: 10014')) {
323
- // Try to get the existing namespace ID
324
- try {
325
- const listOutput = execSync('wrangler kv namespace list', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
326
-
327
- try {
328
- const allNamespaces = JSON.parse(listOutput);
329
- const found = allNamespaces.find(n => n.title === ns.binding);
330
-
331
- if (found && found.id) {
332
- kvIds[ns.binding] = found.id;
333
- log(\` āœ… Using existing namespace with ID: \${found.id}\`, colors.green);
334
- } else {
335
- log(\` āš ļø Namespace exists but could not find ID. You may need to add it manually.\`, colors.yellow);
336
- }
337
- } catch (parseError) {
338
- // Fallback to regex
339
- const existingMatch = listOutput.match(new RegExp(\`"title":\\s*"\${ns.binding}"[^}]*"id":\\s*"([^"]+)"\`));
340
- if (existingMatch && existingMatch[1]) {
341
- kvIds[ns.binding] = existingMatch[1];
342
- log(\` āœ… Using existing namespace with ID: \${existingMatch[1]}\`, colors.green);
343
- } else {
344
- log(\` āš ļø Namespace exists but could not find ID. You may need to add it manually.\`, colors.yellow);
345
- }
346
- }
347
- } catch (listError) {
348
- log(\` āš ļø Namespace may already exist. Run 'wrangler kv namespace list' to verify.\`, colors.yellow);
349
- }
350
- } else {
351
- // Some other error occurred
352
- log(\` āŒ Failed to create \${ns.binding}\`, colors.red);
353
- log(\` Error: \${errorMessage}\`, colors.red);
354
- }
355
- }
356
- }
357
-
358
- // If multiple accounts detected, skip to post-KV setup
359
- if (multipleAccountsDetected) {
360
- return skipToPostKVSetup();
361
- }
362
-
363
- // 4. Update wrangler.toml with KV IDs
364
- if (Object.keys(kvIds).length > 0) {
365
- log('\\nšŸ“ Updating wrangler.toml with KV namespace IDs...\\n', colors.bright);
366
-
367
- try {
368
- wranglerContent = fs.readFileSync(wranglerTomlPath, 'utf-8');
369
- let updatedCount = 0;
370
-
371
- for (const [binding, id] of Object.entries(kvIds)) {
372
- // Match pattern: binding = "BINDING_NAME"\\nid = "anything" (including placeholders)
373
- const pattern = new RegExp(\`(binding = "\${binding}")\\\\s*\\\\nid = "[^"]*"\`, 'g');
374
- const replacement = \`$1\\nid = "\${id}"\`;
375
-
376
- const newContent = wranglerContent.replace(pattern, replacement);
377
- if (newContent !== wranglerContent) {
378
- updatedCount++;
379
- log(\` āœ… Updated \${binding} with ID: \${id}\`, colors.green);
380
- }
381
- wranglerContent = newContent;
382
- }
383
-
384
- fs.writeFileSync(wranglerTomlPath, wranglerContent);
385
- log(\`\\nāœ… Updated \${updatedCount} namespace ID(s) in wrangler.toml\`, colors.green);
386
-
387
- // Show remaining placeholder IDs if any
388
- const placeholderMatches = wranglerContent.match(/binding = "[^"]+"\\s*\\nid = "your_[^"]+"/g);
389
- if (placeholderMatches) {
390
- log('\\nāš ļø Some namespace IDs still have placeholders:', colors.yellow);
391
- placeholderMatches.forEach(match => {
392
- const bindingMatch = match.match(/binding = "([^"]+)"/);
393
- if (bindingMatch) {
394
- log(\` - \${bindingMatch[1]}\`, colors.yellow);
395
- }
396
- });
397
- }
398
- } catch (error) {
399
- log(\`āŒ Failed to update wrangler.toml: \${error.message}\`, colors.red);
400
- }
401
- }
402
-
403
- // 5. Create .dev.vars from example if it doesn't exist
404
- const devVarsPath = path.join(__dirname, '..', '.dev.vars');
405
- const devVarsExamplePath = path.join(__dirname, '..', '.dev.vars.example');
406
-
407
- if (!fs.existsSync(devVarsPath) && fs.existsSync(devVarsExamplePath)) {
408
- log('\\nšŸ“‹ Creating .dev.vars from example...', colors.blue);
409
- fs.copyFileSync(devVarsExamplePath, devVarsPath);
410
- log('āœ… Created .dev.vars - Please update with your values', colors.green);
411
- }
412
-
413
- // 6. Check if identity needs to be generated
414
- if (fs.existsSync(devVarsPath)) {
415
- const devVarsContent = fs.readFileSync(devVarsPath, 'utf-8');
416
- if (devVarsContent.includes('your-private-key-here')) {
417
- log('\\nšŸ”‘ Generating agent identity...', colors.blue);
418
- try {
419
- execSync('npx @kya-os/create-mcpi-app regenerate-identity', { stdio: 'inherit' });
420
- log('āœ… Identity generated successfully', colors.green);
421
- } catch {
422
- log('āš ļø Could not generate identity automatically. Run: npx @kya-os/create-mcpi-app regenerate-identity', colors.yellow);
423
- }
424
- }
425
- }
426
-
427
- // 7. Show next steps
428
- log('\\n✨ Setup complete! Next steps:\\n', colors.bright + colors.green);
429
- log('1. Review .dev.vars and add any missing values (AgentShield API key, etc.)', colors.blue);
430
- log('2. Start development server: npm run dev', colors.blue);
431
- log('3. Deploy to production: npm run deploy', colors.blue);
432
- log('\\nUseful commands:', colors.bright);
433
- log(' npm run dev - Start local development server');
434
- log(' npm run deploy - Deploy to Cloudflare Workers');
435
- log(' npm run kv:list - List all KV namespaces');
436
- log(' wrangler secret put <KEY> - Set production secrets');
437
- log('\\nFor more information, see the README.md file.\\n');
438
-
439
- rl.close();
440
- }
441
-
442
- // Handle errors gracefully
443
- process.on('unhandledRejection', (error) => {
444
- log(\`\\nāŒ Setup failed: \${error.message}\`, colors.red);
445
- process.exit(1);
446
- });
447
-
448
- // Run the setup
449
- setup().catch((error) => {
450
- log(\`\\nāŒ Setup failed: \${error.message}\`, colors.red);
451
- process.exit(1);
452
- });
453
- `;
454
- fs.writeFileSync(path.join(scriptsDir, "setup.js"), setupScriptContent);
455
- // Make setup script executable
456
- if (process.platform !== "win32") {
457
- fs.chmodSync(path.join(scriptsDir, "setup.js"), "755");
458
- }
459
- // Note: Tests directory is not created by default
460
- // Users can add their own tests as needed
461
- // Create greet tool
462
- const greetToolContent = `import { z } from "zod";
463
-
464
- /**
465
- * Greet Tool - Example MCP tool with AgentShield integration
466
- *
467
- * This tool demonstrates proper scopeId configuration for tool auto-discovery.
468
- *
469
- * Configure the corresponding scope in mcpi-runtime-config.ts:
470
- * \`\`\`typescript
471
- * toolProtections: {
472
- * greet: {
473
- * requiresDelegation: false,
474
- * requiredScopes: ["greet:execute"], // ← This becomes the scopeId in proofs
475
- * }
476
- * }
477
- * \`\`\`
478
- *
479
- * The scopeId format is "toolName:action":
480
- * - Tool name: "greet" (extracted before the ":")
481
- * - Action: "execute" (extracted after the ":")
482
- * - Risk level: Auto-determined from action keyword (execute = high)
8
+ * Fetches the Cloudflare MCP-I template
483
9
  *
484
- * Other scopeId examples:
485
- * - "files:read" → Medium risk
486
- * - "files:write" → High risk
487
- * - "database:delete" → Critical risk
10
+ * Generates a complete project structure:
11
+ * - package.json with all scripts
12
+ * - wrangler.toml with all KV namespaces configured
13
+ * - .dev.vars with all secrets
14
+ * - src/index.ts
15
+ * - src/agent.ts
16
+ * - src/tools/greet.ts
17
+ * - src/mcpi-runtime-config.ts
18
+ * - scripts/setup.js for KV namespace creation and key regeneration
19
+ * - Automatically runs setup to create KV namespaces
488
20
  */
489
- export const greetTool = {
490
- name: "greet",
491
- description: "Greet a user by name",
492
- inputSchema: z.object({
493
- name: z.string().describe("The name of the user to greet")
494
- }),
495
- handler: async ({ name }: { name: string }) => {
496
- return {
497
- content: [
498
- {
499
- type: "text" as const,
500
- text: \`Hello, \${name}! Welcome to your Cloudflare MCP server.\`
21
+ export async function fetchCloudflareMcpiTemplate(targetDir, options) {
22
+ // Handle legacy string argument (projectName) for backward compatibility
23
+ const opts = typeof options === 'string'
24
+ ? { packageManager: 'npm', projectName: options }
25
+ : options;
26
+ const { projectName, apikey, projectId, packageManager } = opts;
27
+ const projectNameUpper = projectName.toUpperCase().replace(/-/g, '_');
28
+ console.log(chalk.blue(`\nšŸ—ļø Generating Cloudflare MCP-I project: ${projectName}...`));
29
+ // 1. Create directory structure
30
+ await fs.ensureDir(path.join(targetDir, "src"));
31
+ await fs.ensureDir(path.join(targetDir, "src/tools"));
32
+ await fs.ensureDir(path.join(targetDir, "scripts"));
33
+ // 2. Generate Identity (Keys & DID)
34
+ console.log(chalk.blue("šŸ”‘ Generating cryptographic identity..."));
35
+ const identity = await generateIdentity();
36
+ // 3. Create package.json with all scripts
37
+ const packageJson = {
38
+ name: projectName,
39
+ version: "0.1.0",
40
+ private: true,
41
+ scripts: {
42
+ deploy: "wrangler deploy",
43
+ dev: "wrangler dev",
44
+ start: "wrangler dev",
45
+ test: "vitest",
46
+ "cf-typegen": "wrangler types",
47
+ "setup": "node scripts/setup.js",
48
+ "kv:create-nonce": `wrangler kv:namespace create ${projectNameUpper}_NONCE_CACHE`,
49
+ "kv:create-proof": `wrangler kv:namespace create ${projectNameUpper}_PROOF_ARCHIVE`,
50
+ "kv:create-identity": `wrangler kv:namespace create ${projectNameUpper}_IDENTITY_STORAGE`,
51
+ "kv:create-delegation": `wrangler kv:namespace create ${projectNameUpper}_DELEGATION_STORAGE`,
52
+ "kv:create-tool-protection": `wrangler kv:namespace create ${projectNameUpper}_TOOL_PROTECTION_KV`
53
+ },
54
+ dependencies: {
55
+ "@kya-os/mcp-i-cloudflare": "^1.5.8-canary.48",
56
+ "@modelcontextprotocol/sdk": "^0.6.0",
57
+ "agents": "^0.2.21",
58
+ "hono": "^4.6.3"
59
+ },
60
+ devDependencies: {
61
+ "@cloudflare/vitest-pool-workers": "^0.5.2",
62
+ "@cloudflare/workers-types": "^4.20240925.0",
63
+ "@types/node": "^20.8.3",
64
+ "typescript": "^5.5.2",
65
+ "vitest": "2.0.5",
66
+ "wrangler": "^3.78.12"
501
67
  }
502
- ]
503
68
  };
504
- }
505
- };
506
- `;
507
- fs.writeFileSync(path.join(toolsDir, "greet.ts"), greetToolContent);
508
- // Create mcpi-runtime-config.ts for AgentShield integration
509
- const runtimeConfigContent = `import type { CloudflareRuntimeConfig } from '@kya-os/mcp-i-cloudflare/config';
510
- import type { CloudflareEnv } from '@kya-os/mcp-i-cloudflare';
511
- import { defineConfig } from '@kya-os/mcp-i-cloudflare';
512
-
513
- /**
514
- * Runtime configuration for MCP-I server
515
- *
516
- * This file configures runtime features like proof submission to AgentShield,
517
- * delegation verification, and audit logging.
518
- *
519
- * Environment variables are automatically injected from wrangler.toml (Cloudflare)
520
- * or .env (Node.js). Configure them there:
521
- * - AGENTSHIELD_API_URL: AgentShield API base URL
522
- * - AGENTSHIELD_API_KEY: Your AgentShield API key
523
- * - AGENTSHIELD_PROJECT_ID: Your AgentShield project ID (optional but recommended)
524
- * - MCPI_ENV: "development" or "production"
525
- *
526
- * Tool Protection:
527
- * - Automatically enabled if AGENTSHIELD_API_KEY and TOOL_PROTECTION_KV are set
528
- * - Fetches tool protection config from AgentShield API by project ID
529
- * - Caches config for 5 minutes to minimize API calls
530
- * - Falls back to local config if API is unavailable
531
- * - Configure delegation requirements in AgentShield dashboard
532
- */
533
- export function getRuntimeConfig(env: CloudflareEnv): CloudflareRuntimeConfig {
534
- return defineConfig({
535
- // Only specify overrides - defaults are handled automatically
536
- vars: {
537
- ENVIRONMENT: env.ENVIRONMENT || env.MCPI_ENV || 'development',
538
- AGENTSHIELD_API_KEY: env.AGENTSHIELD_API_KEY,
539
- AGENTSHIELD_API_URL: env.AGENTSHIELD_API_URL,
540
- },
541
- // Tool protection is automatically enabled by defineConfig if AGENTSHIELD_API_KEY is present
542
- // You can override by explicitly setting toolProtection here
543
- // Admin endpoints: automatically enabled by defineConfig if AGENTSHIELD_API_KEY is present
544
- // Uses AGENTSHIELD_API_KEY for authentication (same as tool protection)
545
- // No need to explicitly set admin - defineConfig handles it automatically
546
- // admin: {
547
- // enabled: true, // Auto-enabled by defineConfig when AGENTSHIELD_API_KEY is present
548
- // apiKey: env.ADMIN_API_KEY || env.AGENTSHIELD_API_KEY,
549
- // },
550
- });
551
- }
552
- `;
553
- fs.writeFileSync(path.join(srcDir, "mcpi-runtime-config.ts"), runtimeConfigContent);
554
- // Create main index.ts using MCPICloudflareServer
555
- const indexContent = `import { MCPICloudflareAgent, createMCPIApp, type PrefixedCloudflareEnv } from "@kya-os/mcp-i-cloudflare";
556
- import { greetTool } from "./tools/greet";
557
- import { getRuntimeConfig } from "./mcpi-runtime-config";
558
-
559
- /**
560
- * Extended CloudflareEnv with prefixed KV bindings for multi-agent deployments
561
- */
562
- interface MyPrefixedCloudflareEnv extends PrefixedCloudflareEnv {
563
- ${className.toUpperCase()}_NONCE_CACHE?: KVNamespace;
564
- ${className.toUpperCase()}_PROOF_ARCHIVE?: KVNamespace;
565
- ${className.toUpperCase()}_IDENTITY_STORAGE?: KVNamespace;
566
- ${className.toUpperCase()}_DELEGATION_STORAGE?: KVNamespace;
567
- ${className.toUpperCase()}_TOOL_PROTECTION_KV?: KVNamespace;
568
- }
69
+ await fs.writeJson(path.join(targetDir, "package.json"), packageJson, {
70
+ spaces: 2,
71
+ });
72
+ // 4. Create .dev.vars with ALL secrets
73
+ const devVarsContent = `# Secrets for local development
74
+ # Generated by create-mcpi-app
75
+
76
+ # Agent Identity (DO NOT COMMIT THIS FILE)
77
+ MCP_IDENTITY_PRIVATE_KEY="${identity.privateKey}"
569
78
 
570
- /**
571
- * Your custom MCP agent - only define your tools here!
572
- * All framework complexity (runtime initialization, proof generation, etc.) is handled automatically.
573
- */
574
- export class ${pascalClassName}MCP extends MCPICloudflareAgent {
575
- async registerTools() {
576
- // Register your custom tools - proofs are generated automatically
577
- this.server.tool(
578
- greetTool.name,
579
- greetTool.description,
580
- greetTool.inputSchema.shape,
581
- async (args: { name: string }) => {
582
- return this.executeToolWithProof(
583
- greetTool.name,
584
- args,
585
- greetTool.handler
586
- );
587
- }
588
- );
589
- }
590
- }
79
+ # AgentShield Configuration
80
+ ${apikey ? `AGENTSHIELD_API_KEY="${apikey}"` : '# AGENTSHIELD_API_KEY="sk_YOUR_API_KEY_HERE"'}
591
81
 
592
- // Create and export the app - all routing is handled automatically
593
- export default createMCPIApp({
594
- AgentClass: ${pascalClassName}MCP,
595
- agentName: "${projectName}",
596
- agentVersion: "1.0.0",
597
- envPrefix: "${className.toUpperCase()}",
598
- getRuntimeConfig,
599
- });
82
+ # Admin API Key for cache management
83
+ # Generate a secure key: openssl rand -base64 32
84
+ ADMIN_API_KEY="${generateAdminApiKey()}"
600
85
  `;
601
- fs.writeFileSync(path.join(srcDir, "index.ts"), indexContent);
602
- const wranglerContent = `#:schema node_modules/wrangler/config-schema.json
603
- name = "${wranglerName}"
86
+ await fs.writeFile(path.join(targetDir, ".dev.vars"), devVarsContent);
87
+ // 5. Create wrangler.toml with all KV namespaces (placeholders initially)
88
+ const wranglerToml = `#:schema node_modules/wrangler/config-schema.json
89
+ name = "${projectName}"
604
90
  main = "src/index.ts"
605
- compatibility_date = "2025-06-18"
91
+ compatibility_date = "2024-09-25"
606
92
  compatibility_flags = ["nodejs_compat"]
607
93
 
94
+ # Durable Object binding for MCP Agent state
608
95
  [[durable_objects.bindings]]
609
96
  name = "MCP_OBJECT"
610
- class_name = "${pascalClassName}MCP"
97
+ class_name = "${toPascalCase(projectName)}MCP"
611
98
 
612
99
  [[migrations]]
613
100
  tag = "v1"
614
- new_sqlite_classes = ["${pascalClassName}MCP"]
101
+ new_classes = ["${toPascalCase(projectName)}MCP"]
615
102
 
616
103
  # Cron trigger for proof batch queue flushing (OPTIONAL - CURRENTLY DISABLED)
617
104
  #
@@ -640,8 +127,8 @@ new_sqlite_classes = ["${pascalClassName}MCP"]
640
127
  # This namespace is automatically created by the setup script (npm run setup)
641
128
  # If you need to recreate it: npm run kv:create-nonce
642
129
  [[kv_namespaces]]
643
- binding = "${className.toUpperCase()}_NONCE_CACHE"
644
- id = "your_nonce_kv_namespace_id" # Auto-filled by setup script
130
+ binding = "${projectNameUpper}_NONCE_CACHE"
131
+ id = "TODO_REPLACE_WITH_ID" # Auto-filled by setup script
645
132
 
646
133
  # KV Namespace for proof archive (RECOMMENDED for auditability)
647
134
  #
@@ -652,8 +139,8 @@ id = "your_nonce_kv_namespace_id" # Auto-filled by setup script
652
139
  #
653
140
  # Note: Comment out if you don't need proof archiving
654
141
  [[kv_namespaces]]
655
- binding = "${className.toUpperCase()}_PROOF_ARCHIVE"
656
- id = "your_proof_kv_namespace_id" # Auto-filled by setup script
142
+ binding = "${projectNameUpper}_PROOF_ARCHIVE"
143
+ id = "TODO_REPLACE_WITH_ID" # Auto-filled by setup script
657
144
 
658
145
  # KV Namespace for identity storage (RECOMMENDED for persistent agent identity)
659
146
  #
@@ -662,8 +149,8 @@ id = "your_proof_kv_namespace_id" # Auto-filled by setup script
662
149
  # This namespace is automatically created by the setup script (npm run setup)
663
150
  # If you need to recreate it: npm run kv:create-identity
664
151
  [[kv_namespaces]]
665
- binding = "${className.toUpperCase()}_IDENTITY_STORAGE"
666
- id = "your_identity_kv_namespace_id" # Auto-filled by setup script
152
+ binding = "${projectNameUpper}_IDENTITY_STORAGE"
153
+ id = "TODO_REPLACE_WITH_ID" # Auto-filled by setup script
667
154
 
668
155
  # KV Namespace for delegation storage (REQUIRED for OAuth/delegation flows)
669
156
  #
@@ -672,10 +159,10 @@ id = "your_identity_kv_namespace_id" # Auto-filled by setup script
672
159
  # This namespace is automatically created by the setup script (npm run setup)
673
160
  # If you need to recreate it: npm run kv:create-delegation
674
161
  [[kv_namespaces]]
675
- binding = "${className.toUpperCase()}_DELEGATION_STORAGE"
676
- id = "your_delegation_kv_namespace_id" # Auto-filled by setup script
162
+ binding = "${projectNameUpper}_DELEGATION_STORAGE"
163
+ id = "TODO_REPLACE_WITH_ID" # Auto-filled by setup script
677
164
 
678
- # KV Namespace for tool protection config (${apikey ? "ENABLED" : "OPTIONAL"} for dashboard-controlled delegation)
165
+ # KV Namespace for tool protection config (ENABLED for dashboard-controlled delegation)
679
166
  #
680
167
  # šŸ†• Enables dynamic tool protection configuration from AgentShield dashboard
681
168
  # Caches which tools require user delegation based on dashboard toggle switches
@@ -693,21 +180,40 @@ id = "your_delegation_kv_namespace_id" # Auto-filled by setup script
693
180
  # Note: This namespace is REQUIRED when using AgentShield API key (--apikey)
694
181
  # It will be automatically created by the setup script (npm run setup)
695
182
  [[kv_namespaces]]
696
- binding = "${className.toUpperCase()}_TOOL_PROTECTION_KV"
697
- id = "your_tool_protection_kv_id" # Auto-filled by setup script
183
+ binding = "${projectNameUpper}_TOOL_PROTECTION_KV"
184
+ id = "TODO_REPLACE_WITH_ID" # Auto-filled by setup script
698
185
 
699
186
  [vars]
187
+ # Agent DID (public identifier - safe to commit)
188
+ MCP_IDENTITY_AGENT_DID = "${identity.did}"
189
+
190
+ # Public identity key (safe to commit - not sensitive)
191
+ MCP_IDENTITY_PUBLIC_KEY = "${identity.publicKey}"
192
+
193
+ # Private identity key (SECRET - NOT declared here)
194
+ # For local development: Add to .dev.vars file
195
+ # For production: Use wrangler secret put MCP_IDENTITY_PRIVATE_KEY
196
+
197
+ # ALLOWED_ORIGINS for CORS (update for production)
198
+ ALLOWED_ORIGINS = "https://claude.ai,https://app.anthropic.com"
199
+
200
+ # DO routing strategy: "session" for dev, "shard" for production high-load
201
+ DO_ROUTING_STRATEGY = "session"
202
+ DO_SHARD_COUNT = "10" # Number of shards if using shard strategy
203
+
700
204
  XMCP_I_TS_SKEW_SEC = "120"
701
205
  XMCP_I_SESSION_TTL = "1800"
702
206
 
703
207
  # AgentShield Integration (https://kya.vouched.id)
704
208
  AGENTSHIELD_API_URL = "https://kya.vouched.id"
209
+
705
210
  # AGENTSHIELD_PROJECT_ID - Your project ID from AgentShield dashboard (e.g., "batman-txh0ae")
706
211
  # Required for project-scoped tool protection configuration (recommended)
707
212
  # Find it in your dashboard URL: https://kya.vouched.id/dashboard/projects/{PROJECT_ID}
708
213
  # Or in your project settings
709
214
  # This is not sensitive, so it's safe to keep a value here
710
- AGENTSHIELD_PROJECT_ID = "${projectId || ""}"
215
+ ${projectId ? `AGENTSHIELD_PROJECT_ID = "${projectId}"` : '# AGENTSHIELD_PROJECT_ID = "your-project-id"'}
216
+
711
217
  MCPI_ENV = "development"
712
218
 
713
219
  # Secrets (NOT declared here - see instructions below)
@@ -716,467 +222,289 @@ MCPI_ENV = "development"
716
222
  # $ wrangler secret put MCP_IDENTITY_PRIVATE_KEY
717
223
  # $ wrangler secret put AGENTSHIELD_API_KEY
718
224
  # $ wrangler secret put ADMIN_API_KEY
225
+
719
226
  # Note: .dev.vars is git-ignored and contains actual secret values for local dev
720
227
 
721
228
  # Optional: MCP Server URL for tool discovery and consent page generation
722
229
  # Uncomment to explicitly set your MCP server URL (auto-detected from request origin if not set)
723
230
  # IMPORTANT: Use base URL WITHOUT /mcp suffix (e.g., "https://your-worker.workers.dev")
724
231
  # The consent pages are at /consent, not /mcp/consent
725
- # MCP_SERVER_URL = "https://your-worker.workers.dev"
232
+ # MCP_SERVER_URL = "https://${projectName}.YOUR-SUBDOMAIN.workers.dev"
726
233
  `;
727
- fs.writeFileSync(path.join(projectPath, "wrangler.toml"), wranglerContent);
728
- // Generate persistent identity for Cloudflare Worker
729
- if (!skipIdentity) {
730
- try {
731
- const identity = await generateIdentity();
732
- // Logs removed - identity generation is silent
733
- // Read existing wrangler.toml
734
- const wranglerPath = path.join(projectPath, "wrangler.toml");
735
- let wranglerTomlContent = fs.readFileSync(wranglerPath, "utf8");
736
- // Find [vars] section and add identity environment variables
737
- // Add ALL identity variables (empty values will be overridden by .dev.vars)
738
- const varsMatch = wranglerTomlContent.match(/\[vars\]/);
739
- if (varsMatch) {
740
- const insertPosition = varsMatch.index + varsMatch[0].length;
741
- const identityVars = `
742
- # Agent DID (public identifier - safe to commit)
743
- MCP_IDENTITY_AGENT_DID = "${identity.did}"
744
-
745
- # Public identity key (safe to commit - not sensitive)
746
- MCP_IDENTITY_PUBLIC_KEY = "${identity.publicKey}"
747
-
748
- # Private identity key (SECRET - NOT declared here)
749
- # For local development: Add to .dev.vars file
750
- # For production: Use wrangler secret put MCP_IDENTITY_PRIVATE_KEY
751
-
752
- # ALLOWED_ORIGINS for CORS (update for production)
753
- ALLOWED_ORIGINS = "https://claude.ai,https://app.anthropic.com"
754
-
755
- # DO routing strategy: "session" for dev, "shard" for production high-load
756
- DO_ROUTING_STRATEGY = "session"
757
- DO_SHARD_COUNT = "10" # Number of shards if using shard strategy
234
+ await fs.writeFile(path.join(targetDir, "wrangler.toml"), wranglerToml);
235
+ // 6. Create src/index.ts (Entry Point)
236
+ const indexTs = `import { createMCPICloudflareAdapter } from "@kya-os/mcp-i-cloudflare";
237
+ import { ${toPascalCase(projectName)}Agent } from "./agent";
238
+ import config from "./mcpi-runtime-config";
239
+
240
+ // Create the adapter
241
+ const adapter = createMCPICloudflareAdapter({
242
+ agent: ${toPascalCase(projectName)}Agent,
243
+ env: {} as any, // Will be injected by Cloudflare
244
+ serverInfo: {
245
+ name: "${projectName}",
246
+ version: "1.0.0",
247
+ },
248
+ tools: config.tools, // Pass tools from config
249
+ });
758
250
 
251
+ // Export the fetch handler and Durable Object
252
+ export default adapter;
253
+ export { ${toPascalCase(projectName)}MCP } from "./agent";
759
254
  `;
760
- // Remove any secret declarations from [vars] (they should not be here)
761
- // Secrets go in .dev.vars for local dev, wrangler secret put for production
762
- wranglerTomlContent = wranglerTomlContent.replace(/^\s*MCP_IDENTITY_PRIVATE_KEY\s*=.*$/gm, `# MCP_IDENTITY_PRIVATE_KEY - SECRET (not declared here, see .dev.vars or wrangler secret put)`);
763
- wranglerTomlContent = wranglerTomlContent.replace(/^\s*AGENTSHIELD_API_KEY\s*=.*$/gm, `# AGENTSHIELD_API_KEY - SECRET (not declared here, see .dev.vars or wrangler secret put)`);
764
- wranglerTomlContent = wranglerTomlContent.replace(/^\s*ADMIN_API_KEY\s*=.*$/gm, `# ADMIN_API_KEY - SECRET (not declared here, see .dev.vars or wrangler secret put)`);
765
- // Update public key if it exists (safe to keep in [vars])
766
- if (/MCP_IDENTITY_PUBLIC_KEY\s*=/.test(wranglerTomlContent)) {
767
- wranglerTomlContent = wranglerTomlContent.replace(/MCP_IDENTITY_PUBLIC_KEY\s*=\s*"[^"]*"/, `MCP_IDENTITY_PUBLIC_KEY = "${identity.publicKey}"`);
768
- }
769
- // Update AGENTSHIELD_PROJECT_ID if it exists and projectId is provided
770
- // This is safe to keep a value since it's not sensitive
771
- if (projectId &&
772
- /AGENTSHIELD_PROJECT_ID\s*=/.test(wranglerTomlContent)) {
773
- wranglerTomlContent = wranglerTomlContent.replace(/AGENTSHIELD_PROJECT_ID\s*=\s*"[^"]*"/, `AGENTSHIELD_PROJECT_ID = "${projectId}"`);
774
- }
775
- // Check if non-secret variables already exist in wrangler.toml
776
- const hasIdentityDid = /MCP_IDENTITY_AGENT_DID\s*=/.test(wranglerTomlContent);
777
- const hasIdentityPublicKey = /MCP_IDENTITY_PUBLIC_KEY\s*=/.test(wranglerTomlContent);
778
- // Only insert identity vars if they don't already exist
779
- const needsInsertion = !hasIdentityDid || !hasIdentityPublicKey;
780
- if (needsInsertion) {
781
- wranglerTomlContent =
782
- wranglerTomlContent.slice(0, insertPosition) +
783
- identityVars +
784
- wranglerTomlContent.slice(insertPosition);
785
- }
786
- // Write updated wrangler.toml (without secrets)
787
- fs.writeFileSync(wranglerPath, wranglerTomlContent);
788
- // Create .dev.vars file for local development (git-ignored)
789
- // Only contains SECRETS (not public keys or project IDs)
790
- const devVarsPath = path.join(projectPath, ".dev.vars");
791
- const devVarsContent = `# Local development secrets (DO NOT COMMIT)
792
- # This file is git-ignored and contains sensitive data
793
- #
794
- # HOW IT WORKS:
795
- # 1. Secrets are NOT declared in wrangler.toml [vars] (avoids conflicts)
796
- # 2. This file (.dev.vars) provides secrets for local development
797
- # 3. Production uses: wrangler secret put VARIABLE_NAME
798
- #
799
- # Non-secrets (MCP_IDENTITY_PUBLIC_KEY, AGENTSHIELD_PROJECT_ID) are in wrangler.toml
255
+ await fs.writeFile(path.join(targetDir, "src/index.ts"), indexTs);
256
+ // 7. Create src/agent.ts (Agent Class)
257
+ const agentTs = `import { MCPICloudflareAgent } from "@kya-os/mcp-i-cloudflare";
258
+ import config from "./mcpi-runtime-config";
259
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
800
260
 
801
- # Private identity key (generated by create-mcpi-app)
802
- MCP_IDENTITY_PRIVATE_KEY="${identity.privateKey}"
261
+ /**
262
+ * MCP-I Agent Implementation
263
+ */
264
+ export class ${toPascalCase(projectName)}Agent extends MCPICloudflareAgent {
265
+ /**
266
+ * Get tools definition from runtime config
267
+ */
268
+ protected getTools(): Tool[] {
269
+ return config.tools || [];
270
+ }
803
271
 
804
- # AgentShield API key (get from https://kya.vouched.id/dashboard)
805
- AGENTSHIELD_API_KEY="${apikey || ""}"${apikey ? " # Provided via --apikey flag" : ""}
272
+ /**
273
+ * Handle tool execution by routing to handlers defined in config
274
+ */
275
+ protected async executeTool(name: string, args: any): Promise<any> {
276
+ // Find the tool handler in our config
277
+ const toolDef = config.tools?.find(t => t.name === name);
278
+
279
+ if (toolDef && (toolDef as any).handler) {
280
+ return (toolDef as any).handler(args);
281
+ }
282
+
283
+ throw new Error(\`Unknown tool: \${name}\`);
284
+ }
285
+ }
806
286
 
807
- # Admin API key for protected endpoints (set to same value as AGENTSHIELD_API_KEY)
808
- ADMIN_API_KEY="${apikey || ""}"${apikey ? " # Set to same value as AGENTSHIELD_API_KEY" : ""}
287
+ // Export Durable Object class
288
+ export class ${toPascalCase(projectName)}MCP extends ${toPascalCase(projectName)}Agent {}
809
289
  `;
810
- fs.writeFileSync(devVarsPath, devVarsContent);
811
- // Create .dev.vars.example for reference
812
- const devVarsExamplePath = path.join(projectPath, ".dev.vars.example");
813
- const devVarsExampleContent = `# Copy this file to .dev.vars and fill in your values
814
- # DO NOT commit .dev.vars to version control
815
- #
816
- # NOTE: Only secrets go here. Non-secrets (MCP_IDENTITY_PUBLIC_KEY, AGENTSHIELD_PROJECT_ID)
817
- # are in wrangler.toml [vars] and can be committed safely.
818
-
819
- # Private identity key (generate with: npx @kya-os/create-mcpi-app regenerate-identity)
820
- MCP_IDENTITY_PRIVATE_KEY="your-private-key-here"
290
+ await fs.writeFile(path.join(targetDir, "src/agent.ts"), agentTs);
291
+ // 8. Create src/tools/greet.ts
292
+ const greetToolTs = `import type { ToolDefinition } from "@kya-os/mcp-i-cloudflare";
821
293
 
822
- # AgentShield API key (get from https://kya.vouched.id/dashboard)
823
- AGENTSHIELD_API_KEY="your-api-key-here"
824
-
825
- # Admin API key for protected endpoints (set to same value as AGENTSHIELD_API_KEY)
826
- ADMIN_API_KEY="your-admin-key-here"
827
- `;
828
- fs.writeFileSync(devVarsExamplePath, devVarsExampleContent);
829
- }
830
- }
831
- catch (error) {
832
- // Logs removed - errors are silent during scaffolding
833
- }
834
- }
835
- // Create tsconfig.json
836
- fs.ensureDirSync(projectPath); // Ensure directory exists before writing
837
- const tsconfigContent = {
838
- compilerOptions: {
839
- target: "ES2022",
840
- module: "ES2022",
841
- lib: ["ES2022"],
842
- types: ["@cloudflare/workers-types"],
843
- moduleResolution: "bundler",
844
- resolveJsonModule: true,
845
- allowSyntheticDefaultImports: true,
846
- esModuleInterop: true,
847
- strict: true,
848
- skipLibCheck: true,
849
- forceConsistentCasingInFileNames: true,
850
- },
851
- include: ["src/**/*"],
852
- };
853
- fs.writeJsonSync(path.join(projectPath, "tsconfig.json"), tsconfigContent, {
854
- spaces: 2,
855
- });
856
- // Create .gitignore
857
- const gitignoreContent = `node_modules/
858
- dist/
859
- .wrangler/
860
- .dev.vars
861
- .env
862
- .env.local
863
- *.log
864
- `;
865
- fs.writeFileSync(path.join(projectPath, ".gitignore"), gitignoreContent);
866
- // Create .npmrc to suppress harmless deprecation warnings
867
- const npmrcContent = `# Suppress deprecation warnings from transitive dependencies
868
- # These warnings are harmless and don't affect functionality
869
- # They come from: inflight (glob@7), phin (jimp), rimraf (del), glob (rimraf), node-domexception (node-fetch)
870
- # Will be resolved when upstream packages update their dependencies
871
- loglevel=error
294
+ export const greetTool: ToolDefinition = {
295
+ name: "greet",
296
+ description: "Greet the user",
297
+ inputSchema: {
298
+ type: "object",
299
+ properties: {
300
+ name: { type: "string", description: "Name of the person to greet" },
301
+ },
302
+ required: ["name"],
303
+ },
304
+ handler: async (args: { name: string }) => {
305
+ return {
306
+ content: [
307
+ {
308
+ type: "text",
309
+ text: \`Hello, \${args.name}! Welcome to ${projectName}.\`,
310
+ },
311
+ ],
312
+ };
313
+ },
314
+ };
872
315
  `;
873
- fs.writeFileSync(path.join(projectPath, ".npmrc"), npmrcContent);
874
- // Create README.md
875
- const readmeContent = `# ${projectName}
876
-
877
- MCP server running on Cloudflare Workers with MCP-I identity features, cryptographic proofs, and full SSE/HTTP streaming support.
878
-
879
- ## Features
880
-
881
- - āœ… **MCP Protocol Support**: SSE and HTTP streaming transports
882
- - āœ… **Cryptographic Identity**: DID-based agent identity with Ed25519 signatures
883
- - āœ… **Proof Generation**: Every tool call generates a cryptographic proof
884
- - āœ… **Audit Logging**: Track all operations with proof IDs and signatures
885
- - āœ… **Nonce Protection**: Replay attack prevention via KV-backed nonce cache
886
- - āœ… **Proof Archiving**: Optional KV storage for proof history
887
-
888
- ## Quick Start
889
-
890
- ### 1. Install Dependencies
891
-
892
- \`\`\`bash
893
- ${packageManager} install
894
- \`\`\`
895
-
896
- ### 2. Create KV Namespaces
897
-
898
- #### Create All KV Namespaces (Recommended)
899
-
900
- \`\`\`bash
901
- ${packageManager === "npm" ? "npm run" : packageManager} kv:create
902
- \`\`\`
903
-
904
- This creates all 5 KV namespaces at once:
905
- - \`NONCE_CACHE\` - Replay attack prevention (Required)
906
- - \`PROOF_ARCHIVE\` - Cryptographic proof storage (Recommended)
907
- - \`IDENTITY_STORAGE\` - Agent identity persistence (Recommended)
908
- - \`DELEGATION_STORAGE\` - OAuth delegation storage (Required for delegation)
909
- - \`TOOL_PROTECTION_KV\` - Dashboard-controlled permissions (Optional)
910
-
911
- Copy the namespace IDs from the output and update each one in \`wrangler.toml\`:
912
-
913
- \`\`\`toml
914
- [[kv_namespaces]]
915
- binding = "NONCE_CACHE"
916
- id = "your_nonce_kv_id_here" # ← Update this
917
-
918
- [[kv_namespaces]]
919
- binding = "PROOF_ARCHIVE"
920
- id = "your_proof_kv_id_here" # ← Update this
921
-
922
- [[kv_namespaces]]
923
- binding = "IDENTITY_STORAGE"
924
- id = "your_identity_kv_id_here" # ← Update this
925
-
926
- [[kv_namespaces]]
927
- binding = "DELEGATION_STORAGE"
928
- id = "your_delegation_kv_id_here" # ← Update this
929
-
930
- [[kv_namespaces]]
931
- binding = "TOOL_PROTECTION_KV"
932
- id = "your_tool_protection_kv_id_here" # ← Update this
933
- \`\`\`
934
-
935
- **Note:** You can also create namespaces individually:
936
- - \`${packageManager === "npm" ? "npm run" : packageManager} kv:create-nonce\` - Create nonce cache only
937
- - \`${packageManager === "npm" ? "npm run" : packageManager} kv:create-proof\` - Create proof archive only
938
- - \`${packageManager === "npm" ? "npm run" : packageManager} kv:create-identity\` - Create identity storage only
939
- - \`${packageManager === "npm" ? "npm run" : packageManager} kv:create-delegation\` - Create delegation storage only
940
- - \`${packageManager === "npm" ? "npm run" : packageManager} kv:create-tool-protection\` - Create tool protection cache only
941
-
942
- ### 3. Test Locally
943
-
944
- \`\`\`bash
945
- ${packageManager === "npm" ? "npm run" : packageManager} dev
946
- \`\`\`
947
-
948
- ### 4. Deploy
949
-
950
- #### Production Deployment
951
-
952
- Secrets are **not** declared in \`wrangler.toml\` to avoid conflicts. Set them using:
953
-
954
- \`\`\`bash
955
- wrangler secret put MCP_IDENTITY_PRIVATE_KEY
956
- wrangler secret put AGENTSHIELD_API_KEY
957
- wrangler secret put ADMIN_API_KEY
958
- \`\`\`
959
-
960
- **Tip:** Copy values from your \`.dev.vars\` file when prompted.
316
+ await fs.writeFile(path.join(targetDir, "src/tools/greet.ts"), greetToolTs);
317
+ // 9. Create src/mcpi-runtime-config.ts
318
+ const runtimeConfigTs = `import type { ToolDefinition } from "@kya-os/mcp-i-cloudflare";
319
+ import { greetTool } from "./tools/greet";
961
320
 
962
- **Note:** \`MCP_IDENTITY_PUBLIC_KEY\` and \`AGENTSHIELD_PROJECT_ID\` are not secrets and are already in \`wrangler.toml\` with values.
321
+ interface RuntimeConfig {
322
+ tools: ToolDefinition[];
323
+ }
963
324
 
964
- Now deploy:
325
+ const config: RuntimeConfig = {
326
+ tools: [
327
+ greetTool,
328
+ // Add more tools here
329
+ ],
330
+ };
965
331
 
966
- \`\`\`bash
967
- ${packageManager === "npm" ? "npm run" : packageManager} deploy
968
- \`\`\`
332
+ export default config;
333
+ `;
334
+ await fs.writeFile(path.join(targetDir, "src/mcpi-runtime-config.ts"), runtimeConfigTs);
335
+ // 10. Create scripts/setup.js (KV Namespace Creation & Key Regeneration Script)
336
+ const setupJs = `#!/usr/bin/env node
337
+ const { execSync } = require('child_process');
338
+ const fs = require('fs');
339
+ const path = require('path');
340
+ const crypto = require('crypto');
969
341
 
970
- ## Connect with Claude Desktop
342
+ const projectName = '${projectName}';
343
+ const projectNameUpper = '${projectNameUpper}';
971
344
 
972
- Add to \`~/Library/Application Support/Claude/claude_desktop_config.json\`:
345
+ console.log('šŸš€ Setting up MCP-I Cloudflare Worker...');
346
+ console.log('');
973
347
 
974
- \`\`\`json
975
- {
976
- "mcpServers": {
977
- "${projectName}": {
978
- "command": "npx",
979
- "args": ["mcp-remote", "https://your-worker.workers.dev/sse"]
348
+ // Function to execute command and capture output
349
+ function exec(command, silent = false) {
350
+ try {
351
+ const output = execSync(command, { encoding: 'utf8', stdio: silent ? 'pipe' : 'inherit' });
352
+ return output?.trim();
353
+ } catch (error) {
354
+ if (!silent) {
355
+ console.error(\`āŒ Command failed: \${command}\`);
356
+ console.error(error.message);
980
357
  }
358
+ return null;
981
359
  }
982
360
  }
983
- \`\`\`
984
-
985
- **Note:** Use the \`/sse\` endpoint for Claude Desktop compatibility.
986
-
987
- Restart Claude Desktop and test: "Use the greet tool to say hello to Alice"
988
-
989
- ## Adding Tools
990
-
991
- Create tools in \`src/tools/\`:
992
-
993
- \`\`\`typescript
994
- import { z } from "zod";
995
-
996
- export const myTool = {
997
- name: "my_tool",
998
- description: "Tool description",
999
- inputSchema: z.object({
1000
- input: z.string().describe("Input parameter")
1001
- }),
1002
- handler: async ({ input }: { input: string }) => ({
1003
- content: [{ type: "text" as const, text: \`Result: \${input}\` }]
1004
- })
1005
- };
1006
- \`\`\`
1007
-
1008
- Register in \`src/index.ts\` init method:
1009
361
 
1010
- \`\`\`typescript
1011
- this.server.tool(
1012
- myTool.name,
1013
- myTool.description,
1014
- myTool.inputSchema.shape,
1015
- myTool.handler
1016
- );
1017
- \`\`\`
1018
-
1019
- ## Endpoints
1020
-
1021
- - \`/health\` - Health check
1022
- - \`/sse\` - SSE transport for MCP
1023
- - \`/mcp\` - Streamable HTTP transport for MCP
1024
- - \`/oauth/callback\` - OAuth callback for delegation flows
1025
- - \`/admin/clear-cache\` - Clear tool protection cache (requires API key)
1026
-
1027
- ## Viewing Cryptographic Proofs
1028
-
1029
- Every tool call generates a cryptographic proof that's logged to the console:
1030
-
1031
- \`\`\`bash
1032
- ${packageManager === "npm" ? "npm run" : packageManager} dev
1033
- \`\`\`
1034
-
1035
- When you call a tool, you'll see logs like:
1036
-
1037
- \`\`\`
1038
- [MCP-I] Initialized with DID: did:web:localhost:agents:key-abc123
1039
- [MCP-I Proof] {
1040
- tool: 'greet',
1041
- did: 'did:web:localhost:agents:key-abc123',
1042
- proofId: 'proof_1234567890_abcd',
1043
- signature: 'mNYP8x2k9FqV3...'
362
+ // Function to extract namespace ID from wrangler output
363
+ function extractNamespaceId(output) {
364
+ // Match patterns like: id = "abc123" or { id: "abc123" }
365
+ const match = output.match(/id\\s*[:=]\\s*"([^"]+)"/);
366
+ return match ? match[1] : null;
1044
367
  }
1045
- \`\`\`
1046
-
1047
- ### Proof Archives (Optional)
1048
-
1049
- If you configured the \`PROOF_ARCHIVE\` KV namespace, proofs are also stored for querying:
1050
-
1051
- \`\`\`bash
1052
- # List all proofs
1053
- wrangler kv:key list --namespace-id=your_proof_kv_id
1054
-
1055
- # View a specific proof
1056
- wrangler kv:key get "proof_1234567890_abcd" --namespace-id=your_proof_kv_id
1057
- \`\`\`
1058
-
1059
- ## Identity Management
1060
-
1061
- Your agent's cryptographic identity is stored in Durable Objects state. To view your agent's DID:
1062
-
1063
- 1. Check the logs during \`init()\` - it prints the DID
1064
- 2. Or query the runtime: \`await mcpiRuntime.getIdentity()\`
1065
-
1066
- The identity includes:
1067
- - \`did\`: Decentralized identifier (e.g., \`did:web:your-worker.workers.dev:agents:key-xyz\`)
1068
- - \`publicKey\`: Ed25519 public key for signature verification
1069
- - \`privateKey\`: Ed25519 private key (secured in Durable Object state)
1070
-
1071
- ## AgentShield Integration
1072
-
1073
- This project is configured to send cryptographic proofs to AgentShield for audit trails and compliance monitoring.
1074
-
1075
- ### Setup
1076
368
 
1077
- 1. **Get your AgentShield API key**:
1078
- - Sign up at https://kya.vouched.id
1079
- - Create a project
1080
- - Copy your API key from the dashboard
1081
-
1082
- 2. **Update \`wrangler.toml\`**:
1083
- \`\`\`toml
1084
- [vars]
1085
- AGENTSHIELD_API_URL = "https://kya.vouched.id"
1086
- AGENTSHIELD_API_KEY = "sk_your_actual_key_here" # ← Replace this
1087
- MCPI_ENV = "development"
1088
- \`\`\`
1089
-
1090
- 3. **Test proof submission**:
1091
- \`\`\`bash
1092
- ${packageManager === "npm" ? "npm run" : packageManager} dev
1093
- \`\`\`
1094
-
1095
- Call a tool and check the logs:
1096
- \`\`\`
1097
- [AgentShield] Submitting proof: { did: 'did:web:...', sessionId: '...', jwsFormat: 'valid (3 parts)' }
1098
- [AgentShield] āœ… Proofs accepted: 1
1099
- \`\`\`
1100
-
1101
- 4. **View proofs in dashboard**:
1102
- - Go to https://kya.vouched.id/dashboard
1103
- - Select your project
1104
- - Click "Interactions" tab
1105
- - See your proofs in real-time
1106
-
1107
- ### Configuration
1108
-
1109
- The AgentShield integration is configured in \`src/mcpi-runtime-config.ts\`. You can customize:
1110
- - Proof batch size (\`maxBatchSize\`)
1111
- - Flush interval (\`flushIntervalMs\`)
1112
- - Retry policy (\`maxRetries\`)
1113
- - Tool protection rules (\`toolProtections\`)
1114
-
1115
- ### Dashboard-Controlled Tool Protection (Advanced)
1116
-
1117
- šŸ†• **NEW**: Control which tools require user delegation directly from the AgentShield dashboard - no code changes needed!
1118
-
1119
- Instead of hardcoding \`requiresDelegation\` in your config, enable dynamic tool protection:
1120
-
1121
- 1. **Create Tool Protection KV namespace**:
1122
- \`\`\`bash
1123
- ${packageManager === "npm" ? "npm run" : packageManager} kv:create-tool-protection
1124
- \`\`\`
1125
-
1126
- 2. **Uncomment TOOL_PROTECTION_KV in \`wrangler.toml\`**:
1127
- \`\`\`toml
1128
- [[kv_namespaces]]
1129
- binding = "TOOL_PROTECTION_KV"
1130
- id = "your_tool_protection_kv_id" # ← Add the ID from step 1
1131
- \`\`\`
1132
-
1133
- 3. **Enable Tool Protection Service in \`src/mcpi-runtime-config.ts\`**:
1134
- - Uncomment the import: \`import { CloudflareRuntime } from "@kya-os/mcp-i-cloudflare";\`
1135
- - Uncomment the \`toolProtectionService\` configuration block
1136
-
1137
- 4. **Deploy and test**:
1138
- \`\`\`bash
1139
- ${packageManager === "npm" ? "npm run" : packageManager} deploy
1140
- \`\`\`
1141
-
1142
- 5. **Control delegation from dashboard**:
1143
- - Go to https://kya.vouched.id/dashboard
1144
- - Select your project → "Tools" tab
1145
- - Toggle "Require Delegation" for any tool
1146
- - Changes apply in real-time (5-minute cache)
1147
-
1148
- **Benefits:**
1149
- - Update tool permissions without redeploying
1150
- - Test delegation flows instantly
1151
- - Different requirements per environment (dev vs prod)
1152
- - Automatic tool discovery from proof submissions
1153
-
1154
- **Note:** The first time a tool is called, it auto-discovers in the dashboard. The \`requiresDelegation\` toggle will appear after the first proof is submitted.
1155
-
1156
- ### Disable AgentShield (Optional)
1157
-
1158
- If you don't want to use AgentShield, edit \`src/mcpi-runtime-config.ts\`:
1159
-
1160
- \`\`\`typescript
1161
- proofing: {
1162
- enabled: false, // Disable proof submission
1163
- // ...
369
+ // Function to update wrangler.toml with namespace ID
370
+ function updateWranglerToml(binding, namespaceId) {
371
+ const wranglerPath = path.join(__dirname, '..', 'wrangler.toml');
372
+ let content = fs.readFileSync(wranglerPath, 'utf8');
373
+
374
+ // Find the binding section and update the ID
375
+ const bindingPattern = new RegExp(\`binding\\\\s*=\\\\s*"\${binding}"[^\\\\[]*id\\\\s*=\\\\s*"[^"]*"\`, 's');
376
+ content = content.replace(bindingPattern, (match) => {
377
+ return match.replace(/id\\s*=\\s*"[^"]*"/, \`id = "\${namespaceId}"\`);
378
+ });
379
+
380
+ fs.writeFileSync(wranglerPath, content);
381
+ console.log(\`āœ… Updated wrangler.toml with \${binding} namespace ID: \${namespaceId}\`);
1164
382
  }
1165
- \`\`\`
1166
-
1167
- Or simply don't configure the \`AGENTSHIELD_API_KEY\` environment variable.
1168
383
 
1169
- ## References
384
+ // Create KV namespaces
385
+ const namespaces = [
386
+ { binding: \`\${projectNameUpper}_NONCE_CACHE\`, name: 'NONCE_CACHE', description: 'Nonce cache for replay attack prevention' },
387
+ { binding: \`\${projectNameUpper}_PROOF_ARCHIVE\`, name: 'PROOF_ARCHIVE', description: 'Proof archive for auditability' },
388
+ { binding: \`\${projectNameUpper}_IDENTITY_STORAGE\`, name: 'IDENTITY_STORAGE', description: 'Identity storage for persistent agent identity' },
389
+ { binding: \`\${projectNameUpper}_DELEGATION_STORAGE\`, name: 'DELEGATION_STORAGE', description: 'Delegation storage for OAuth flows' },
390
+ { binding: \`\${projectNameUpper}_TOOL_PROTECTION_KV\`, name: 'TOOL_PROTECTION_KV', description: 'Tool protection configuration cache' },
391
+ ];
392
+
393
+ console.log('šŸ“¦ Creating KV namespaces...');
394
+ console.log('');
395
+
396
+ for (const ns of namespaces) {
397
+ console.log(\`Creating \${ns.name} (\${ns.description})...\`);
398
+
399
+ // Try to create the namespace
400
+ const output = exec(\`wrangler kv:namespace create \${ns.binding}\`, true);
401
+
402
+ if (output) {
403
+ const namespaceId = extractNamespaceId(output);
404
+ if (namespaceId) {
405
+ updateWranglerToml(ns.binding, namespaceId);
406
+ } else {
407
+ console.log(\`āš ļø Could not extract namespace ID for \${ns.binding}. You may need to update wrangler.toml manually.\`);
408
+ console.log(\` Output: \${output}\`);
409
+ }
410
+ } else {
411
+ // Namespace might already exist, try to list and find it
412
+ console.log(\` Checking if namespace already exists...\`);
413
+ const listOutput = exec(\`wrangler kv:namespace list\`, true);
414
+ if (listOutput && listOutput.includes(ns.binding)) {
415
+ console.log(\` āœ“ Namespace \${ns.binding} already exists\`);
416
+ } else {
417
+ console.log(\` āš ļø Could not create or find namespace \${ns.binding}. You may need to create it manually.\`);
418
+ }
419
+ }
420
+ }
1170
421
 
1171
- - [Cloudflare Agents MCP](https://developers.cloudflare.com/agents/model-context-protocol/)
1172
- - [MCP Specification](https://spec.modelcontextprotocol.io/)
1173
- - [MCP-I Documentation](https://github.com/kya-os/xmcp-i)
422
+ console.log('');
423
+ console.log('✨ Setup complete!');
424
+ console.log('');
425
+ console.log('Next steps:');
426
+ console.log('1. Review wrangler.toml to ensure all namespace IDs are populated');
427
+ console.log('2. Run "npm run dev" to start the development server');
428
+ console.log('3. Run "npm run deploy" to deploy to Cloudflare Workers');
429
+ console.log('');
430
+ console.log('For production deployment:');
431
+ console.log(' wrangler secret put MCP_IDENTITY_PRIVATE_KEY');
432
+ console.log(' wrangler secret put AGENTSHIELD_API_KEY');
433
+ console.log(' wrangler secret put ADMIN_API_KEY');
434
+ `;
435
+ await fs.writeFile(path.join(targetDir, "scripts/setup.js"), setupJs);
436
+ await fs.chmod(path.join(targetDir, "scripts/setup.js"), '755');
437
+ // 11. Create tsconfig.json
438
+ const tsConfig = {
439
+ compilerOptions: {
440
+ target: "esnext",
441
+ module: "esnext",
442
+ moduleResolution: "bundler",
443
+ types: ["@cloudflare/workers-types", "vitest/globals"],
444
+ strict: true,
445
+ skipLibCheck: true,
446
+ noEmit: true,
447
+ },
448
+ include: ["src/**/*"],
449
+ exclude: ["node_modules"],
450
+ };
451
+ await fs.writeJson(path.join(targetDir, "tsconfig.json"), tsConfig, {
452
+ spaces: 2,
453
+ });
454
+ // 12. Create .gitignore
455
+ const gitignore = `node_modules
456
+ dist
457
+ .wrangler
458
+ .dev.vars
1174
459
  `;
1175
- fs.writeFileSync(path.join(projectPath, "README.md"), readmeContent);
460
+ await fs.writeFile(path.join(targetDir, ".gitignore"), gitignore);
461
+ console.log(chalk.green("āœ” Created Cloudflare MCP-I template files"));
462
+ console.log(chalk.gray(" - Generated identity keys in .dev.vars"));
463
+ console.log(chalk.gray(" - Configured wrangler.toml with KV namespaces"));
464
+ console.log(chalk.gray(" - Created modular tool structure"));
465
+ console.log(chalk.gray(" - Created setup script for KV namespace creation"));
466
+ // 13. Run npm install first
467
+ console.log(chalk.blue("\nšŸ“¦ Installing dependencies..."));
468
+ await runCommand(packageManager, ["install"], targetDir);
469
+ // 14. Run setup script to create KV namespaces
470
+ console.log(chalk.blue("\nšŸ”§ Running setup script to create KV namespaces..."));
471
+ try {
472
+ await runCommand("node", ["scripts/setup.js"], targetDir);
1176
473
  }
1177
474
  catch (error) {
1178
- console.error(chalk.red("Failed to set up Cloudflare Worker MCP server:"), error);
1179
- throw error;
475
+ console.log(chalk.yellow("\nāš ļø Setup script encountered issues. You may need to:"));
476
+ console.log(chalk.yellow(" 1. Make sure you're logged in to Cloudflare: wrangler login"));
477
+ console.log(chalk.yellow(" 2. Run 'npm run setup' manually after logging in"));
1180
478
  }
1181
479
  }
480
+ function toPascalCase(str) {
481
+ return str
482
+ .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
483
+ return word.toUpperCase();
484
+ })
485
+ .replace(/\s+/g, "")
486
+ .replace(/-/g, "");
487
+ }
488
+ function generateAdminApiKey() {
489
+ // Generate a secure random API key
490
+ return Buffer.from(crypto.randomBytes(32)).toString('base64');
491
+ }
492
+ function runCommand(command, args, cwd) {
493
+ return new Promise((resolve, reject) => {
494
+ const child = spawn(command, args, {
495
+ cwd,
496
+ stdio: 'inherit',
497
+ shell: process.platform === 'win32'
498
+ });
499
+ child.on('error', reject);
500
+ child.on('exit', (code) => {
501
+ if (code === 0) {
502
+ resolve();
503
+ }
504
+ else {
505
+ reject(new Error(`Command failed with exit code ${code}`));
506
+ }
507
+ });
508
+ });
509
+ }
1182
510
  //# sourceMappingURL=fetch-cloudflare-mcpi-template.js.map