@kya-os/create-mcpi-app 1.7.42-canary.34 → 1.7.42-canary.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/helpers/fetch-cloudflare-mcpi-template.d.ts +7 -6
- package/dist/helpers/fetch-cloudflare-mcpi-template.d.ts.map +1 -1
- package/dist/helpers/fetch-cloudflare-mcpi-template.js +143 -1193
- package/dist/helpers/fetch-cloudflare-mcpi-template.js.map +1 -1
- package/package.json +25 -67
|
@@ -1,1222 +1,172 @@
|
|
|
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";
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Fetches the Cloudflare MCP-I template
|
|
6
|
+
* Since this is inside the monorepo, we can copy from local packages
|
|
7
|
+
* But for the published package, we need to construct package.json with correct versions
|
|
8
8
|
*/
|
|
9
|
-
export async function fetchCloudflareMcpiTemplate(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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.45",
|
|
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}
|
|
105
|
-
*
|
|
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
|
|
112
|
-
*/
|
|
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);
|
|
9
|
+
export async function fetchCloudflareMcpiTemplate(targetDir, options) {
|
|
10
|
+
// Handle legacy string argument (projectName) for backward compatibility if needed
|
|
11
|
+
const projectName = typeof options === 'string' ? options : options.projectName;
|
|
12
|
+
// In a real implementation, this might download from a repo
|
|
13
|
+
// For now, we'll construct the template files
|
|
14
|
+
// 1. Create directory structure
|
|
15
|
+
await fs.ensureDir(path.join(targetDir, "src"));
|
|
16
|
+
// 2. Create package.json
|
|
17
|
+
const packageJson = {
|
|
18
|
+
name: projectName,
|
|
19
|
+
version: "0.1.0",
|
|
20
|
+
private: true,
|
|
21
|
+
scripts: {
|
|
22
|
+
deploy: "wrangler deploy",
|
|
23
|
+
dev: "wrangler dev",
|
|
24
|
+
start: "wrangler dev",
|
|
25
|
+
test: "vitest",
|
|
26
|
+
"cf-typegen": "wrangler types"
|
|
27
|
+
},
|
|
28
|
+
dependencies: {
|
|
29
|
+
"@kya-os/mcp-i-cloudflare": "^1.5.8-canary.47",
|
|
30
|
+
"@modelcontextprotocol/sdk": "^0.6.0",
|
|
31
|
+
"hono": "^4.6.3"
|
|
32
|
+
},
|
|
33
|
+
devDependencies: {
|
|
34
|
+
"@cloudflare/vitest-pool-workers": "^0.5.2",
|
|
35
|
+
"@cloudflare/workers-types": "^4.20240925.0",
|
|
36
|
+
"@types/node": "^20.8.3",
|
|
37
|
+
"typescript": "^5.5.2",
|
|
38
|
+
"vitest": "2.0.5",
|
|
39
|
+
"wrangler": "^3.78.12"
|
|
383
40
|
}
|
|
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
41
|
};
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
}
|
|
589
|
-
|
|
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
|
-
});
|
|
598
|
-
`;
|
|
599
|
-
fs.writeFileSync(path.join(srcDir, "index.ts"), indexContent);
|
|
600
|
-
const wranglerContent = `#:schema node_modules/wrangler/config-schema.json
|
|
601
|
-
name = "${wranglerName}"
|
|
42
|
+
await fs.writeJson(path.join(targetDir, "package.json"), packageJson, {
|
|
43
|
+
spaces: 2,
|
|
44
|
+
});
|
|
45
|
+
// 3. Create wrangler.toml
|
|
46
|
+
const wranglerToml = `#:schema node_modules/wrangler/config-schema.json
|
|
47
|
+
name = "${projectName}"
|
|
602
48
|
main = "src/index.ts"
|
|
603
|
-
compatibility_date = "
|
|
49
|
+
compatibility_date = "2024-09-25"
|
|
604
50
|
compatibility_flags = ["nodejs_compat"]
|
|
605
51
|
|
|
52
|
+
# Durable Object binding for MCP Agent state
|
|
606
53
|
[[durable_objects.bindings]]
|
|
607
54
|
name = "MCP_OBJECT"
|
|
608
|
-
class_name = "${
|
|
55
|
+
class_name = "${toPascalCase(projectName)}MCP"
|
|
609
56
|
|
|
610
57
|
[[migrations]]
|
|
611
58
|
tag = "v1"
|
|
612
|
-
|
|
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
|
|
59
|
+
new_classes = ["${toPascalCase(projectName)}MCP"]
|
|
643
60
|
|
|
644
|
-
# KV Namespace for
|
|
645
|
-
#
|
|
646
|
-
#
|
|
647
|
-
#
|
|
648
|
-
#
|
|
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
|
|
655
|
-
|
|
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
|
|
61
|
+
# KV Namespace for nonce cache (replay protection)
|
|
62
|
+
# Run 'npm run kv:create-nonce' to generate ID
|
|
63
|
+
# [[kv_namespaces]]
|
|
64
|
+
# binding = "NONCE_CACHE"
|
|
65
|
+
# id = "TODO_REPLACE_WITH_ID"
|
|
696
66
|
|
|
697
67
|
[vars]
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
# AgentShield Integration (https://kya.vouched.id)
|
|
702
|
-
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
|
-
MCPI_ENV = "development"
|
|
710
|
-
|
|
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"
|
|
68
|
+
# Environment variables
|
|
69
|
+
# AGENTSHIELD_API_URL = "https://kya.vouched.id"
|
|
70
|
+
# MCPI_ENV = "development"
|
|
724
71
|
`;
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
const identityVars = `
|
|
740
|
-
# Agent DID (public identifier - safe to commit)
|
|
741
|
-
MCP_IDENTITY_AGENT_DID = "${identity.did}"
|
|
742
|
-
|
|
743
|
-
# Public identity key (safe to commit - not sensitive)
|
|
744
|
-
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
|
-
`;
|
|
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"
|
|
72
|
+
await fs.writeFile(path.join(targetDir, "wrangler.toml"), wranglerToml);
|
|
73
|
+
// 4. Create src/index.ts (entry point)
|
|
74
|
+
const indexTs = `import { createMCPICloudflareAdapter } from "@kya-os/mcp-i-cloudflare";
|
|
75
|
+
import { ${toPascalCase(projectName)}Agent } from "./agent";
|
|
76
|
+
|
|
77
|
+
// Create the adapter
|
|
78
|
+
const adapter = createMCPICloudflareAdapter({
|
|
79
|
+
agent: ${toPascalCase(projectName)}Agent,
|
|
80
|
+
env: {} as any, // Will be injected by Cloudflare
|
|
81
|
+
serverInfo: {
|
|
82
|
+
name: "${projectName}",
|
|
83
|
+
version: "1.0.0",
|
|
84
|
+
},
|
|
85
|
+
});
|
|
822
86
|
|
|
823
|
-
|
|
824
|
-
|
|
87
|
+
// Export the fetch handler and Durable Object
|
|
88
|
+
export default adapter;
|
|
89
|
+
export { ${toPascalCase(projectName)}MCP } from "./agent";
|
|
825
90
|
`;
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
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
|
|
881
|
-
`;
|
|
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)
|
|
916
|
-
|
|
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\`:
|
|
91
|
+
await fs.writeFile(path.join(targetDir, "src/index.ts"), indexTs);
|
|
92
|
+
// 5. Create src/agent.ts (Agent implementation)
|
|
93
|
+
const agentTs = `import { MCPICloudflareAgent } from "@kya-os/mcp-i-cloudflare";
|
|
94
|
+
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
95
|
+
|
|
96
|
+
export class ${toPascalCase(projectName)}Agent extends MCPICloudflareAgent {
|
|
97
|
+
/**
|
|
98
|
+
* Define tools available to this agent
|
|
99
|
+
*/
|
|
100
|
+
protected getTools(): Tool[] {
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
name: "greet",
|
|
104
|
+
description: "Greet the user",
|
|
105
|
+
inputSchema: {
|
|
106
|
+
type: "object",
|
|
107
|
+
properties: {
|
|
108
|
+
name: { type: "string" },
|
|
109
|
+
},
|
|
110
|
+
required: ["name"],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
}
|
|
990
115
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Handle tool execution
|
|
118
|
+
*/
|
|
119
|
+
protected async executeTool(name: string, args: any): Promise<any> {
|
|
120
|
+
if (name === "greet") {
|
|
121
|
+
return {
|
|
122
|
+
content: [
|
|
123
|
+
{
|
|
124
|
+
type: "text",
|
|
125
|
+
text: \`Hello, \${args.name}!\`,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
};
|
|
997
129
|
}
|
|
130
|
+
throw new Error(\`Unknown tool: \${name}\`);
|
|
998
131
|
}
|
|
999
132
|
}
|
|
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
|
-
|
|
1010
|
-
\`\`\`typescript
|
|
1011
|
-
import { z } from "zod";
|
|
1012
|
-
|
|
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
|
-
})
|
|
1022
|
-
};
|
|
1023
|
-
\`\`\`
|
|
1024
133
|
|
|
1025
|
-
|
|
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:
|
|
1053
|
-
|
|
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...'
|
|
1061
|
-
}
|
|
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
|
-
|
|
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.
|
|
1172
|
-
|
|
1173
|
-
### Disable AgentShield (Optional)
|
|
1174
|
-
|
|
1175
|
-
If you don't want to use AgentShield, edit \`src/mcpi-runtime-config.ts\`:
|
|
1176
|
-
|
|
1177
|
-
\`\`\`typescript
|
|
1178
|
-
proofing: {
|
|
1179
|
-
enabled: false, // Disable proof submission
|
|
1180
|
-
// ...
|
|
1181
|
-
}
|
|
1182
|
-
\`\`\`
|
|
1183
|
-
|
|
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)
|
|
134
|
+
// Export Durable Object class
|
|
135
|
+
export class ${toPascalCase(projectName)}MCP extends ${toPascalCase(projectName)}Agent {}
|
|
1191
136
|
`;
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
137
|
+
await fs.writeFile(path.join(targetDir, "src/agent.ts"), agentTs);
|
|
138
|
+
// 6. Create tsconfig.json
|
|
139
|
+
const tsConfig = {
|
|
140
|
+
compilerOptions: {
|
|
141
|
+
target: "esnext",
|
|
142
|
+
module: "esnext",
|
|
143
|
+
moduleResolution: "bundler",
|
|
144
|
+
types: ["@cloudflare/workers-types", "vitest/globals"],
|
|
145
|
+
strict: true,
|
|
146
|
+
skipLibCheck: true,
|
|
147
|
+
noEmit: true,
|
|
148
|
+
},
|
|
149
|
+
include: ["src/**/*"],
|
|
150
|
+
exclude: ["node_modules"],
|
|
151
|
+
};
|
|
152
|
+
await fs.writeJson(path.join(targetDir, "tsconfig.json"), tsConfig, {
|
|
153
|
+
spaces: 2,
|
|
154
|
+
});
|
|
155
|
+
// 7. Create .gitignore
|
|
156
|
+
const gitignore = `node_modules
|
|
157
|
+
dist
|
|
158
|
+
.wrangler
|
|
159
|
+
.dev.vars
|
|
160
|
+
`;
|
|
161
|
+
await fs.writeFile(path.join(targetDir, ".gitignore"), gitignore);
|
|
162
|
+
console.log(chalk.green("✔ Created Cloudflare MCP-I template files"));
|
|
163
|
+
}
|
|
164
|
+
function toPascalCase(str) {
|
|
165
|
+
return str
|
|
166
|
+
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
|
|
167
|
+
return word.toUpperCase();
|
|
168
|
+
})
|
|
169
|
+
.replace(/\s+/g, "")
|
|
170
|
+
.replace(/-/g, "");
|
|
1221
171
|
}
|
|
1222
172
|
//# sourceMappingURL=fetch-cloudflare-mcpi-template.js.map
|