@kya-os/create-mcpi-app 1.8.15-canary.0 → 1.8.16

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