@kya-os/create-mcpi-app 1.7.42-canary.35 → 1.7.42-canary.37

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