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