@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.
- package/.eslintrc.cjs +10 -0
- package/.turbo/turbo-build.log +25 -0
- package/README.md +290 -0
- package/dist/bundles/blank.js +98817 -0
- package/dist/bundles/ecommerce.js +98891 -0
- package/dist/bundles/hardware-world.js +99289 -0
- package/dist/bundles/manifest.json +23 -0
- package/dist/cli-runner.d.ts +2 -0
- package/dist/cli-runner.d.ts.map +1 -0
- package/dist/cli-runner.js +407 -0
- package/dist/cli-runner.js.map +1 -0
- package/dist/effects/index.d.ts +14 -0
- package/dist/effects/index.d.ts.map +1 -0
- package/dist/effects/index.js +51 -0
- package/dist/effects/index.js.map +1 -0
- package/dist/helpers/apply-identity-preset.d.ts +14 -0
- package/dist/helpers/apply-identity-preset.d.ts.map +1 -0
- package/dist/helpers/apply-identity-preset.js +169 -0
- package/dist/helpers/apply-identity-preset.js.map +1 -0
- package/dist/helpers/config-builder.d.ts +53 -0
- package/dist/helpers/config-builder.d.ts.map +1 -0
- package/dist/helpers/config-builder.js +46 -0
- package/dist/helpers/config-builder.js.map +1 -0
- package/dist/helpers/copy-template.d.ts +2 -0
- package/dist/helpers/copy-template.d.ts.map +1 -0
- package/dist/helpers/copy-template.js +11 -0
- package/dist/helpers/copy-template.js.map +1 -0
- package/dist/helpers/create.d.ts +31 -0
- package/dist/helpers/create.d.ts.map +1 -0
- package/dist/helpers/create.js +136 -0
- package/dist/helpers/create.js.map +1 -0
- package/dist/helpers/fetch-cloudflare-mcpi-template.d.ts +25 -0
- package/dist/helpers/fetch-cloudflare-mcpi-template.d.ts.map +1 -0
- package/dist/helpers/fetch-cloudflare-mcpi-template.js +558 -0
- package/dist/helpers/fetch-cloudflare-mcpi-template.js.map +1 -0
- package/dist/helpers/fetch-cloudflare-template.d.ts +11 -0
- package/dist/helpers/fetch-cloudflare-template.d.ts.map +1 -0
- package/dist/helpers/fetch-cloudflare-template.js +338 -0
- package/dist/helpers/fetch-cloudflare-template.js.map +1 -0
- package/dist/helpers/fetch-mcpi-template.d.ts +13 -0
- package/dist/helpers/fetch-mcpi-template.d.ts.map +1 -0
- package/dist/helpers/fetch-mcpi-template.js +237 -0
- package/dist/helpers/fetch-mcpi-template.js.map +1 -0
- package/dist/helpers/fetch-xmcp-template.d.ts +13 -0
- package/dist/helpers/fetch-xmcp-template.d.ts.map +1 -0
- package/dist/helpers/fetch-xmcp-template.js +127 -0
- package/dist/helpers/fetch-xmcp-template.js.map +1 -0
- package/dist/helpers/generate-cloudflare-files.d.ts +89 -0
- package/dist/helpers/generate-cloudflare-files.d.ts.map +1 -0
- package/dist/helpers/generate-cloudflare-files.js +1541 -0
- package/dist/helpers/generate-cloudflare-files.js.map +1 -0
- package/dist/helpers/generate-config.d.ts +2 -0
- package/dist/helpers/generate-config.d.ts.map +1 -0
- package/dist/helpers/generate-config.js +105 -0
- package/dist/helpers/generate-config.js.map +1 -0
- package/dist/helpers/generate-identity.d.ts +38 -0
- package/dist/helpers/generate-identity.d.ts.map +1 -0
- package/dist/helpers/generate-identity.js +123 -0
- package/dist/helpers/generate-identity.js.map +1 -0
- package/dist/helpers/get-package-versions.d.ts +37 -0
- package/dist/helpers/get-package-versions.d.ts.map +1 -0
- package/dist/helpers/get-package-versions.js +92 -0
- package/dist/helpers/get-package-versions.js.map +1 -0
- package/dist/helpers/identity-manager.d.ts +23 -0
- package/dist/helpers/identity-manager.d.ts.map +1 -0
- package/dist/helpers/identity-manager.js +144 -0
- package/dist/helpers/identity-manager.js.map +1 -0
- package/dist/helpers/index.d.ts +14 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +18 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/helpers/install.d.ts +3 -0
- package/dist/helpers/install.d.ts.map +1 -0
- package/dist/helpers/install.js +57 -0
- package/dist/helpers/install.js.map +1 -0
- package/dist/helpers/kta-registration.d.ts +58 -0
- package/dist/helpers/kta-registration.d.ts.map +1 -0
- package/dist/helpers/kta-registration.js +77 -0
- package/dist/helpers/kta-registration.js.map +1 -0
- package/dist/helpers/rename.d.ts +2 -0
- package/dist/helpers/rename.d.ts.map +1 -0
- package/dist/helpers/rename.js +15 -0
- package/dist/helpers/rename.js.map +1 -0
- package/dist/helpers/validate-project-structure.d.ts +14 -0
- package/dist/helpers/validate-project-structure.d.ts.map +1 -0
- package/dist/helpers/validate-project-structure.js +102 -0
- package/dist/helpers/validate-project-structure.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/check-node.d.ts +2 -0
- package/dist/utils/check-node.d.ts.map +1 -0
- package/dist/utils/check-node.js +12 -0
- package/dist/utils/check-node.js.map +1 -0
- package/dist/utils/fetch-remote-config.d.ts +74 -0
- package/dist/utils/fetch-remote-config.d.ts.map +1 -0
- package/dist/utils/fetch-remote-config.js +109 -0
- package/dist/utils/fetch-remote-config.js.map +1 -0
- package/dist/utils/is-folder-empty.d.ts +2 -0
- package/dist/utils/is-folder-empty.d.ts.map +1 -0
- package/dist/utils/is-folder-empty.js +55 -0
- package/dist/utils/is-folder-empty.js.map +1 -0
- package/dist/utils/validate-project-name.d.ts +15 -0
- package/dist/utils/validate-project-name.d.ts.map +1 -0
- package/dist/utils/validate-project-name.js +106 -0
- package/dist/utils/validate-project-name.js.map +1 -0
- package/index.js +4 -0
- package/package.json +73 -0
- 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
|