@kya-os/create-mcpi-app 0.6.4-canary.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.
Files changed (110) hide show
  1. package/.eslintrc.cjs +10 -0
  2. package/.turbo/turbo-build.log +25 -0
  3. package/README.md +290 -0
  4. package/dist/bundles/blank.js +98817 -0
  5. package/dist/bundles/ecommerce.js +98891 -0
  6. package/dist/bundles/hardware-world.js +99289 -0
  7. package/dist/bundles/manifest.json +23 -0
  8. package/dist/cli-runner.d.ts +2 -0
  9. package/dist/cli-runner.d.ts.map +1 -0
  10. package/dist/cli-runner.js +407 -0
  11. package/dist/cli-runner.js.map +1 -0
  12. package/dist/effects/index.d.ts +14 -0
  13. package/dist/effects/index.d.ts.map +1 -0
  14. package/dist/effects/index.js +51 -0
  15. package/dist/effects/index.js.map +1 -0
  16. package/dist/helpers/apply-identity-preset.d.ts +14 -0
  17. package/dist/helpers/apply-identity-preset.d.ts.map +1 -0
  18. package/dist/helpers/apply-identity-preset.js +169 -0
  19. package/dist/helpers/apply-identity-preset.js.map +1 -0
  20. package/dist/helpers/config-builder.d.ts +53 -0
  21. package/dist/helpers/config-builder.d.ts.map +1 -0
  22. package/dist/helpers/config-builder.js +46 -0
  23. package/dist/helpers/config-builder.js.map +1 -0
  24. package/dist/helpers/copy-template.d.ts +2 -0
  25. package/dist/helpers/copy-template.d.ts.map +1 -0
  26. package/dist/helpers/copy-template.js +11 -0
  27. package/dist/helpers/copy-template.js.map +1 -0
  28. package/dist/helpers/create.d.ts +31 -0
  29. package/dist/helpers/create.d.ts.map +1 -0
  30. package/dist/helpers/create.js +136 -0
  31. package/dist/helpers/create.js.map +1 -0
  32. package/dist/helpers/fetch-cloudflare-mcpi-template.d.ts +25 -0
  33. package/dist/helpers/fetch-cloudflare-mcpi-template.d.ts.map +1 -0
  34. package/dist/helpers/fetch-cloudflare-mcpi-template.js +558 -0
  35. package/dist/helpers/fetch-cloudflare-mcpi-template.js.map +1 -0
  36. package/dist/helpers/fetch-cloudflare-template.d.ts +11 -0
  37. package/dist/helpers/fetch-cloudflare-template.d.ts.map +1 -0
  38. package/dist/helpers/fetch-cloudflare-template.js +338 -0
  39. package/dist/helpers/fetch-cloudflare-template.js.map +1 -0
  40. package/dist/helpers/fetch-mcpi-template.d.ts +13 -0
  41. package/dist/helpers/fetch-mcpi-template.d.ts.map +1 -0
  42. package/dist/helpers/fetch-mcpi-template.js +237 -0
  43. package/dist/helpers/fetch-mcpi-template.js.map +1 -0
  44. package/dist/helpers/fetch-xmcp-template.d.ts +13 -0
  45. package/dist/helpers/fetch-xmcp-template.d.ts.map +1 -0
  46. package/dist/helpers/fetch-xmcp-template.js +127 -0
  47. package/dist/helpers/fetch-xmcp-template.js.map +1 -0
  48. package/dist/helpers/generate-cloudflare-files.d.ts +89 -0
  49. package/dist/helpers/generate-cloudflare-files.d.ts.map +1 -0
  50. package/dist/helpers/generate-cloudflare-files.js +1541 -0
  51. package/dist/helpers/generate-cloudflare-files.js.map +1 -0
  52. package/dist/helpers/generate-config.d.ts +2 -0
  53. package/dist/helpers/generate-config.d.ts.map +1 -0
  54. package/dist/helpers/generate-config.js +105 -0
  55. package/dist/helpers/generate-config.js.map +1 -0
  56. package/dist/helpers/generate-identity.d.ts +38 -0
  57. package/dist/helpers/generate-identity.d.ts.map +1 -0
  58. package/dist/helpers/generate-identity.js +123 -0
  59. package/dist/helpers/generate-identity.js.map +1 -0
  60. package/dist/helpers/get-package-versions.d.ts +37 -0
  61. package/dist/helpers/get-package-versions.d.ts.map +1 -0
  62. package/dist/helpers/get-package-versions.js +92 -0
  63. package/dist/helpers/get-package-versions.js.map +1 -0
  64. package/dist/helpers/identity-manager.d.ts +23 -0
  65. package/dist/helpers/identity-manager.d.ts.map +1 -0
  66. package/dist/helpers/identity-manager.js +144 -0
  67. package/dist/helpers/identity-manager.js.map +1 -0
  68. package/dist/helpers/index.d.ts +14 -0
  69. package/dist/helpers/index.d.ts.map +1 -0
  70. package/dist/helpers/index.js +18 -0
  71. package/dist/helpers/index.js.map +1 -0
  72. package/dist/helpers/install.d.ts +3 -0
  73. package/dist/helpers/install.d.ts.map +1 -0
  74. package/dist/helpers/install.js +57 -0
  75. package/dist/helpers/install.js.map +1 -0
  76. package/dist/helpers/kta-registration.d.ts +58 -0
  77. package/dist/helpers/kta-registration.d.ts.map +1 -0
  78. package/dist/helpers/kta-registration.js +77 -0
  79. package/dist/helpers/kta-registration.js.map +1 -0
  80. package/dist/helpers/rename.d.ts +2 -0
  81. package/dist/helpers/rename.d.ts.map +1 -0
  82. package/dist/helpers/rename.js +15 -0
  83. package/dist/helpers/rename.js.map +1 -0
  84. package/dist/helpers/validate-project-structure.d.ts +14 -0
  85. package/dist/helpers/validate-project-structure.d.ts.map +1 -0
  86. package/dist/helpers/validate-project-structure.js +102 -0
  87. package/dist/helpers/validate-project-structure.js.map +1 -0
  88. package/dist/index.d.ts +3 -0
  89. package/dist/index.d.ts.map +1 -0
  90. package/dist/index.js +18 -0
  91. package/dist/index.js.map +1 -0
  92. package/dist/utils/check-node.d.ts +2 -0
  93. package/dist/utils/check-node.d.ts.map +1 -0
  94. package/dist/utils/check-node.js +12 -0
  95. package/dist/utils/check-node.js.map +1 -0
  96. package/dist/utils/fetch-remote-config.d.ts +74 -0
  97. package/dist/utils/fetch-remote-config.d.ts.map +1 -0
  98. package/dist/utils/fetch-remote-config.js +109 -0
  99. package/dist/utils/fetch-remote-config.js.map +1 -0
  100. package/dist/utils/is-folder-empty.d.ts +2 -0
  101. package/dist/utils/is-folder-empty.d.ts.map +1 -0
  102. package/dist/utils/is-folder-empty.js +55 -0
  103. package/dist/utils/is-folder-empty.js.map +1 -0
  104. package/dist/utils/validate-project-name.d.ts +15 -0
  105. package/dist/utils/validate-project-name.d.ts.map +1 -0
  106. package/dist/utils/validate-project-name.js +106 -0
  107. package/dist/utils/validate-project-name.js.map +1 -0
  108. package/index.js +4 -0
  109. package/package.json +73 -0
  110. package/vitest.integration.config.ts +16 -0
@@ -0,0 +1,1541 @@
1
+ /**
2
+ * Programmatic Cloudflare Project File Generation
3
+ *
4
+ * This module generates Cloudflare MCP-I project files in-memory,
5
+ * suitable for committing to GitHub via API (one-click deployment).
6
+ *
7
+ * Unlike fetchCloudflareMcpiTemplate (which writes to disk), this function
8
+ * returns file contents that can be committed programmatically.
9
+ *
10
+ * @module @kya-os/create-mcpi-app/helpers/generate-cloudflare-files
11
+ */
12
+ import crypto from "crypto";
13
+ import { generateIdentity, } from "./generate-identity.js";
14
+ import { getPackageVersions } from "./get-package-versions.js";
15
+ /**
16
+ * Unified KV binding name (matches env-mapper.ts UNIFIED_KV_BINDING_NAME)
17
+ *
18
+ * A single KV namespace per project instead of 5 individual ones.
19
+ * The mcp-i-cloudflare env-mapper expands this to all 5 expected bindings
20
+ * at runtime via fallback, so worker code sees familiar names.
21
+ */
22
+ const UNIFIED_KV_BINDING = "MCPI_KV";
23
+ /**
24
+ * Generate Cloudflare MCP-I project files in-memory
25
+ *
26
+ * This function generates all files needed for a Cloudflare Workers MCP-I project
27
+ * but returns them as strings instead of writing to disk. This enables
28
+ * programmatic deployment via GitHub API.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const result = await generateCloudflareProjectFiles({
33
+ * projectName: "my-agent",
34
+ * agentShieldProjectId: "my-project-abc123",
35
+ * });
36
+ *
37
+ * // Commit files to GitHub
38
+ * for (const file of result.files) {
39
+ * await octokit.repos.createOrUpdateFileContents({
40
+ * owner, repo,
41
+ * path: file.path,
42
+ * content: Buffer.from(file.content).toString("base64"),
43
+ * });
44
+ * }
45
+ *
46
+ * // Add secrets to GitHub
47
+ * await octokit.actions.createOrUpdateRepoSecret({
48
+ * owner, repo,
49
+ * secret_name: "MCP_IDENTITY_PRIVATE_KEY",
50
+ * encrypted_value: await encrypt(result.secrets.MCP_IDENTITY_PRIVATE_KEY),
51
+ * });
52
+ * ```
53
+ */
54
+ export async function generateCloudflareProjectFiles(options) {
55
+ const { projectName, template = "blank", agentShieldProjectId, agentShieldApiUrl = "https://kya.vouched.id", agentShieldApiKey, skipIdentity = false, serverUrl, } = options;
56
+ // Standard DO class name for all templates (no more per-project PascalCase names)
57
+ const doClassName = "MCPIAgent";
58
+ // Get package versions from scaffolder's package.json (single source of truth)
59
+ // This ensures scaffolded projects use tested, consistent versions
60
+ const versions = await getPackageVersions();
61
+ // Extract exact version (without caret) for template dependencies
62
+ // Templates use exact versions to ensure users get the tested version
63
+ const mcpICloudflareVersion = versions["@kya-os/mcp-i-cloudflare"].replace(/^[\^~]/, "");
64
+ // Generate identity
65
+ let identity;
66
+ if (skipIdentity) {
67
+ identity = {
68
+ did: "did:key:zTestMockIdentity",
69
+ kid: "did:key:zTestMockIdentity#key-1",
70
+ privateKey: "mock-private-key",
71
+ publicKey: "mock-public-key",
72
+ createdAt: new Date().toISOString(),
73
+ type: "development",
74
+ };
75
+ }
76
+ else {
77
+ identity = await generateIdentity();
78
+ }
79
+ // Generate OAuth encryption secret
80
+ const oauthEncryptionSecret = crypto.randomBytes(32).toString("hex");
81
+ const files = [];
82
+ // 1. package.json
83
+ files.push({
84
+ path: "package.json",
85
+ content: JSON.stringify({
86
+ name: projectName,
87
+ version: "0.1.0",
88
+ private: true,
89
+ scripts: {
90
+ deploy: "wrangler deploy",
91
+ dev: "wrangler dev",
92
+ start: "wrangler dev",
93
+ test: "vitest",
94
+ "cf-typegen": "wrangler types",
95
+ },
96
+ dependencies: {
97
+ "@kya-os/mcp-i-cloudflare": mcpICloudflareVersion,
98
+ "@modelcontextprotocol/sdk": "1.25.2",
99
+ // agents@0.3.x removed 'ai' from direct deps, fixing Redis bundling issue
100
+ agents: "0.3.6",
101
+ hono: "4.11.7",
102
+ },
103
+ devDependencies: {
104
+ "@cloudflare/vitest-pool-workers": "^0.5.41",
105
+ "@cloudflare/workers-types": "^4.20251126.0",
106
+ "@types/node": "^20.8.3",
107
+ typescript: "^5.5.2",
108
+ vitest: "^2.0.5",
109
+ wrangler: "^4.53.0",
110
+ },
111
+ }, null, 2),
112
+ encoding: "utf-8",
113
+ });
114
+ // 2. wrangler.toml (NO private key - that goes to GitHub Secrets)
115
+ files.push({
116
+ path: "wrangler.toml",
117
+ content: generateWranglerToml({
118
+ projectName,
119
+ doClassName,
120
+ agentDid: identity.did,
121
+ publicKey: identity.publicKey,
122
+ agentShieldProjectId,
123
+ agentShieldApiUrl,
124
+ template,
125
+ serverUrl,
126
+ }),
127
+ encoding: "utf-8",
128
+ });
129
+ // 3. .dev.vars.example (template for local dev, not the actual secrets)
130
+ files.push({
131
+ path: ".dev.vars.example",
132
+ content: `# Copy this to .dev.vars for local development
133
+ # For production, secrets are managed via GitHub Secrets + Cloudflare
134
+
135
+ # Agent Identity (DO NOT COMMIT .dev.vars)
136
+ MCP_IDENTITY_PRIVATE_KEY="your-private-key-here"
137
+
138
+ # OAuth Encryption Secret (required for OAuth/delegation flows)
139
+ OAUTH_ENCRYPTION_SECRET="your-oauth-secret-here"
140
+
141
+ # AgentShield API Key
142
+ AGENTSHIELD_API_KEY="sk_your_api_key_here"
143
+
144
+ # Admin API Key (optional, falls back to AGENTSHIELD_API_KEY)
145
+ # ADMIN_API_KEY="sk_your_admin_key_here"
146
+ `,
147
+ encoding: "utf-8",
148
+ });
149
+ // 4. .gitignore
150
+ files.push({
151
+ path: ".gitignore",
152
+ content: `node_modules/
153
+ dist/
154
+ .wrangler/
155
+ .dev.vars
156
+ *.log
157
+ .DS_Store
158
+ `,
159
+ encoding: "utf-8",
160
+ });
161
+ // 5. tsconfig.json
162
+ files.push({
163
+ path: "tsconfig.json",
164
+ content: JSON.stringify({
165
+ compilerOptions: {
166
+ target: "esnext",
167
+ module: "esnext",
168
+ moduleResolution: "bundler",
169
+ types: ["@cloudflare/workers-types", "vitest/globals"],
170
+ strict: true,
171
+ skipLibCheck: true,
172
+ noEmit: true,
173
+ },
174
+ include: ["src/**/*"],
175
+ exclude: ["node_modules"],
176
+ }, null, 2),
177
+ encoding: "utf-8",
178
+ });
179
+ // 6. src/index.ts
180
+ files.push({
181
+ path: "src/index.ts",
182
+ content: generateIndexTs(doClassName),
183
+ encoding: "utf-8",
184
+ });
185
+ // 7. src/agent.ts
186
+ files.push({
187
+ path: "src/agent.ts",
188
+ content: generateAgentTs(projectName, doClassName),
189
+ encoding: "utf-8",
190
+ });
191
+ // 8. src/mcpi-runtime-config.ts
192
+ files.push({
193
+ path: "src/mcpi-runtime-config.ts",
194
+ content: generateRuntimeConfigTs(template),
195
+ encoding: "utf-8",
196
+ });
197
+ // 9. src/tools/greet.ts (always included)
198
+ files.push({
199
+ path: "src/tools/greet.ts",
200
+ content: generateGreetToolTs(projectName),
201
+ encoding: "utf-8",
202
+ });
203
+ // 10. Add template-specific tools
204
+ if (template === "ecommerce") {
205
+ files.push(...generateEcommerceTools());
206
+ }
207
+ else if (template === "hardware-world") {
208
+ files.push(...generateHardwareWorldTools());
209
+ }
210
+ // 11. GitHub Actions workflow
211
+ files.push({
212
+ path: ".github/workflows/deploy.yml",
213
+ content: generateDeployWorkflow(),
214
+ encoding: "utf-8",
215
+ });
216
+ // 12. README.md
217
+ files.push({
218
+ path: "README.md",
219
+ content: generateReadme(projectName, agentShieldProjectId),
220
+ encoding: "utf-8",
221
+ });
222
+ // Build secrets object - always include identity and OAuth secrets
223
+ const secrets = {
224
+ MCP_IDENTITY_PRIVATE_KEY: identity.privateKey,
225
+ OAUTH_ENCRYPTION_SECRET: oauthEncryptionSecret,
226
+ };
227
+ // Include AgentShield API key if provided
228
+ if (agentShieldApiKey) {
229
+ secrets.AGENTSHIELD_API_KEY = agentShieldApiKey;
230
+ }
231
+ return {
232
+ files,
233
+ identity: {
234
+ did: identity.did,
235
+ publicKey: identity.publicKey,
236
+ privateKey: identity.privateKey,
237
+ },
238
+ secrets,
239
+ };
240
+ }
241
+ // ============================================================================
242
+ // Helper Functions
243
+ // ============================================================================
244
+ function generateWranglerToml(opts) {
245
+ const { projectName, doClassName, agentDid, publicKey, agentShieldProjectId, agentShieldApiUrl, template, serverUrl, } = opts;
246
+ // Template-specific environment variables
247
+ let templateVars = "";
248
+ if (template === "hardware-world") {
249
+ templateVars = `
250
+ # Hardware World API Configuration
251
+ HARDWARE_WORLD_API_BASE_URL = "https://hardwareworld.com"
252
+ `;
253
+ }
254
+ return `#:schema node_modules/wrangler/config-schema.json
255
+ name = "${projectName}"
256
+ main = "src/index.ts"
257
+ compatibility_date = "2025-01-01"
258
+ compatibility_flags = ["nodejs_compat"]
259
+
260
+ # Durable Object binding for MCP Agent state
261
+ [[durable_objects.bindings]]
262
+ name = "MCP_OBJECT"
263
+ class_name = "${doClassName}"
264
+
265
+ [[migrations]]
266
+ tag = "v1"
267
+ new_sqlite_classes = ["${doClassName}"]
268
+
269
+ # ═══════════════════════════════════════════════════════════════════════════════
270
+ # KV NAMESPACE - Unified single namespace, auto-provisioned by Wrangler v4.45+
271
+ # ═══════════════════════════════════════════════════════════════════════════════
272
+ # One KV namespace handles all MCP-I storage (nonces, proofs, identity,
273
+ # delegations, tool protection). Key prefixes are naturally unique across
274
+ # all logical namespaces so there is zero collision risk.
275
+ # Just run: npm run deploy (KV namespace is created automatically!)
276
+
277
+ [[kv_namespaces]]
278
+ binding = "${UNIFIED_KV_BINDING}"
279
+
280
+ [vars]
281
+ # Agent DID (public identifier - safe to commit)
282
+ MCP_IDENTITY_AGENT_DID = "${agentDid}"
283
+
284
+ # Public identity key (safe to commit)
285
+ MCP_IDENTITY_PUBLIC_KEY = "${publicKey}"
286
+
287
+ # CORS origins
288
+ ALLOWED_ORIGINS = "https://claude.ai,https://app.anthropic.com"
289
+
290
+ # Durable Object routing
291
+ DO_ROUTING_STRATEGY = "singleton"
292
+ # DO_SHARD_COUNT = "10" # Only needed if using shard strategy
293
+
294
+ XMCP_I_TS_SKEW_SEC = "120"
295
+ XMCP_I_SESSION_TTL = "1800"
296
+
297
+ # AgentShield Integration
298
+ AGENTSHIELD_API_URL = "${agentShieldApiUrl}"
299
+ ${agentShieldProjectId ? `AGENTSHIELD_PROJECT_ID = "${agentShieldProjectId}"` : '# AGENTSHIELD_PROJECT_ID = "your-project-id"'}
300
+
301
+ MCPI_ENV = "development"
302
+
303
+ # MCP Server URL (OPTIONAL - auto-detected from request origin by default)
304
+ # Only set this if you're using a custom domain or auto-detection isn't working
305
+ # Use base URL WITHOUT /mcp suffix - consent pages are at /consent, not /mcp/consent
306
+ ${serverUrl ? `MCP_SERVER_URL = "${serverUrl}"` : `# MCP_SERVER_URL = "https://${projectName}.YOUR-SUBDOMAIN.workers.dev"`}
307
+ ${templateVars}
308
+ # ═══════════════════════════════════════════════════════════════════════════════
309
+ # SECRETS (managed via GitHub Secrets + wrangler secret)
310
+ # ═══════════════════════════════════════════════════════════════════════════════
311
+ # These are automatically deployed via GitHub Actions workflow.
312
+ # For local development, copy .dev.vars.example to .dev.vars
313
+ `;
314
+ }
315
+ function generateIndexTs(doClassName) {
316
+ return `import { createMCPIApp } from "@kya-os/mcp-i-cloudflare";
317
+ import { ${doClassName} } from "./agent";
318
+ import { getRuntimeConfig } from "./mcpi-runtime-config";
319
+
320
+ export default createMCPIApp({
321
+ AgentClass: ${doClassName},
322
+ getRuntimeConfig,
323
+ });
324
+
325
+ // Export Durable Object class for Cloudflare Workers binding
326
+ export { ${doClassName} };
327
+ `;
328
+ }
329
+ function generateAgentTs(projectName, doClassName) {
330
+ return `import { MCPICloudflareAgent, type CloudflareEnv } from "@kya-os/mcp-i-cloudflare";
331
+ import { getRuntimeConfig, getTools } from "./mcpi-runtime-config";
332
+ import type { CloudflareRuntimeConfig } from "@kya-os/mcp-i-cloudflare";
333
+
334
+ /**
335
+ * MCP-I Agent Implementation
336
+ */
337
+ export class ${doClassName} extends MCPICloudflareAgent {
338
+ protected getAgentName(): string {
339
+ return (this.env as CloudflareEnv).MCP_SERVER_NAME || "${projectName}";
340
+ }
341
+
342
+ protected getAgentVersion(): string {
343
+ return "1.0.0";
344
+ }
345
+
346
+ protected getRuntimeConfigInternal(env: CloudflareEnv): CloudflareRuntimeConfig {
347
+ return getRuntimeConfig(env);
348
+ }
349
+
350
+ protected async registerTools(): Promise<void> {
351
+ const tools = getTools();
352
+ for (const toolDef of tools) {
353
+ this.registerToolWithProof(
354
+ toolDef.name,
355
+ toolDef.description,
356
+ toolDef.inputSchema,
357
+ toolDef.handler
358
+ );
359
+ }
360
+ }
361
+ }
362
+ `;
363
+ }
364
+ function generateRuntimeConfigTs(template) {
365
+ // Hardware World has different imports and tool protection
366
+ if (template === "hardware-world") {
367
+ return generateHardwareWorldRuntimeConfig();
368
+ }
369
+ const imports = [`import { greetTool } from "./tools/greet";`];
370
+ const tools = ["greetTool"];
371
+ if (template === "ecommerce") {
372
+ imports.push(`import { addToCartTool } from "./tools/add-to-cart";`);
373
+ imports.push(`import { getCartTool } from "./tools/get-cart";`);
374
+ imports.push(`import { checkoutTool } from "./tools/checkout";`);
375
+ tools.push("addToCartTool", "getCartTool", "checkoutTool");
376
+ }
377
+ return `import { defineConfig, type CloudflareRuntimeConfig } from "@kya-os/mcp-i-cloudflare";
378
+ import type { CloudflareEnv } from "@kya-os/mcp-i-cloudflare";
379
+ ${imports.join("\n")}
380
+
381
+ export function getRuntimeConfig(env: CloudflareEnv): CloudflareRuntimeConfig {
382
+ const environment = (env.MCPI_ENV || env.ENVIRONMENT || "development") as "development" | "production";
383
+
384
+ const proofingConfig = env.AGENTSHIELD_API_KEY
385
+ ? {
386
+ enabled: true,
387
+ batchQueue: {
388
+ destinations: [
389
+ {
390
+ type: "agentshield" as const,
391
+ apiKey: env.AGENTSHIELD_API_KEY,
392
+ apiUrl: env.AGENTSHIELD_API_URL || "https://kya.vouched.id",
393
+ },
394
+ ],
395
+ },
396
+ }
397
+ : undefined;
398
+
399
+ return defineConfig({
400
+ environment,
401
+ proofing: proofingConfig,
402
+ vars: {
403
+ ENVIRONMENT: environment,
404
+ AGENTSHIELD_API_KEY: env.AGENTSHIELD_API_KEY,
405
+ AGENTSHIELD_API_URL: env.AGENTSHIELD_API_URL,
406
+ AGENTSHIELD_PROJECT_ID: env.AGENTSHIELD_PROJECT_ID,
407
+ },
408
+ });
409
+ }
410
+
411
+ export function getTools() {
412
+ return [
413
+ ${tools.join(",\n ")},
414
+ ];
415
+ }
416
+ `;
417
+ }
418
+ function generateHardwareWorldRuntimeConfig() {
419
+ return `/**
420
+ * MCP-I Runtime Configuration
421
+ *
422
+ * Defines tool protection, proofing, and other runtime settings.
423
+ * Uses inline config with correct scopes (no hyphens) to avoid
424
+ * AgentShield's auto-generated scope validation issues.
425
+ */
426
+
427
+ import {
428
+ defineConfig,
429
+ type CloudflareRuntimeConfig,
430
+ type CloudflareEnv,
431
+ } from "@kya-os/mcp-i-cloudflare";
432
+
433
+ // Import tools
434
+ import { greetTool } from "./tools/greet";
435
+ import { getProductsTool } from "./tools/get-products";
436
+ import { getBrandsTool } from "./tools/get-brands";
437
+ import { getBrandTool } from "./tools/get-brand";
438
+ import { addToCartTool } from "./tools/add-to-cart";
439
+ import { getCartTool } from "./tools/get-cart";
440
+ import { getCustomerTool } from "./tools/get-customer";
441
+
442
+ /**
443
+ * Tool Protection Configuration
444
+ *
445
+ * Using inline config because:
446
+ * 1. AgentShield auto-generates scopes like "ADD-TO-CART:EXECUTE" which fail validation
447
+ * 2. The scope regex /^[a-zA-Z0-9_]+:[a-zA-Z0-9_]+$/ doesn't allow hyphens
448
+ * 3. Inline config uses correct scopes like "cart:write"
449
+ */
450
+ const toolProtections = {
451
+ // Public tools - no delegation required
452
+ greet: {
453
+ requiresDelegation: false,
454
+ requiredScopes: [] as string[],
455
+ riskLevel: "low" as const,
456
+ },
457
+ "get-products": {
458
+ requiresDelegation: false,
459
+ requiredScopes: [] as string[],
460
+ riskLevel: "low" as const,
461
+ },
462
+ "get-brands": {
463
+ requiresDelegation: false,
464
+ requiredScopes: [] as string[],
465
+ riskLevel: "low" as const,
466
+ },
467
+ "get-brand": {
468
+ requiresDelegation: false,
469
+ requiredScopes: [] as string[],
470
+ riskLevel: "low" as const,
471
+ },
472
+
473
+ // Protected tools - require user delegation
474
+ "get-customer": {
475
+ requiresDelegation: true,
476
+ requiredScopes: ["customer:read"],
477
+ riskLevel: "low" as const,
478
+ },
479
+ "get-cart": {
480
+ requiresDelegation: true,
481
+ requiredScopes: ["cart:read"],
482
+ riskLevel: "low" as const,
483
+ },
484
+ "add-to-cart": {
485
+ requiresDelegation: true,
486
+ requiredScopes: ["cart:write"],
487
+ riskLevel: "medium" as const,
488
+ },
489
+ };
490
+
491
+ /**
492
+ * Get runtime configuration for MCP-I
493
+ */
494
+ export function getRuntimeConfig(env: CloudflareEnv): CloudflareRuntimeConfig {
495
+ const environment = (env.MCPI_ENV || "development") as
496
+ | "development"
497
+ | "production";
498
+
499
+ // Proofing config - sends execution proofs to AgentShield
500
+ const proofingConfig = env.AGENTSHIELD_API_KEY
501
+ ? {
502
+ enabled: true,
503
+ batchQueue: {
504
+ destinations: [
505
+ {
506
+ type: "agentshield" as const,
507
+ apiKey: env.AGENTSHIELD_API_KEY,
508
+ apiUrl: env.AGENTSHIELD_API_URL || "https://kya.vouched.id",
509
+ },
510
+ ],
511
+ },
512
+ }
513
+ : undefined;
514
+
515
+ // Tool protection - using inline config with correct scopes
516
+ const toolProtectionConfig = {
517
+ source: "inline" as const,
518
+ toolProtections,
519
+ };
520
+
521
+ return defineConfig({
522
+ environment,
523
+ proofing: proofingConfig,
524
+ toolProtection: toolProtectionConfig,
525
+ vars: {
526
+ ENVIRONMENT: environment,
527
+ AGENTSHIELD_API_KEY: env.AGENTSHIELD_API_KEY,
528
+ AGENTSHIELD_API_URL: env.AGENTSHIELD_API_URL,
529
+ AGENTSHIELD_PROJECT_ID: env.AGENTSHIELD_PROJECT_ID,
530
+ },
531
+ });
532
+ }
533
+
534
+ /**
535
+ * Get all tools for this agent
536
+ *
537
+ * Note: No login tool - users authenticate via the MCP-I consent flow,
538
+ * not by passing credentials through the agent.
539
+ */
540
+ export function getTools() {
541
+ return [
542
+ // Public tools
543
+ greetTool,
544
+ getProductsTool,
545
+ getBrandsTool,
546
+ getBrandTool,
547
+ // Protected tools (require delegation)
548
+ getCustomerTool,
549
+ getCartTool,
550
+ addToCartTool,
551
+ ];
552
+ }
553
+ `;
554
+ }
555
+ function generateGreetToolTs(projectName) {
556
+ return `import type { ToolDefinition } from "@kya-os/mcp-i-cloudflare";
557
+
558
+ export const greetTool: ToolDefinition = {
559
+ name: "greet",
560
+ description: "Greet the user",
561
+ inputSchema: {
562
+ type: "object",
563
+ properties: {
564
+ name: { type: "string", description: "Name of the person to greet" },
565
+ },
566
+ required: ["name"],
567
+ },
568
+ handler: async (args: { name: string }) => {
569
+ return {
570
+ content: [
571
+ {
572
+ type: "text",
573
+ text: \`Hello, \${args.name}! Welcome to ${projectName}.\`,
574
+ },
575
+ ],
576
+ };
577
+ },
578
+ };
579
+ `;
580
+ }
581
+ function generateEcommerceTools() {
582
+ return [
583
+ {
584
+ path: "src/tools/add-to-cart.ts",
585
+ content: `import type { ToolDefinition } from "@kya-os/mcp-i-cloudflare";
586
+
587
+ export const addToCartTool: ToolDefinition = {
588
+ name: "add_to_cart",
589
+ description: "Add an item to the shopping cart",
590
+ inputSchema: {
591
+ type: "object",
592
+ properties: {
593
+ productId: { type: "string", description: "Product ID to add" },
594
+ quantity: { type: "number", description: "Quantity to add", default: 1 },
595
+ },
596
+ required: ["productId"],
597
+ },
598
+ handler: async (args: { productId: string; quantity?: number }) => {
599
+ const quantity = args.quantity || 1;
600
+ return {
601
+ content: [
602
+ {
603
+ type: "text",
604
+ text: \`Added \${quantity}x product \${args.productId} to cart.\`,
605
+ },
606
+ ],
607
+ };
608
+ },
609
+ };
610
+ `,
611
+ encoding: "utf-8",
612
+ },
613
+ {
614
+ path: "src/tools/get-cart.ts",
615
+ content: `import type { ToolDefinition } from "@kya-os/mcp-i-cloudflare";
616
+
617
+ export const getCartTool: ToolDefinition = {
618
+ name: "get_cart",
619
+ description: "Get the current shopping cart contents",
620
+ inputSchema: {
621
+ type: "object",
622
+ properties: {},
623
+ },
624
+ handler: async () => {
625
+ return {
626
+ content: [
627
+ {
628
+ type: "text",
629
+ text: "Your cart is currently empty. Add some items!",
630
+ },
631
+ ],
632
+ };
633
+ },
634
+ };
635
+ `,
636
+ encoding: "utf-8",
637
+ },
638
+ {
639
+ path: "src/tools/checkout.ts",
640
+ content: `import type { ToolDefinition } from "@kya-os/mcp-i-cloudflare";
641
+
642
+ export const checkoutTool: ToolDefinition = {
643
+ name: "checkout",
644
+ description: "Proceed to checkout with the current cart",
645
+ inputSchema: {
646
+ type: "object",
647
+ properties: {
648
+ paymentMethod: {
649
+ type: "string",
650
+ description: "Payment method (credit_card, paypal, etc.)",
651
+ },
652
+ },
653
+ required: ["paymentMethod"],
654
+ },
655
+ handler: async (args: { paymentMethod: string }) => {
656
+ return {
657
+ content: [
658
+ {
659
+ type: "text",
660
+ text: \`Checkout initiated with \${args.paymentMethod}. Redirecting to payment...\`,
661
+ },
662
+ ],
663
+ };
664
+ },
665
+ };
666
+ `,
667
+ encoding: "utf-8",
668
+ },
669
+ ];
670
+ }
671
+ function generateHardwareWorldTools() {
672
+ return [
673
+ // API Client - handles authentication and API calls
674
+ {
675
+ path: "src/lib/api-client.ts",
676
+ content: `/**
677
+ * Hardware World API Client
678
+ *
679
+ * Utility functions for calling the Hardware World API.
680
+ * Handles authentication via ToolContext (idpHeaders from delegation flow).
681
+ */
682
+
683
+ import type { ToolExecutionContext } from "@kya-os/mcp-i-cloudflare";
684
+
685
+ // Re-export for convenience
686
+ export type ToolContext = ToolExecutionContext;
687
+
688
+ export interface ApiResponse<T = unknown> {
689
+ status: number;
690
+ data: T;
691
+ ok: boolean;
692
+ }
693
+
694
+ export interface ApiOptions {
695
+ method?: "GET" | "POST" | "PUT" | "DELETE";
696
+ body?: Record<string, unknown> | null;
697
+ baseUrl?: string;
698
+ headers?: Record<string, string>;
699
+ }
700
+
701
+ // Default base URL - can be overridden via HARDWARE_WORLD_API_BASE_URL env var
702
+ const DEFAULT_BASE_URL = "https://hardwareworld.com";
703
+
704
+ // Get base URL from environment or use default
705
+ function getBaseUrl(): string {
706
+ // Check for environment variable (available in Cloudflare Workers)
707
+ if (typeof globalThis !== "undefined" && (globalThis as any).HARDWARE_WORLD_API_BASE_URL) {
708
+ return (globalThis as any).HARDWARE_WORLD_API_BASE_URL;
709
+ }
710
+ return DEFAULT_BASE_URL;
711
+ }
712
+
713
+ /**
714
+ * Call the Hardware World API
715
+ *
716
+ * @param endpoint - API endpoint (e.g., "/products", "/cart/123")
717
+ * @param options - Request options
718
+ * @param context - Tool execution context with auth headers
719
+ */
720
+ export async function callHardwareWorldAPI<T = unknown>(
721
+ endpoint: string,
722
+ options: ApiOptions = {},
723
+ context?: ToolContext
724
+ ): Promise<ApiResponse<T>> {
725
+ const baseUrl = options.baseUrl || getBaseUrl();
726
+ const url = \`\${baseUrl}/api\${endpoint}\`;
727
+
728
+ const headers: Record<string, string> = {
729
+ "Content-Type": "application/json",
730
+ ...options.headers,
731
+ };
732
+
733
+ // Hardware World API uses ONLY Cookie authentication (not Authorization header)
734
+ // The idpToken from credential provider contains the customerCookie value
735
+ if (context?.idpToken) {
736
+ // Hardware World API expects BOTH CIX and customerCookie for compatibility
737
+ headers["Cookie"] =
738
+ \`CIX=\${context.idpToken}; customerCookie=\${context.idpToken}\`;
739
+ console.log("[API Client] Cookie auth set from idpToken");
740
+ } else if (context?.idpHeaders?.["Cookie"]) {
741
+ // Fallback: use Cookie from idpHeaders if present
742
+ headers["Cookie"] = context.idpHeaders["Cookie"];
743
+ console.log("[API Client] Cookie auth set from idpHeaders");
744
+ }
745
+
746
+ console.log("[API Client] Request:", {
747
+ url,
748
+ method: options.method || "GET",
749
+ hasAuth: !!headers["Cookie"],
750
+ });
751
+
752
+ const response = await fetch(url, {
753
+ method: options.method || "GET",
754
+ headers,
755
+ body: options.body ? JSON.stringify(options.body) : null,
756
+ });
757
+
758
+ const responseData = await response.text();
759
+ let parsedData: T;
760
+
761
+ try {
762
+ parsedData = JSON.parse(responseData) as T;
763
+ } catch {
764
+ parsedData = responseData as T;
765
+ }
766
+
767
+ console.log("[API Client] Response:", {
768
+ status: response.status,
769
+ ok: response.ok,
770
+ });
771
+
772
+ return {
773
+ status: response.status,
774
+ data: parsedData,
775
+ ok: response.ok,
776
+ };
777
+ }
778
+
779
+ /**
780
+ * Format error response for MCP tools
781
+ */
782
+ export function formatError(error: unknown): {
783
+ content: Array<{ type: "text"; text: string }>;
784
+ isError: true;
785
+ } {
786
+ const message = error instanceof Error ? error.message : String(error);
787
+ return {
788
+ content: [{ type: "text", text: \`Error: \${message}\` }],
789
+ isError: true,
790
+ };
791
+ }
792
+
793
+ /**
794
+ * Format success response for MCP tools
795
+ */
796
+ export function formatSuccess(data: unknown): {
797
+ content: Array<{ type: "text"; text: string }>;
798
+ } {
799
+ return {
800
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
801
+ };
802
+ }
803
+ `,
804
+ encoding: "utf-8",
805
+ },
806
+ // Get Products - Public tool
807
+ {
808
+ path: "src/tools/get-products.ts",
809
+ content: `/**
810
+ * Get Products Tool
811
+ *
812
+ * Retrieves products from the Hardware World catalog.
813
+ * Public tool - no authentication required.
814
+ */
815
+
816
+ import type { ToolDefinition } from "@kya-os/mcp-i-cloudflare";
817
+ import {
818
+ callHardwareWorldAPI,
819
+ formatError,
820
+ formatSuccess,
821
+ } from "../lib/api-client";
822
+
823
+ interface Product {
824
+ productId: number;
825
+ name: string;
826
+ price: number;
827
+ description?: string;
828
+ [key: string]: unknown;
829
+ }
830
+
831
+ interface ProductsResponse {
832
+ products?: Product[];
833
+ [key: string]: unknown;
834
+ }
835
+
836
+ interface GetProductsArgs {
837
+ query?: string;
838
+ take?: number;
839
+ skip?: number;
840
+ baseUrl?: string;
841
+ }
842
+
843
+ export const getProductsTool: ToolDefinition = {
844
+ name: "get-products",
845
+ description:
846
+ "Search and browse Hardware World products. Supports filtering by search query and pagination.",
847
+ inputSchema: {
848
+ type: "object",
849
+ properties: {
850
+ query: {
851
+ type: "string",
852
+ description: "Search query to filter products (optional)",
853
+ },
854
+ take: {
855
+ type: "number",
856
+ description: "Number of products to return (default: 10)",
857
+ },
858
+ skip: {
859
+ type: "number",
860
+ description: "Number of products to skip for pagination (default: 0)",
861
+ },
862
+ baseUrl: {
863
+ type: "string",
864
+ description: "Override upstream base URL (optional)",
865
+ },
866
+ },
867
+ required: [],
868
+ },
869
+ handler: async (args: GetProductsArgs) => {
870
+ try {
871
+ const { query, take = 10, skip = 0, baseUrl } = args;
872
+
873
+ const endpoint = query
874
+ ? \`/products/search?query=\${encodeURIComponent(query)}&skip=\${skip}&take=\${take}\`
875
+ : \`/products?take=\${take}&skip=\${skip}\`;
876
+
877
+ const result = await callHardwareWorldAPI<ProductsResponse>(endpoint, {
878
+ method: "GET",
879
+ baseUrl,
880
+ });
881
+
882
+ if (!result.ok) {
883
+ return {
884
+ content: [
885
+ {
886
+ type: "text" as const,
887
+ text: JSON.stringify(
888
+ { error: "Failed to get products", details: result.data },
889
+ null,
890
+ 2
891
+ ),
892
+ },
893
+ ],
894
+ isError: true,
895
+ };
896
+ }
897
+
898
+ const products = result.data?.products || result.data || [];
899
+ return formatSuccess({
900
+ success: true,
901
+ products,
902
+ count: Array.isArray(products) ? products.length : 0,
903
+ });
904
+ } catch (error) {
905
+ return formatError(error);
906
+ }
907
+ },
908
+ };
909
+ `,
910
+ encoding: "utf-8",
911
+ },
912
+ // Get Brands - Public tool
913
+ {
914
+ path: "src/tools/get-brands.ts",
915
+ content: `/**
916
+ * Get Brands Tool
917
+ *
918
+ * Retrieves all brands from Hardware World.
919
+ * Public tool - no authentication required.
920
+ */
921
+
922
+ import type { ToolDefinition } from "@kya-os/mcp-i-cloudflare";
923
+ import {
924
+ callHardwareWorldAPI,
925
+ formatError,
926
+ formatSuccess,
927
+ } from "../lib/api-client";
928
+
929
+ interface Brand {
930
+ brandId: number;
931
+ name: string;
932
+ [key: string]: unknown;
933
+ }
934
+
935
+ interface BrandsResponse {
936
+ brands?: Brand[];
937
+ [key: string]: unknown;
938
+ }
939
+
940
+ interface GetBrandsArgs {
941
+ baseUrl?: string;
942
+ }
943
+
944
+ export const getBrandsTool: ToolDefinition = {
945
+ name: "get-brands",
946
+ description: "Get all available brands from Hardware World catalog.",
947
+ inputSchema: {
948
+ type: "object",
949
+ properties: {
950
+ baseUrl: {
951
+ type: "string",
952
+ description: "Override upstream base URL (optional)",
953
+ },
954
+ },
955
+ required: [],
956
+ },
957
+ handler: async (args: GetBrandsArgs) => {
958
+ try {
959
+ const { baseUrl } = args;
960
+
961
+ const result = await callHardwareWorldAPI<BrandsResponse>("/brands", {
962
+ method: "GET",
963
+ baseUrl,
964
+ });
965
+
966
+ if (!result.ok) {
967
+ return {
968
+ content: [
969
+ {
970
+ type: "text" as const,
971
+ text: JSON.stringify(
972
+ { error: "Failed to get brands", details: result.data },
973
+ null,
974
+ 2
975
+ ),
976
+ },
977
+ ],
978
+ isError: true,
979
+ };
980
+ }
981
+
982
+ const brands = result.data?.brands || result.data || [];
983
+ return formatSuccess({
984
+ success: true,
985
+ brands,
986
+ count: Array.isArray(brands) ? brands.length : 0,
987
+ });
988
+ } catch (error) {
989
+ return formatError(error);
990
+ }
991
+ },
992
+ };
993
+ `,
994
+ encoding: "utf-8",
995
+ },
996
+ // Get Brand - Public tool
997
+ {
998
+ path: "src/tools/get-brand.ts",
999
+ content: `/**
1000
+ * Get Brand Tool
1001
+ *
1002
+ * Retrieves a specific brand by ID from Hardware World.
1003
+ * Public tool - no authentication required.
1004
+ */
1005
+
1006
+ import type { ToolDefinition } from "@kya-os/mcp-i-cloudflare";
1007
+ import {
1008
+ callHardwareWorldAPI,
1009
+ formatError,
1010
+ formatSuccess,
1011
+ } from "../lib/api-client";
1012
+
1013
+ interface Brand {
1014
+ brandId: number;
1015
+ name: string;
1016
+ description?: string;
1017
+ [key: string]: unknown;
1018
+ }
1019
+
1020
+ interface GetBrandArgs {
1021
+ brandId: number;
1022
+ baseUrl?: string;
1023
+ }
1024
+
1025
+ export const getBrandTool: ToolDefinition = {
1026
+ name: "get-brand",
1027
+ description: "Get details for a specific brand by its ID.",
1028
+ inputSchema: {
1029
+ type: "object",
1030
+ properties: {
1031
+ brandId: {
1032
+ type: "number",
1033
+ description: "The brand ID to look up",
1034
+ },
1035
+ baseUrl: {
1036
+ type: "string",
1037
+ description: "Override upstream base URL (optional)",
1038
+ },
1039
+ },
1040
+ required: ["brandId"],
1041
+ },
1042
+ handler: async (args: GetBrandArgs) => {
1043
+ try {
1044
+ const { brandId, baseUrl } = args;
1045
+
1046
+ if (brandId === undefined || brandId === null) {
1047
+ return formatError("Brand ID is required");
1048
+ }
1049
+
1050
+ const result = await callHardwareWorldAPI<Brand>(\`/brands/\${brandId}\`, {
1051
+ method: "GET",
1052
+ baseUrl,
1053
+ });
1054
+
1055
+ if (!result.ok) {
1056
+ return {
1057
+ content: [
1058
+ {
1059
+ type: "text" as const,
1060
+ text: JSON.stringify(
1061
+ { error: "Failed to get brand", details: result.data },
1062
+ null,
1063
+ 2
1064
+ ),
1065
+ },
1066
+ ],
1067
+ isError: true,
1068
+ };
1069
+ }
1070
+
1071
+ return formatSuccess({
1072
+ success: true,
1073
+ brand: result.data,
1074
+ });
1075
+ } catch (error) {
1076
+ return formatError(error);
1077
+ }
1078
+ },
1079
+ };
1080
+ `,
1081
+ encoding: "utf-8",
1082
+ },
1083
+ // Get Customer - Protected tool (requires delegation)
1084
+ {
1085
+ path: "src/tools/get-customer.ts",
1086
+ content: `/**
1087
+ * Get Customer Tool
1088
+ *
1089
+ * Retrieves the authenticated customer's information from Hardware World.
1090
+ *
1091
+ * PROTECTED OPERATION - requires delegation with 'customer:read' scope.
1092
+ * Authentication is handled via MCP-I delegation flow.
1093
+ */
1094
+
1095
+ import type { ToolDefinition } from "@kya-os/mcp-i-cloudflare";
1096
+ import {
1097
+ callHardwareWorldAPI,
1098
+ formatError,
1099
+ formatSuccess,
1100
+ type ToolContext,
1101
+ } from "../lib/api-client";
1102
+
1103
+ interface CustomerData {
1104
+ customerId: number;
1105
+ email?: string;
1106
+ firstName?: string;
1107
+ lastName?: string;
1108
+ [key: string]: unknown;
1109
+ }
1110
+
1111
+ interface GetCustomerArgs {
1112
+ customerId?: number;
1113
+ baseUrl?: string;
1114
+ }
1115
+
1116
+ export const getCustomerTool: ToolDefinition = {
1117
+ name: "get-customer",
1118
+ description:
1119
+ "Get the authenticated customer's information. Requires user authorization (handled automatically via consent flow).",
1120
+ inputSchema: {
1121
+ type: "object",
1122
+ properties: {
1123
+ customerId: {
1124
+ type: "number",
1125
+ description:
1126
+ "Customer ID (optional - automatically provided via authorization)",
1127
+ },
1128
+ baseUrl: {
1129
+ type: "string",
1130
+ description: "Override upstream base URL (optional)",
1131
+ },
1132
+ },
1133
+ required: [],
1134
+ },
1135
+ handler: async (args: GetCustomerArgs, context?: ToolContext) => {
1136
+ try {
1137
+ const { baseUrl } = args;
1138
+
1139
+ // Verify we have authentication
1140
+ if (!context?.idpToken) {
1141
+ return formatError(
1142
+ "Authentication not available. Please ensure you have authorized this tool."
1143
+ );
1144
+ }
1145
+
1146
+ // Get customerId from context or args
1147
+ const customerId = args.customerId || context?.userId;
1148
+
1149
+ if (customerId === undefined || customerId === null) {
1150
+ return formatError(
1151
+ "Customer ID not available. Please ensure you have authorized this tool."
1152
+ );
1153
+ }
1154
+
1155
+ const result = await callHardwareWorldAPI<CustomerData>(
1156
+ \`/customers/\${customerId}\`,
1157
+ {
1158
+ method: "GET",
1159
+ baseUrl,
1160
+ },
1161
+ context
1162
+ );
1163
+
1164
+ if (!result.ok) {
1165
+ return {
1166
+ content: [
1167
+ {
1168
+ type: "text" as const,
1169
+ text: JSON.stringify(
1170
+ { error: "Failed to get customer", details: result.data },
1171
+ null,
1172
+ 2
1173
+ ),
1174
+ },
1175
+ ],
1176
+ isError: true,
1177
+ };
1178
+ }
1179
+
1180
+ return formatSuccess({
1181
+ success: true,
1182
+ customer: result.data,
1183
+ });
1184
+ } catch (error) {
1185
+ return formatError(error);
1186
+ }
1187
+ },
1188
+ };
1189
+ `,
1190
+ encoding: "utf-8",
1191
+ },
1192
+ // Get Cart - Protected tool (requires delegation)
1193
+ {
1194
+ path: "src/tools/get-cart.ts",
1195
+ content: `/**
1196
+ * Get Cart Tool
1197
+ *
1198
+ * Retrieves the customer's shopping cart contents and totals.
1199
+ *
1200
+ * PROTECTED OPERATION - requires delegation with 'cart:read' scope.
1201
+ * Authentication is handled via MCP-I delegation flow.
1202
+ */
1203
+
1204
+ import type { ToolDefinition } from "@kya-os/mcp-i-cloudflare";
1205
+ import {
1206
+ callHardwareWorldAPI,
1207
+ formatError,
1208
+ formatSuccess,
1209
+ type ToolContext,
1210
+ } from "../lib/api-client";
1211
+
1212
+ interface CartData {
1213
+ items?: unknown[];
1214
+ total?: number;
1215
+ [key: string]: unknown;
1216
+ }
1217
+
1218
+ interface GetCartArgs {
1219
+ customerId?: number;
1220
+ baseUrl?: string;
1221
+ }
1222
+
1223
+ export const getCartTool: ToolDefinition = {
1224
+ name: "get-cart",
1225
+ description:
1226
+ "Get the current shopping cart contents and totals. Requires user authorization (handled automatically via consent flow).",
1227
+ inputSchema: {
1228
+ type: "object",
1229
+ properties: {
1230
+ customerId: {
1231
+ type: "number",
1232
+ description:
1233
+ "Customer ID (optional - automatically provided via authorization)",
1234
+ },
1235
+ baseUrl: {
1236
+ type: "string",
1237
+ description: "Override upstream base URL (optional)",
1238
+ },
1239
+ },
1240
+ required: [],
1241
+ },
1242
+ handler: async (args: GetCartArgs, context?: ToolContext) => {
1243
+ try {
1244
+ const { baseUrl } = args;
1245
+
1246
+ // Verify we have authentication
1247
+ if (!context?.idpToken) {
1248
+ return formatError(
1249
+ "Authentication not available. Please ensure you have authorized this tool."
1250
+ );
1251
+ }
1252
+
1253
+ const result = await callHardwareWorldAPI<CartData>(
1254
+ "/cart",
1255
+ {
1256
+ method: "GET",
1257
+ baseUrl,
1258
+ },
1259
+ context
1260
+ );
1261
+
1262
+ if (!result.ok) {
1263
+ return {
1264
+ content: [
1265
+ {
1266
+ type: "text" as const,
1267
+ text: JSON.stringify(
1268
+ { error: "Failed to get cart", details: result.data },
1269
+ null,
1270
+ 2
1271
+ ),
1272
+ },
1273
+ ],
1274
+ isError: true,
1275
+ };
1276
+ }
1277
+
1278
+ return formatSuccess({
1279
+ success: true,
1280
+ cart: result.data,
1281
+ });
1282
+ } catch (error) {
1283
+ return formatError(error);
1284
+ }
1285
+ },
1286
+ };
1287
+ `,
1288
+ encoding: "utf-8",
1289
+ },
1290
+ // Add to Cart - Protected tool (requires delegation)
1291
+ {
1292
+ path: "src/tools/add-to-cart.ts",
1293
+ content: `/**
1294
+ * Add to Cart Tool
1295
+ *
1296
+ * Adds a product to the customer's shopping cart.
1297
+ *
1298
+ * PROTECTED OPERATION - requires delegation with 'cart:write' scope.
1299
+ * Authentication is handled via MCP-I delegation flow.
1300
+ */
1301
+
1302
+ import type { ToolDefinition } from "@kya-os/mcp-i-cloudflare";
1303
+ import {
1304
+ callHardwareWorldAPI,
1305
+ formatError,
1306
+ formatSuccess,
1307
+ type ToolContext,
1308
+ } from "../lib/api-client";
1309
+
1310
+ interface CartResponse {
1311
+ message?: string;
1312
+ customerId?: number;
1313
+ [key: string]: unknown;
1314
+ }
1315
+
1316
+ interface AddToCartArgs {
1317
+ productId: number;
1318
+ quantity?: number;
1319
+ customerId?: number;
1320
+ baseUrl?: string;
1321
+ }
1322
+
1323
+ export const addToCartTool: ToolDefinition = {
1324
+ name: "add-to-cart",
1325
+ description:
1326
+ "Add a product to the shopping cart. Requires user authorization (handled automatically via consent flow).",
1327
+ inputSchema: {
1328
+ type: "object",
1329
+ properties: {
1330
+ productId: {
1331
+ type: "number",
1332
+ description: "Product ID to add to cart",
1333
+ },
1334
+ quantity: {
1335
+ type: "number",
1336
+ description: "Quantity to add (default: 1)",
1337
+ },
1338
+ customerId: {
1339
+ type: "number",
1340
+ description:
1341
+ "Customer ID (optional - automatically provided via authorization)",
1342
+ },
1343
+ baseUrl: {
1344
+ type: "string",
1345
+ description: "Override upstream base URL (optional)",
1346
+ },
1347
+ },
1348
+ required: ["productId"],
1349
+ },
1350
+ handler: async (args: AddToCartArgs, context?: ToolContext) => {
1351
+ try {
1352
+ const { productId, quantity = 1, baseUrl } = args;
1353
+
1354
+ if (productId === undefined || productId === null) {
1355
+ return formatError("Product ID is required");
1356
+ }
1357
+
1358
+ // Verify we have authentication
1359
+ if (!context?.idpToken) {
1360
+ return formatError(
1361
+ "Authentication not available. Please ensure you have authorized this tool."
1362
+ );
1363
+ }
1364
+
1365
+ const result = await callHardwareWorldAPI<CartResponse>(
1366
+ "/cart/add",
1367
+ {
1368
+ method: "POST",
1369
+ body: { productId, quantity },
1370
+ baseUrl,
1371
+ },
1372
+ context
1373
+ );
1374
+
1375
+ if (!result.ok) {
1376
+ return {
1377
+ content: [
1378
+ {
1379
+ type: "text" as const,
1380
+ text: JSON.stringify(
1381
+ { error: "Failed to add to cart", details: result.data },
1382
+ null,
1383
+ 2
1384
+ ),
1385
+ },
1386
+ ],
1387
+ isError: true,
1388
+ };
1389
+ }
1390
+
1391
+ return formatSuccess({
1392
+ success: true,
1393
+ message: "Product added to cart",
1394
+ data: result.data,
1395
+ });
1396
+ } catch (error) {
1397
+ return formatError(error);
1398
+ }
1399
+ },
1400
+ };
1401
+ `,
1402
+ encoding: "utf-8",
1403
+ },
1404
+ ];
1405
+ }
1406
+ function generateDeployWorkflow() {
1407
+ // Note: YAML values containing ${{ }} must be quoted to avoid parsing issues
1408
+ return `name: Deploy to Cloudflare
1409
+
1410
+ on:
1411
+ push:
1412
+ branches: [main]
1413
+ workflow_dispatch:
1414
+ inputs:
1415
+ reason:
1416
+ description: 'Reason for manual deployment'
1417
+ required: false
1418
+ default: 'Manual deployment'
1419
+
1420
+ jobs:
1421
+ deploy:
1422
+ runs-on: ubuntu-latest
1423
+ name: Deploy
1424
+ steps:
1425
+ - uses: actions/checkout@v4
1426
+
1427
+ - name: Log deployment reason
1428
+ if: \${{ github.event_name == 'workflow_dispatch' }}
1429
+ run: |
1430
+ echo "Deployment triggered - Reason: \${{ github.event.inputs.reason }}"
1431
+
1432
+ - name: Setup Node.js
1433
+ uses: actions/setup-node@v4
1434
+ with:
1435
+ node-version: '20'
1436
+
1437
+ - name: Install dependencies
1438
+ run: npm install
1439
+
1440
+ - name: Deploy to Cloudflare
1441
+ uses: cloudflare/wrangler-action@v3
1442
+ with:
1443
+ apiToken: \${{ secrets.CLOUDFLARE_API_TOKEN }}
1444
+ secrets: |
1445
+ MCP_IDENTITY_PRIVATE_KEY
1446
+ OAUTH_ENCRYPTION_SECRET
1447
+ AGENTSHIELD_API_KEY
1448
+ env:
1449
+ MCP_IDENTITY_PRIVATE_KEY: \${{ secrets.MCP_IDENTITY_PRIVATE_KEY }}
1450
+ OAUTH_ENCRYPTION_SECRET: \${{ secrets.OAUTH_ENCRYPTION_SECRET }}
1451
+ AGENTSHIELD_API_KEY: \${{ secrets.AGENTSHIELD_API_KEY }}
1452
+ `;
1453
+ }
1454
+ function generateReadme(projectName, projectId) {
1455
+ const dashboardUrl = projectId
1456
+ ? `https://kya.vouched.id/dashboard/bouncer/${projectId}`
1457
+ : "https://kya.vouched.id/dashboard/bouncer";
1458
+ return `# ${projectName}
1459
+
1460
+ MCP-I Agent deployed on Cloudflare Workers with AgentShield integration.
1461
+
1462
+ ## Quick Start
1463
+
1464
+ 1. **Clone this repository**
1465
+ \`\`\`bash
1466
+ git clone https://github.com/YOUR_USERNAME/${projectName}.git
1467
+ cd ${projectName}
1468
+ \`\`\`
1469
+
1470
+ 2. **Install dependencies**
1471
+ \`\`\`bash
1472
+ npm install
1473
+ \`\`\`
1474
+
1475
+ 3. **Local Development**
1476
+ \`\`\`bash
1477
+ # Copy secrets template
1478
+ cp .dev.vars.example .dev.vars
1479
+
1480
+ # Add your secrets to .dev.vars
1481
+
1482
+ # Start dev server
1483
+ npm run dev
1484
+ \`\`\`
1485
+
1486
+ 4. **Deploy**
1487
+ \`\`\`bash
1488
+ npm run deploy
1489
+ \`\`\`
1490
+
1491
+ ## AgentShield Dashboard
1492
+
1493
+ Configure your agent at: ${dashboardUrl}
1494
+
1495
+ - Add tool protections
1496
+ - Configure OAuth providers
1497
+ - View delegations and proofs
1498
+
1499
+ ## Environment Variables
1500
+
1501
+ | Variable | Description | Required |
1502
+ |----------|-------------|----------|
1503
+ | \`MCP_IDENTITY_PRIVATE_KEY\` | Agent's private key | Yes |
1504
+ | \`OAUTH_ENCRYPTION_SECRET\` | OAuth state encryption | Yes |
1505
+ | \`AGENTSHIELD_API_KEY\` | AgentShield API key | Yes |
1506
+ | \`CLOUDFLARE_API_TOKEN\` | Wrangler deployment token | For CI/CD |
1507
+
1508
+ ## Adding Tools
1509
+
1510
+ Add new tools in \`src/tools/\`:
1511
+
1512
+ \`\`\`typescript
1513
+ // src/tools/my-tool.ts
1514
+ import type { ToolDefinition } from "@kya-os/mcp-i-cloudflare";
1515
+
1516
+ export const myTool: ToolDefinition = {
1517
+ name: "my_tool",
1518
+ description: "Description of my tool",
1519
+ inputSchema: {
1520
+ type: "object",
1521
+ properties: {
1522
+ param1: { type: "string" },
1523
+ },
1524
+ required: ["param1"],
1525
+ },
1526
+ handler: async (args) => {
1527
+ return {
1528
+ content: [{ type: "text", text: \`Result: \${args.param1}\` }],
1529
+ };
1530
+ },
1531
+ };
1532
+ \`\`\`
1533
+
1534
+ Then register in \`src/mcpi-runtime-config.ts\`.
1535
+
1536
+ ## License
1537
+
1538
+ MIT
1539
+ `;
1540
+ }
1541
+ //# sourceMappingURL=generate-cloudflare-files.js.map