@curenorway/kode-mcp 1.2.0 → 1.4.0
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/README.md +183 -83
- package/dist/index.js +604 -46
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,42 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
MCP (Model Context Protocol) server that enables AI agents to manage Webflow scripts via Cure Kode CDN.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
7
|
+
- **Script Management** - List, create, update, and delete scripts
|
|
8
|
+
- **Deployment Control** - Deploy to staging, promote to production, rollback
|
|
9
|
+
- **Page Context** - Cache and retrieve page structures for development
|
|
10
|
+
- **Script Analysis** - Auto-analyze scripts for metadata (selectors, triggers, dependencies)
|
|
11
|
+
- **Production Safety** - Explicit enable required, confirmation for promote
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
15
|
### For Claude Code
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
The easiest way is to run `kode init` in your project, which creates `.mcp.json`:
|
|
18
18
|
|
|
19
19
|
```json
|
|
20
20
|
{
|
|
21
21
|
"mcpServers": {
|
|
22
22
|
"cure-kode": {
|
|
23
|
+
"type": "stdio",
|
|
23
24
|
"command": "npx",
|
|
24
|
-
"args": ["@curenorway/kode-mcp"]
|
|
25
|
+
"args": ["-y", "@curenorway/kode-mcp"]
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
```
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
Then restart Claude Code and approve the MCP when prompted.
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
### Manual Configuration
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
Add to your Claude Code configuration (`~/.claude/claude_code_config.json`):
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"cure-kode": {
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": ["-y", "@curenorway/kode-mcp"]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
37
47
|
|
|
38
|
-
|
|
48
|
+
## Configuration
|
|
39
49
|
|
|
40
|
-
|
|
50
|
+
The MCP server reads configuration from `.cure-kode/config.json` (created by `kode init`):
|
|
41
51
|
|
|
42
52
|
```json
|
|
43
53
|
{
|
|
@@ -45,46 +55,151 @@ Run `kode init` in your project to create `.cure-kode/config.json`:
|
|
|
45
55
|
"siteSlug": "my-site",
|
|
46
56
|
"siteName": "My Site",
|
|
47
57
|
"apiKey": "ck_...",
|
|
48
|
-
"scriptsDir": "kode",
|
|
58
|
+
"scriptsDir": ".cure-kode-scripts",
|
|
49
59
|
"environment": "staging"
|
|
50
60
|
}
|
|
51
61
|
```
|
|
52
62
|
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
Or via environment variables:
|
|
55
64
|
```bash
|
|
56
65
|
export CURE_KODE_API_KEY="ck_..."
|
|
57
66
|
export CURE_KODE_SITE_ID="your-site-uuid"
|
|
58
|
-
export CURE_KODE_API_URL="https://app.cure.no" # optional
|
|
59
67
|
```
|
|
60
68
|
|
|
61
69
|
## Available Tools
|
|
62
70
|
|
|
71
|
+
### Script Management
|
|
72
|
+
|
|
63
73
|
| Tool | Description |
|
|
64
74
|
|------|-------------|
|
|
65
75
|
| `kode_list_scripts` | List all scripts for the site |
|
|
66
|
-
| `kode_get_script` | Get script
|
|
67
|
-
| `kode_create_script` | Create a new script |
|
|
68
|
-
| `kode_update_script` | Update script
|
|
76
|
+
| `kode_get_script` | Get script by slug (supports `includeContent: false` for metadata only) |
|
|
77
|
+
| `kode_create_script` | Create a new script entry (metadata only, no content) |
|
|
78
|
+
| `kode_update_script` | Update script settings (autoLoad, scope, purpose) |
|
|
69
79
|
| `kode_delete_script` | Delete a script |
|
|
70
|
-
| `
|
|
71
|
-
|
|
80
|
+
| `kode_push` | Upload local files from `.cure-kode-scripts/` to server |
|
|
81
|
+
|
|
82
|
+
### Deployment
|
|
83
|
+
|
|
84
|
+
| Tool | Description |
|
|
85
|
+
|------|-------------|
|
|
86
|
+
| `kode_deploy` | Deploy to staging |
|
|
87
|
+
| `kode_promote` | Promote staging to production (**requires `confirmed: true`**) |
|
|
88
|
+
| `kode_rollback` | Rollback to previous deployment |
|
|
72
89
|
| `kode_status` | Get deployment status |
|
|
73
|
-
|
|
74
|
-
|
|
90
|
+
|
|
91
|
+
### Production Management
|
|
92
|
+
|
|
93
|
+
| Tool | Description |
|
|
94
|
+
|------|-------------|
|
|
95
|
+
| `kode_production_enable` | Enable production environment |
|
|
96
|
+
| `kode_production_disable` | Disable production environment |
|
|
97
|
+
|
|
98
|
+
### Page & HTML Analysis
|
|
99
|
+
|
|
100
|
+
| Tool | Description |
|
|
101
|
+
|------|-------------|
|
|
102
|
+
| `kode_fetch_html` | Fetch and parse HTML from URL |
|
|
103
|
+
| `kode_fetch_html_smart` | Fetch with CMS truncation for smaller context |
|
|
104
|
+
| `kode_list_pages` | List page definitions for the site |
|
|
105
|
+
| `kode_assign_script_to_page` | Assign script to specific pages |
|
|
106
|
+
| `kode_remove_script_from_page` | Remove script from page |
|
|
107
|
+
|
|
108
|
+
### Page Context (for AI Development)
|
|
109
|
+
|
|
110
|
+
| Tool | Description |
|
|
111
|
+
|------|-------------|
|
|
112
|
+
| `kode_refresh_page` | Fetch and cache page structure |
|
|
113
|
+
| `kode_get_page_context` | Get cached page context (sections, forms, CTAs, selectors) |
|
|
114
|
+
| `kode_list_pages_context` | List all cached page contexts |
|
|
115
|
+
|
|
116
|
+
### Script Metadata
|
|
117
|
+
|
|
118
|
+
| Tool | Description |
|
|
119
|
+
|------|-------------|
|
|
120
|
+
| `kode_analyze_script` | Analyze script and generate metadata |
|
|
121
|
+
| `kode_get_script_metadata` | Get script metadata and AI summary |
|
|
122
|
+
|
|
123
|
+
### Other
|
|
124
|
+
|
|
125
|
+
| Tool | Description |
|
|
126
|
+
|------|-------------|
|
|
75
127
|
| `kode_site_info` | Get site info and CDN URL |
|
|
128
|
+
| `kode_read_context` | Read AI context file |
|
|
129
|
+
| `kode_update_context` | Update AI context file |
|
|
130
|
+
|
|
131
|
+
## Important: Content Workflow
|
|
132
|
+
|
|
133
|
+
**MCP is the control plane - never send script content through MCP tools.**
|
|
134
|
+
|
|
135
|
+
The correct workflow:
|
|
136
|
+
1. Write script file locally to `.cure-kode-scripts/script-name.js`
|
|
137
|
+
2. Use `kode_push` to upload local files to server
|
|
138
|
+
3. Use `kode_deploy` to deploy
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
AI writes file → kode_push → kode_deploy
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Do NOT** pass content to `kode_create_script` or `kode_update_script` - these only handle metadata.
|
|
145
|
+
|
|
146
|
+
## Safety Features
|
|
147
|
+
|
|
148
|
+
### Production Confirmation
|
|
149
|
+
|
|
150
|
+
`kode_promote` requires explicit confirmation to prevent accidental production deployments:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// This will fail:
|
|
154
|
+
kode_promote()
|
|
155
|
+
|
|
156
|
+
// This works:
|
|
157
|
+
kode_promote({ confirmed: true })
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Production Disabled by Default
|
|
161
|
+
|
|
162
|
+
New sites start with production disabled. Must explicitly enable:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
kode_production_enable()
|
|
166
|
+
// Then can promote
|
|
167
|
+
kode_promote({ confirmed: true })
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Example Usage
|
|
76
171
|
|
|
77
|
-
|
|
172
|
+
### Create and Deploy a Script
|
|
78
173
|
|
|
79
|
-
|
|
174
|
+
```
|
|
175
|
+
1. "Create a new script called tracking.js"
|
|
176
|
+
→ kode_create_script({ name: "tracking", slug: "tracking", type: "javascript" })
|
|
177
|
+
|
|
178
|
+
2. Write the file locally
|
|
179
|
+
→ Write to .cure-kode-scripts/tracking.js
|
|
180
|
+
|
|
181
|
+
3. "Push the changes"
|
|
182
|
+
→ kode_push()
|
|
183
|
+
|
|
184
|
+
4. "Deploy to staging"
|
|
185
|
+
→ kode_deploy()
|
|
186
|
+
|
|
187
|
+
5. "Promote to production"
|
|
188
|
+
→ kode_promote({ confirmed: true })
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Analyze a Page and Add Functionality
|
|
80
192
|
|
|
81
193
|
```
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"What
|
|
87
|
-
"
|
|
194
|
+
1. "Analyze the homepage"
|
|
195
|
+
→ kode_fetch_html_smart({ url: "https://mysite.com" })
|
|
196
|
+
→ kode_refresh_page({ url: "https://mysite.com" })
|
|
197
|
+
|
|
198
|
+
2. "What sections are on this page?"
|
|
199
|
+
→ kode_get_page_context({ url: "https://mysite.com" })
|
|
200
|
+
|
|
201
|
+
3. "Add a script to animate the hero section"
|
|
202
|
+
→ Create script, write file, push, deploy
|
|
88
203
|
```
|
|
89
204
|
|
|
90
205
|
## How It Works
|
|
@@ -92,88 +207,73 @@ Once configured, you can ask Claude:
|
|
|
92
207
|
```
|
|
93
208
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
94
209
|
│ AI Agent │ MCP │ Kode MCP │ REST │ Cure Kode API │
|
|
95
|
-
│ (Claude/etc) │─────▶│ Server │─────▶│ (
|
|
210
|
+
│ (Claude/etc) │─────▶│ Server │─────▶│ (app.cure.no) │
|
|
96
211
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
97
212
|
│
|
|
98
213
|
▼
|
|
99
214
|
┌─────────────────┐
|
|
100
215
|
│ Local Scripts │
|
|
101
|
-
│
|
|
216
|
+
│(.cure-kode-scripts/)│
|
|
102
217
|
└─────────────────┘
|
|
103
218
|
```
|
|
104
219
|
|
|
105
|
-
1. AI agent calls MCP tools (e.g., `kode_create_script`)
|
|
106
|
-
2. MCP server reads config from `.cure-kode/config.json`
|
|
107
|
-
3. MCP server calls Cure Kode REST API with API key
|
|
108
|
-
4. Results returned to AI agent
|
|
109
|
-
|
|
110
220
|
## Security
|
|
111
221
|
|
|
112
222
|
### API Key Authentication
|
|
113
223
|
|
|
114
|
-
- **
|
|
224
|
+
- **Hashed Storage**: API keys are SHA256/HMAC hashed before server storage
|
|
115
225
|
- **Site-scoped**: Each API key is bound to a specific CDN site
|
|
116
|
-
- **Permission-based**:
|
|
117
|
-
- `read` - List/view scripts and deployments
|
|
118
|
-
- `write` - Create and update scripts
|
|
119
|
-
- `deploy` - Deploy to staging/production
|
|
120
|
-
- `delete` - Delete scripts
|
|
226
|
+
- **Permission-based**: Granular permissions (read, write, deploy, delete)
|
|
121
227
|
- **Expiration**: Keys can have optional expiration dates
|
|
122
228
|
|
|
123
|
-
###
|
|
229
|
+
### CORS Security (v2.6)
|
|
124
230
|
|
|
125
|
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
231
|
+
Script endpoints restrict CORS to configured domains:
|
|
232
|
+
- Site's domain, staging_domain, production_domain
|
|
233
|
+
- Webflow preview domains (*.webflow.io)
|
|
234
|
+
- Localhost for development
|
|
235
|
+
- **No wildcard CORS** on script endpoints
|
|
128
236
|
|
|
129
|
-
###
|
|
237
|
+
### SSRF Protection
|
|
130
238
|
|
|
131
|
-
|
|
132
|
-
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
- Cloud metadata endpoints (169.254.169.254)
|
|
136
|
-
- Only HTTP/HTTPS protocols allowed
|
|
137
|
-
|
|
138
|
-
### Best Practices
|
|
139
|
-
|
|
140
|
-
- Generate separate API keys for different environments/developers
|
|
141
|
-
- Use read-only keys when full access isn't needed
|
|
142
|
-
- Rotate keys periodically
|
|
143
|
-
- Revoke keys when team members leave
|
|
239
|
+
HTML fetch endpoints block:
|
|
240
|
+
- Private IP ranges (127.0.0.0/8, 10.0.0.0/8, etc.)
|
|
241
|
+
- Cloud metadata endpoints (169.254.169.254)
|
|
242
|
+
- Internal hostnames
|
|
144
243
|
|
|
145
244
|
## Troubleshooting
|
|
146
245
|
|
|
147
246
|
### "Cure Kode not configured"
|
|
148
247
|
|
|
149
|
-
|
|
150
|
-
1. Run `kode init` in your project directory
|
|
151
|
-
2. Set `CURE_KODE_API_KEY` and `CURE_KODE_SITE_ID` environment variables
|
|
248
|
+
Run `kode init` in your project directory to create configuration.
|
|
152
249
|
|
|
153
250
|
### "API key invalid"
|
|
154
251
|
|
|
155
|
-
- Check
|
|
156
|
-
- Verify
|
|
157
|
-
- Ensure
|
|
252
|
+
- Check key starts with `ck_`
|
|
253
|
+
- Verify key hasn't expired
|
|
254
|
+
- Ensure key has required permissions
|
|
158
255
|
|
|
159
|
-
###
|
|
256
|
+
### "Production not enabled"
|
|
160
257
|
|
|
161
|
-
|
|
162
|
-
- Check the MCP server is running: `npx @curenorway/kode-mcp`
|
|
163
|
-
- Verify config file syntax
|
|
258
|
+
Use `kode_production_enable()` before promoting.
|
|
164
259
|
|
|
165
|
-
|
|
260
|
+
### "Promote requires confirmation"
|
|
166
261
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
262
|
+
Add `confirmed: true` parameter:
|
|
263
|
+
```typescript
|
|
264
|
+
kode_promote({ confirmed: true })
|
|
265
|
+
```
|
|
170
266
|
|
|
171
|
-
|
|
172
|
-
pnpm build
|
|
267
|
+
### Tools not appearing in Claude
|
|
173
268
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
269
|
+
1. Restart Claude Code after config changes
|
|
270
|
+
2. Check `.mcp.json` syntax
|
|
271
|
+
3. Verify MCP server runs: `npx @curenorway/kode-mcp`
|
|
272
|
+
|
|
273
|
+
## Requirements
|
|
274
|
+
|
|
275
|
+
- Node.js 18 or later
|
|
276
|
+
- Cure Kode API key (from https://app.cure.no/tools/kode)
|
|
177
277
|
|
|
178
278
|
## License
|
|
179
279
|
|
package/dist/index.js
CHANGED
|
@@ -105,6 +105,25 @@ var KodeApiClient = class {
|
|
|
105
105
|
async getDeploymentStatus(siteId) {
|
|
106
106
|
return this.request(`/api/cdn/sites/${siteId}/deployments/status`);
|
|
107
107
|
}
|
|
108
|
+
async rollback(siteId, environment = "staging") {
|
|
109
|
+
return this.request("/api/cdn/deploy/rollback", {
|
|
110
|
+
method: "POST",
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
siteId,
|
|
113
|
+
environment
|
|
114
|
+
})
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
// v2.3: Production enabled toggle
|
|
118
|
+
async setProductionEnabled(siteId, enabled, productionDomain) {
|
|
119
|
+
return this.request(`/api/cdn/sites/${siteId}/production`, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
body: JSON.stringify({
|
|
122
|
+
enabled,
|
|
123
|
+
productionDomain
|
|
124
|
+
})
|
|
125
|
+
});
|
|
126
|
+
}
|
|
108
127
|
// HTML operations
|
|
109
128
|
async fetchHtml(siteId, url) {
|
|
110
129
|
return this.request("/api/cdn/fetch-html", {
|
|
@@ -121,6 +140,16 @@ var KodeApiClient = class {
|
|
|
121
140
|
return false;
|
|
122
141
|
}
|
|
123
142
|
}
|
|
143
|
+
// Metadata operations
|
|
144
|
+
async analyzeScript(scriptId, options = {}) {
|
|
145
|
+
return this.request(`/api/cdn/scripts/${scriptId}/analyze`, {
|
|
146
|
+
method: "POST",
|
|
147
|
+
body: JSON.stringify(options)
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async getScriptMetadata(scriptId) {
|
|
151
|
+
return this.request(`/api/cdn/scripts/${scriptId}/metadata`);
|
|
152
|
+
}
|
|
124
153
|
};
|
|
125
154
|
|
|
126
155
|
// src/config.ts
|
|
@@ -274,7 +303,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
274
303
|
},
|
|
275
304
|
{
|
|
276
305
|
name: "kode_create_script",
|
|
277
|
-
description: "Create a new script
|
|
306
|
+
description: "Create a new script entry (metadata only). After creating, write the script file locally to .cure-kode-scripts/{name}.js and use kode_push to upload content. This keeps script content out of MCP context.",
|
|
278
307
|
inputSchema: {
|
|
279
308
|
type: "object",
|
|
280
309
|
properties: {
|
|
@@ -287,10 +316,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
287
316
|
enum: ["javascript", "css"],
|
|
288
317
|
description: "Script type"
|
|
289
318
|
},
|
|
290
|
-
content: {
|
|
291
|
-
type: "string",
|
|
292
|
-
description: "Script content (JavaScript or CSS code)"
|
|
293
|
-
},
|
|
294
319
|
scope: {
|
|
295
320
|
type: "string",
|
|
296
321
|
enum: ["global", "page-specific"],
|
|
@@ -298,15 +323,37 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
298
323
|
},
|
|
299
324
|
autoLoad: {
|
|
300
325
|
type: "boolean",
|
|
301
|
-
description: "Whether to auto-load the script. Default: true for global scripts, false for page-specific.
|
|
326
|
+
description: "Whether to auto-load the script. Default: true for global scripts, false for page-specific."
|
|
327
|
+
},
|
|
328
|
+
purpose: {
|
|
329
|
+
type: "string",
|
|
330
|
+
description: "Brief description of what the script does (for metadata)"
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
required: ["name", "type"]
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: "kode_push",
|
|
338
|
+
description: "Push local script files to Cure Kode. Reads files from .cure-kode-scripts/ folder and uploads to API. This is the proper way to sync code - never send script content through other MCP tools.",
|
|
339
|
+
inputSchema: {
|
|
340
|
+
type: "object",
|
|
341
|
+
properties: {
|
|
342
|
+
scriptSlug: {
|
|
343
|
+
type: "string",
|
|
344
|
+
description: 'Specific script slug to push (e.g., "map"). If omitted, pushes all changed scripts.'
|
|
345
|
+
},
|
|
346
|
+
force: {
|
|
347
|
+
type: "boolean",
|
|
348
|
+
description: "Force push even if content appears unchanged. Default: false"
|
|
302
349
|
}
|
|
303
350
|
},
|
|
304
|
-
required: [
|
|
351
|
+
required: []
|
|
305
352
|
}
|
|
306
353
|
},
|
|
307
354
|
{
|
|
308
355
|
name: "kode_update_script",
|
|
309
|
-
description: "Update
|
|
356
|
+
description: "Update script settings (NOT content). For content changes, edit the local file in .cure-kode-scripts/ and use kode_push. This tool is for metadata, scope, and autoLoad changes only.",
|
|
310
357
|
inputSchema: {
|
|
311
358
|
type: "object",
|
|
312
359
|
properties: {
|
|
@@ -314,10 +361,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
314
361
|
type: "string",
|
|
315
362
|
description: "Script slug or ID to update"
|
|
316
363
|
},
|
|
317
|
-
content: {
|
|
318
|
-
type: "string",
|
|
319
|
-
description: "New script content"
|
|
320
|
-
},
|
|
321
364
|
autoLoad: {
|
|
322
365
|
type: "boolean",
|
|
323
366
|
description: "Whether to auto-load the script. Set to true to load automatically, false for manual loading via CK.loadScript()."
|
|
@@ -327,9 +370,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
327
370
|
enum: ["global", "page-specific"],
|
|
328
371
|
description: "Change script scope. Note: changing to page-specific requires assigning to pages."
|
|
329
372
|
},
|
|
330
|
-
|
|
373
|
+
purpose: {
|
|
331
374
|
type: "string",
|
|
332
|
-
description: "
|
|
375
|
+
description: "Update the script purpose description (shown in Chrome Extension)"
|
|
376
|
+
},
|
|
377
|
+
regenerateSummary: {
|
|
378
|
+
type: "boolean",
|
|
379
|
+
description: "Re-analyze the script content and regenerate the AI summary. Useful after significant changes."
|
|
333
380
|
}
|
|
334
381
|
},
|
|
335
382
|
required: ["slug"]
|
|
@@ -370,16 +417,59 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
370
417
|
},
|
|
371
418
|
{
|
|
372
419
|
name: "kode_promote",
|
|
373
|
-
description: "Promote the latest staging deployment to production. Use this after testing on staging.",
|
|
420
|
+
description: "Promote the latest staging deployment to production. Use this after testing on staging. IMPORTANT: Requires confirmed=true to actually promote - this prevents accidental production deployments.",
|
|
374
421
|
inputSchema: {
|
|
375
422
|
type: "object",
|
|
376
|
-
properties: {
|
|
423
|
+
properties: {
|
|
424
|
+
confirmed: {
|
|
425
|
+
type: "boolean",
|
|
426
|
+
description: "Safety confirmation. Must be set to true to proceed with production promotion. If false or missing, returns a preview of what would be promoted."
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
required: []
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
name: "kode_rollback",
|
|
434
|
+
description: "Rollback to the previous deployment. Copies the previous version back to the active environment. Use this if a deployment caused issues.",
|
|
435
|
+
inputSchema: {
|
|
436
|
+
type: "object",
|
|
437
|
+
properties: {
|
|
438
|
+
environment: {
|
|
439
|
+
type: "string",
|
|
440
|
+
enum: ["staging", "production"],
|
|
441
|
+
description: "Environment to rollback. Default: staging"
|
|
442
|
+
}
|
|
443
|
+
},
|
|
377
444
|
required: []
|
|
378
445
|
}
|
|
379
446
|
},
|
|
380
447
|
{
|
|
381
448
|
name: "kode_status",
|
|
382
|
-
description: "Get the current deployment status
|
|
449
|
+
description: "Get the current deployment status including production enabled state, staging and production environments.",
|
|
450
|
+
inputSchema: {
|
|
451
|
+
type: "object",
|
|
452
|
+
properties: {},
|
|
453
|
+
required: []
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
name: "kode_production_enable",
|
|
458
|
+
description: "Enable production environment for this site. Required before promoting to production. Sites start in staging-only mode by default.",
|
|
459
|
+
inputSchema: {
|
|
460
|
+
type: "object",
|
|
461
|
+
properties: {
|
|
462
|
+
productionDomain: {
|
|
463
|
+
type: "string",
|
|
464
|
+
description: 'Production domain (e.g., "example.com"). Optional - can be set later.'
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
required: []
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
name: "kode_production_disable",
|
|
472
|
+
description: "Disable production environment for this site. Production requests will return an empty script. Useful during development when only staging should be active.",
|
|
383
473
|
inputSchema: {
|
|
384
474
|
type: "object",
|
|
385
475
|
properties: {},
|
|
@@ -458,6 +548,38 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
458
548
|
required: []
|
|
459
549
|
}
|
|
460
550
|
},
|
|
551
|
+
{
|
|
552
|
+
name: "kode_analyze_script",
|
|
553
|
+
description: "Analyze a script's content and auto-generate metadata. Detects DOM selectors, event triggers, dependencies (GSAP, Swiper, etc.), and behavior patterns. Optionally saves the analysis to the script.",
|
|
554
|
+
inputSchema: {
|
|
555
|
+
type: "object",
|
|
556
|
+
properties: {
|
|
557
|
+
slug: {
|
|
558
|
+
type: "string",
|
|
559
|
+
description: "Script slug or ID to analyze"
|
|
560
|
+
},
|
|
561
|
+
saveToScript: {
|
|
562
|
+
type: "boolean",
|
|
563
|
+
description: "Save the generated metadata to the script. Default: false (preview only)"
|
|
564
|
+
}
|
|
565
|
+
},
|
|
566
|
+
required: ["slug"]
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
name: "kode_get_script_metadata",
|
|
571
|
+
description: "Get the metadata for a script, including detected DOM targets, triggers, dependencies, and AI summary. Useful for Chrome Extension visibility.",
|
|
572
|
+
inputSchema: {
|
|
573
|
+
type: "object",
|
|
574
|
+
properties: {
|
|
575
|
+
slug: {
|
|
576
|
+
type: "string",
|
|
577
|
+
description: "Script slug or ID to get metadata for"
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
required: ["slug"]
|
|
581
|
+
}
|
|
582
|
+
},
|
|
461
583
|
{
|
|
462
584
|
name: "kode_read_context",
|
|
463
585
|
description: "Read the project context file (.cure-kode/context.md). Contains current scripts, notes, and session history. ALWAYS call this before starting work on a Kode project.",
|
|
@@ -667,35 +789,155 @@ ${script.content}`
|
|
|
667
789
|
};
|
|
668
790
|
}
|
|
669
791
|
case "kode_create_script": {
|
|
670
|
-
const { name: scriptName, type,
|
|
792
|
+
const { name: scriptName, type, scope, autoLoad, purpose } = args;
|
|
671
793
|
const scriptScope = scope || "global";
|
|
672
794
|
const script = await client.createScript(siteId, {
|
|
673
795
|
name: scriptName,
|
|
674
796
|
slug: scriptName,
|
|
675
797
|
type,
|
|
676
|
-
content,
|
|
798
|
+
content: "",
|
|
799
|
+
// Empty - content comes from local files via kode_push
|
|
677
800
|
scope: scriptScope,
|
|
678
|
-
autoLoad
|
|
679
|
-
|
|
801
|
+
autoLoad,
|
|
802
|
+
metadata: purpose ? { purpose } : void 0
|
|
680
803
|
});
|
|
681
|
-
const
|
|
804
|
+
const scriptsDir = getScriptsDir();
|
|
805
|
+
const ext = type === "javascript" ? "js" : "css";
|
|
806
|
+
const localPath = scriptsDir ? `${scriptsDir}/${scriptName}.${ext}` : `.cure-kode-scripts/${scriptName}.${ext}`;
|
|
807
|
+
let responseText = `Created script "${script.name}" (${script.type})`;
|
|
808
|
+
responseText += `
|
|
809
|
+
Slug: ${script.slug}`;
|
|
810
|
+
responseText += `
|
|
811
|
+
Scope: ${script.scope}`;
|
|
812
|
+
responseText += `
|
|
813
|
+
Auto-load: ${script.auto_load ? "yes" : "no"}`;
|
|
814
|
+
if (purpose) responseText += `
|
|
815
|
+
Purpose: ${purpose}`;
|
|
816
|
+
responseText += `
|
|
817
|
+
|
|
818
|
+
Next steps:`;
|
|
819
|
+
responseText += `
|
|
820
|
+
1. Create file: ${localPath}`;
|
|
821
|
+
responseText += `
|
|
822
|
+
2. Write your ${type} code`;
|
|
823
|
+
responseText += `
|
|
824
|
+
3. Run kode_push to upload content`;
|
|
825
|
+
responseText += `
|
|
826
|
+
4. Run kode_deploy to make it live`;
|
|
682
827
|
return {
|
|
683
828
|
content: [
|
|
684
829
|
{
|
|
685
830
|
type: "text",
|
|
686
|
-
text:
|
|
687
|
-
Slug: ${script.slug}
|
|
688
|
-
Scope: ${script.scope}
|
|
689
|
-
Auto-load: ${autoLoadStatus}
|
|
690
|
-
Version: ${script.current_version}
|
|
691
|
-
|
|
692
|
-
Note: Run kode_deploy to make it live.`
|
|
831
|
+
text: responseText
|
|
693
832
|
}
|
|
694
833
|
]
|
|
695
834
|
};
|
|
696
835
|
}
|
|
836
|
+
case "kode_push": {
|
|
837
|
+
const { scriptSlug, force } = args;
|
|
838
|
+
const scriptsDir = getScriptsDir();
|
|
839
|
+
if (!scriptsDir || !fs2.existsSync(scriptsDir)) {
|
|
840
|
+
return {
|
|
841
|
+
content: [{
|
|
842
|
+
type: "text",
|
|
843
|
+
text: 'No .cure-kode-scripts/ folder found. Run "kode init" first or create the folder manually.'
|
|
844
|
+
}],
|
|
845
|
+
isError: true
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
const remoteScripts = await client.listScripts(siteId);
|
|
849
|
+
const localFiles = fs2.readdirSync(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css"));
|
|
850
|
+
if (scriptSlug) {
|
|
851
|
+
const ext = remoteScripts.find((s) => s.slug === scriptSlug)?.type === "css" ? "css" : "js";
|
|
852
|
+
const fileName = `${scriptSlug}.${ext}`;
|
|
853
|
+
const filePath = path2.join(scriptsDir, fileName);
|
|
854
|
+
if (!fs2.existsSync(filePath)) {
|
|
855
|
+
return {
|
|
856
|
+
content: [{
|
|
857
|
+
type: "text",
|
|
858
|
+
text: `File not found: ${filePath}
|
|
859
|
+
|
|
860
|
+
Available files: ${localFiles.join(", ") || "(none)"}`
|
|
861
|
+
}],
|
|
862
|
+
isError: true
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
866
|
+
const remote = remoteScripts.find((s) => s.slug === scriptSlug);
|
|
867
|
+
if (!remote) {
|
|
868
|
+
return {
|
|
869
|
+
content: [{
|
|
870
|
+
type: "text",
|
|
871
|
+
text: `Script "${scriptSlug}" not found on server. Create it first with kode_create_script.`
|
|
872
|
+
}],
|
|
873
|
+
isError: true
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
if (!force && remote.content === content) {
|
|
877
|
+
return {
|
|
878
|
+
content: [{
|
|
879
|
+
type: "text",
|
|
880
|
+
text: `Script "${scriptSlug}" is already up to date (${content.length} chars). Use force: true to push anyway.`
|
|
881
|
+
}]
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
const updated = await client.updateScript(remote.id, {
|
|
885
|
+
content,
|
|
886
|
+
changeSummary: "Pushed via MCP"
|
|
887
|
+
});
|
|
888
|
+
return {
|
|
889
|
+
content: [{
|
|
890
|
+
type: "text",
|
|
891
|
+
text: `Pushed "${scriptSlug}": ${content.length} chars \u2192 v${updated.current_version}
|
|
892
|
+
|
|
893
|
+
Run kode_deploy to make changes live.`
|
|
894
|
+
}]
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
const results = [];
|
|
898
|
+
let pushedCount = 0;
|
|
899
|
+
let skippedCount = 0;
|
|
900
|
+
for (const file of localFiles) {
|
|
901
|
+
const slug = file.replace(/\.(js|css)$/, "");
|
|
902
|
+
const filePath = path2.join(scriptsDir, file);
|
|
903
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
904
|
+
const remote = remoteScripts.find((s) => s.slug === slug);
|
|
905
|
+
if (!remote) {
|
|
906
|
+
results.push(`\u26A0\uFE0F ${slug}: not on server (create with kode_create_script first)`);
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
if (!force && remote.content === content) {
|
|
910
|
+
skippedCount++;
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
try {
|
|
914
|
+
const updated = await client.updateScript(remote.id, {
|
|
915
|
+
content,
|
|
916
|
+
changeSummary: "Pushed via MCP"
|
|
917
|
+
});
|
|
918
|
+
results.push(`\u2713 ${slug}: ${content.length} chars \u2192 v${updated.current_version}`);
|
|
919
|
+
pushedCount++;
|
|
920
|
+
} catch (err) {
|
|
921
|
+
results.push(`\u2717 ${slug}: ${err instanceof Error ? err.message : "failed"}`);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
let responseText = `Push complete: ${pushedCount} updated, ${skippedCount} unchanged`;
|
|
925
|
+
if (results.length > 0) {
|
|
926
|
+
responseText += `
|
|
927
|
+
|
|
928
|
+
${results.join("\n")}`;
|
|
929
|
+
}
|
|
930
|
+
if (pushedCount > 0) {
|
|
931
|
+
responseText += `
|
|
932
|
+
|
|
933
|
+
Run kode_deploy to make changes live.`;
|
|
934
|
+
}
|
|
935
|
+
return {
|
|
936
|
+
content: [{ type: "text", text: responseText }]
|
|
937
|
+
};
|
|
938
|
+
}
|
|
697
939
|
case "kode_update_script": {
|
|
698
|
-
const { slug,
|
|
940
|
+
const { slug, autoLoad, scope, purpose, regenerateSummary } = args;
|
|
699
941
|
const scripts = await client.listScripts(siteId);
|
|
700
942
|
const script = scripts.find((s) => s.slug === slug || s.id === slug);
|
|
701
943
|
if (!script) {
|
|
@@ -704,24 +946,32 @@ Note: Run kode_deploy to make it live.`
|
|
|
704
946
|
isError: true
|
|
705
947
|
};
|
|
706
948
|
}
|
|
707
|
-
const updateData = {
|
|
708
|
-
|
|
949
|
+
const updateData = {
|
|
950
|
+
changeSummary: "Updated settings via MCP"
|
|
951
|
+
};
|
|
709
952
|
if (autoLoad !== void 0) updateData.autoLoad = autoLoad;
|
|
710
953
|
if (scope !== void 0) updateData.scope = scope;
|
|
711
|
-
updateData.
|
|
954
|
+
if (purpose !== void 0) updateData.metadata = { purpose };
|
|
955
|
+
if (regenerateSummary !== void 0) updateData.regenerateSummary = regenerateSummary;
|
|
712
956
|
const updated = await client.updateScript(script.id, updateData);
|
|
713
957
|
const changes = [];
|
|
714
|
-
if (
|
|
715
|
-
if (autoLoad !== void 0) changes.push(`auto_load \u2192 ${autoLoad}`);
|
|
958
|
+
if (autoLoad !== void 0) changes.push(`autoLoad \u2192 ${autoLoad}`);
|
|
716
959
|
if (scope !== void 0) changes.push(`scope \u2192 ${scope}`);
|
|
960
|
+
if (purpose !== void 0) changes.push(`purpose updated`);
|
|
961
|
+
if (regenerateSummary) changes.push("AI summary regenerated");
|
|
962
|
+
let responseText = `Updated script "${updated.name}"`;
|
|
963
|
+
if (changes.length > 0) {
|
|
964
|
+
responseText += `
|
|
965
|
+
Changes: ${changes.join(", ")}`;
|
|
966
|
+
}
|
|
967
|
+
responseText += `
|
|
968
|
+
|
|
969
|
+
To update content: edit local file and run kode_push`;
|
|
717
970
|
return {
|
|
718
971
|
content: [
|
|
719
972
|
{
|
|
720
973
|
type: "text",
|
|
721
|
-
text:
|
|
722
|
-
Changes: ${changes.join(", ")}
|
|
723
|
-
|
|
724
|
-
Note: Run kode_deploy to make changes live.`
|
|
974
|
+
text: responseText
|
|
725
975
|
}
|
|
726
976
|
]
|
|
727
977
|
};
|
|
@@ -752,19 +1002,55 @@ Note: Run kode_deploy to make changes live.`
|
|
|
752
1002
|
environment: environment || "staging",
|
|
753
1003
|
notes: notes || "Deployed via MCP"
|
|
754
1004
|
});
|
|
1005
|
+
let responseText = `Deployment ${deployment.version} to ${deployment.environment}: ${deployment.status}`;
|
|
1006
|
+
responseText += `
|
|
1007
|
+
Started: ${deployment.started_at}`;
|
|
1008
|
+
if (deployment.completed_at) {
|
|
1009
|
+
responseText += `
|
|
1010
|
+
Completed: ${deployment.completed_at}`;
|
|
1011
|
+
}
|
|
1012
|
+
const scriptSizes = deployment.stats?.scriptSizes;
|
|
1013
|
+
if (scriptSizes && scriptSizes.length > 0) {
|
|
1014
|
+
responseText += `
|
|
1015
|
+
|
|
1016
|
+
Scripts deployed (${scriptSizes.length}):`;
|
|
1017
|
+
for (const s of scriptSizes) {
|
|
1018
|
+
const flags = [
|
|
1019
|
+
s.scope === "global" ? "G" : "P",
|
|
1020
|
+
s.autoLoad ? "\u26A1" : "\u25CB"
|
|
1021
|
+
].join("");
|
|
1022
|
+
responseText += `
|
|
1023
|
+
${s.slug} [${flags}]: ${s.contentSize} chars`;
|
|
1024
|
+
if (s.contentSize === 0) {
|
|
1025
|
+
responseText += " \u26A0\uFE0F EMPTY";
|
|
1026
|
+
} else if (s.contentSize < 50 && s.scope === "global") {
|
|
1027
|
+
responseText += " \u26A0\uFE0F very small";
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
755
1031
|
return {
|
|
756
1032
|
content: [
|
|
757
1033
|
{
|
|
758
1034
|
type: "text",
|
|
759
|
-
text:
|
|
760
|
-
Started: ${deployment.started_at}${deployment.completed_at ? `
|
|
761
|
-
Completed: ${deployment.completed_at}` : ""}`
|
|
1035
|
+
text: responseText
|
|
762
1036
|
}
|
|
763
1037
|
]
|
|
764
1038
|
};
|
|
765
1039
|
}
|
|
766
1040
|
case "kode_promote": {
|
|
1041
|
+
const { confirmed } = args;
|
|
767
1042
|
const status = await client.getDeploymentStatus(siteId);
|
|
1043
|
+
if (!status.productionEnabled) {
|
|
1044
|
+
return {
|
|
1045
|
+
content: [
|
|
1046
|
+
{
|
|
1047
|
+
type: "text",
|
|
1048
|
+
text: "Cannot promote: Production is not enabled for this site.\n\nRun kode_production_enable first to activate the production environment."
|
|
1049
|
+
}
|
|
1050
|
+
],
|
|
1051
|
+
isError: true
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
768
1054
|
if (!status.canPromote) {
|
|
769
1055
|
return {
|
|
770
1056
|
content: [
|
|
@@ -776,23 +1062,109 @@ Completed: ${deployment.completed_at}` : ""}`
|
|
|
776
1062
|
isError: true
|
|
777
1063
|
};
|
|
778
1064
|
}
|
|
1065
|
+
if (!confirmed) {
|
|
1066
|
+
const stagingVersion = status.staging.lastSuccessful?.version || "unknown";
|
|
1067
|
+
const productionVersion = status.production.lastSuccessful?.version || "(none)";
|
|
1068
|
+
return {
|
|
1069
|
+
content: [
|
|
1070
|
+
{
|
|
1071
|
+
type: "text",
|
|
1072
|
+
text: `\u26A0\uFE0F PRODUCTION PROMOTION CONFIRMATION REQUIRED
|
|
1073
|
+
|
|
1074
|
+
This will deploy to production:
|
|
1075
|
+
Staging version: ${stagingVersion}
|
|
1076
|
+
Current production: ${productionVersion}
|
|
1077
|
+
|
|
1078
|
+
To proceed, call kode_promote with confirmed: true
|
|
1079
|
+
|
|
1080
|
+
Note: Always test on staging first before promoting to production.`
|
|
1081
|
+
}
|
|
1082
|
+
]
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
779
1085
|
const deployment = await client.promoteToProduction(siteId);
|
|
780
1086
|
return {
|
|
781
1087
|
content: [
|
|
782
1088
|
{
|
|
783
1089
|
type: "text",
|
|
784
|
-
text:
|
|
1090
|
+
text: `\u2705 Promoted ${deployment.version} to production
|
|
785
1091
|
Status: ${deployment.status}`
|
|
786
1092
|
}
|
|
787
1093
|
]
|
|
788
1094
|
};
|
|
789
1095
|
}
|
|
1096
|
+
case "kode_rollback": {
|
|
1097
|
+
const { environment = "staging" } = args;
|
|
1098
|
+
if (environment === "production") {
|
|
1099
|
+
const status = await client.getDeploymentStatus(siteId);
|
|
1100
|
+
if (!status.productionEnabled) {
|
|
1101
|
+
return {
|
|
1102
|
+
content: [
|
|
1103
|
+
{
|
|
1104
|
+
type: "text",
|
|
1105
|
+
text: "Cannot rollback production: Production is not enabled for this site."
|
|
1106
|
+
}
|
|
1107
|
+
],
|
|
1108
|
+
isError: true
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
const result = await client.rollback(siteId, environment);
|
|
1113
|
+
return {
|
|
1114
|
+
content: [
|
|
1115
|
+
{
|
|
1116
|
+
type: "text",
|
|
1117
|
+
text: `\u2705 Rolled back ${environment}
|
|
1118
|
+
|
|
1119
|
+
From: ${result.rolledBackFrom.version}
|
|
1120
|
+
To: ${result.rolledBackTo.version}
|
|
1121
|
+
|
|
1122
|
+
CDN URL: ${result.cdn_url}
|
|
1123
|
+
Duration: ${result.duration_ms}ms`
|
|
1124
|
+
}
|
|
1125
|
+
]
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
case "kode_production_enable": {
|
|
1129
|
+
const { productionDomain } = args;
|
|
1130
|
+
const result = await client.setProductionEnabled(siteId, true, productionDomain);
|
|
1131
|
+
let text = "Production environment enabled!\n\n";
|
|
1132
|
+
if (result.productionDomain) {
|
|
1133
|
+
text += `Domain: ${result.productionDomain}
|
|
1134
|
+
`;
|
|
1135
|
+
}
|
|
1136
|
+
text += "\nNext steps:\n";
|
|
1137
|
+
text += "1. Deploy to staging: kode_deploy\n";
|
|
1138
|
+
text += "2. Promote to production: kode_promote";
|
|
1139
|
+
return {
|
|
1140
|
+
content: [{ type: "text", text }]
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
case "kode_production_disable": {
|
|
1144
|
+
await client.setProductionEnabled(siteId, false);
|
|
1145
|
+
return {
|
|
1146
|
+
content: [
|
|
1147
|
+
{
|
|
1148
|
+
type: "text",
|
|
1149
|
+
text: "Production environment disabled.\n\nOnly staging is now active. Production domain requests will receive an empty script with a warning."
|
|
1150
|
+
}
|
|
1151
|
+
]
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
790
1154
|
case "kode_status": {
|
|
791
1155
|
const status = await client.getDeploymentStatus(siteId);
|
|
792
1156
|
const config = getConfig();
|
|
793
1157
|
let text = `Site: ${config?.siteName || "Unknown"}
|
|
794
1158
|
|
|
795
1159
|
`;
|
|
1160
|
+
const productionEnabled = status.productionEnabled ?? false;
|
|
1161
|
+
text += `PRODUCTION STATUS: ${productionEnabled ? "\u2713 Enabled" : "\u25CB Disabled (staging only)"}
|
|
1162
|
+
`;
|
|
1163
|
+
if (productionEnabled && status.productionDomain) {
|
|
1164
|
+
text += ` Domain: ${status.productionDomain}
|
|
1165
|
+
`;
|
|
1166
|
+
}
|
|
1167
|
+
text += "\n";
|
|
796
1168
|
text += `STAGING:
|
|
797
1169
|
`;
|
|
798
1170
|
if (status.staging.lastSuccessful) {
|
|
@@ -807,17 +1179,25 @@ Status: ${deployment.status}`
|
|
|
807
1179
|
text += `
|
|
808
1180
|
PRODUCTION:
|
|
809
1181
|
`;
|
|
810
|
-
if (
|
|
1182
|
+
if (!productionEnabled) {
|
|
1183
|
+
text += ` (Disabled - use kode_production_enable to activate)
|
|
1184
|
+
`;
|
|
1185
|
+
} else if (status.production.lastSuccessful) {
|
|
811
1186
|
text += ` Version: ${status.production.lastSuccessful.version}
|
|
812
1187
|
`;
|
|
813
1188
|
text += ` Deployed: ${status.production.lastSuccessful.completed_at}
|
|
814
1189
|
`;
|
|
815
1190
|
} else {
|
|
816
|
-
text += `
|
|
1191
|
+
text += ` Enabled, not yet deployed
|
|
817
1192
|
`;
|
|
818
1193
|
}
|
|
819
|
-
|
|
1194
|
+
if (productionEnabled) {
|
|
1195
|
+
text += `
|
|
820
1196
|
Can promote staging to production: ${status.canPromote ? "Yes" : "No"}`;
|
|
1197
|
+
} else {
|
|
1198
|
+
text += `
|
|
1199
|
+
Enable production first (kode_production_enable) before promoting.`;
|
|
1200
|
+
}
|
|
821
1201
|
return {
|
|
822
1202
|
content: [{ type: "text", text }]
|
|
823
1203
|
};
|
|
@@ -1002,6 +1382,184 @@ Embed code:
|
|
|
1002
1382
|
content: [{ type: "text", text }]
|
|
1003
1383
|
};
|
|
1004
1384
|
}
|
|
1385
|
+
case "kode_analyze_script": {
|
|
1386
|
+
const { slug, saveToScript } = args;
|
|
1387
|
+
const scripts = await client.listScripts(siteId);
|
|
1388
|
+
const script = scripts.find((s) => s.slug === slug || s.id === slug);
|
|
1389
|
+
if (!script) {
|
|
1390
|
+
return {
|
|
1391
|
+
content: [{ type: "text", text: `Script "${slug}" not found` }],
|
|
1392
|
+
isError: true
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
try {
|
|
1396
|
+
const result = await client.analyzeScript(script.id, { saveToScript });
|
|
1397
|
+
let text = `Script Analysis: ${script.name}
|
|
1398
|
+
|
|
1399
|
+
`;
|
|
1400
|
+
text += `Purpose: ${result.metadata.purpose || "(not detected)"}
|
|
1401
|
+
|
|
1402
|
+
`;
|
|
1403
|
+
if (result.metadata.triggers.length > 0) {
|
|
1404
|
+
text += `Triggers:
|
|
1405
|
+
`;
|
|
1406
|
+
for (const t of result.metadata.triggers) {
|
|
1407
|
+
text += ` - ${t.event}${t.selector ? ` on ${t.selector}` : ""}
|
|
1408
|
+
`;
|
|
1409
|
+
}
|
|
1410
|
+
text += "\n";
|
|
1411
|
+
}
|
|
1412
|
+
if (result.metadata.domTargets.length > 0) {
|
|
1413
|
+
text += `DOM Targets:
|
|
1414
|
+
`;
|
|
1415
|
+
for (const d of result.metadata.domTargets) {
|
|
1416
|
+
text += ` - ${d.selector} (${d.action})
|
|
1417
|
+
`;
|
|
1418
|
+
}
|
|
1419
|
+
text += "\n";
|
|
1420
|
+
}
|
|
1421
|
+
if (result.metadata.dependencies.length > 0) {
|
|
1422
|
+
text += `Dependencies:
|
|
1423
|
+
`;
|
|
1424
|
+
for (const dep of result.metadata.dependencies) {
|
|
1425
|
+
text += ` - ${dep.name}${dep.required ? " (required)" : ""}
|
|
1426
|
+
`;
|
|
1427
|
+
}
|
|
1428
|
+
text += "\n";
|
|
1429
|
+
}
|
|
1430
|
+
text += `Flags:
|
|
1431
|
+
`;
|
|
1432
|
+
text += ` - Modifies DOM: ${result.metadata.modifiesDom ? "Yes" : "No"}
|
|
1433
|
+
`;
|
|
1434
|
+
text += ` - Event listeners: ${result.metadata.addsEventListeners ? "Yes" : "No"}
|
|
1435
|
+
`;
|
|
1436
|
+
if (result.metadata.usesExternalApis) text += ` - Uses external APIs: Yes
|
|
1437
|
+
`;
|
|
1438
|
+
if (result.metadata.usesLocalStorage) text += ` - Uses localStorage: Yes
|
|
1439
|
+
`;
|
|
1440
|
+
if (result.metadata.usesCookies) text += ` - Uses cookies: Yes
|
|
1441
|
+
`;
|
|
1442
|
+
if (result.warnings.length > 0) {
|
|
1443
|
+
text += `
|
|
1444
|
+
Warnings:
|
|
1445
|
+
`;
|
|
1446
|
+
for (const w of result.warnings) {
|
|
1447
|
+
text += ` \u26A0\uFE0F ${w}
|
|
1448
|
+
`;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
if (result.suggestions.length > 0) {
|
|
1452
|
+
text += `
|
|
1453
|
+
Suggestions:
|
|
1454
|
+
`;
|
|
1455
|
+
for (const s of result.suggestions) {
|
|
1456
|
+
text += ` \u{1F4A1} ${s}
|
|
1457
|
+
`;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
if (saveToScript) {
|
|
1461
|
+
text += `
|
|
1462
|
+
\u2713 Metadata saved to script. Run kode_deploy to update CDN.`;
|
|
1463
|
+
} else {
|
|
1464
|
+
text += `
|
|
1465
|
+
Use saveToScript: true to save this metadata to the script.`;
|
|
1466
|
+
}
|
|
1467
|
+
return {
|
|
1468
|
+
content: [{ type: "text", text }]
|
|
1469
|
+
};
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1472
|
+
return {
|
|
1473
|
+
content: [{ type: "text", text: `Failed to analyze script: ${message}` }],
|
|
1474
|
+
isError: true
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
case "kode_get_script_metadata": {
|
|
1479
|
+
const { slug } = args;
|
|
1480
|
+
const scripts = await client.listScripts(siteId);
|
|
1481
|
+
const script = scripts.find((s) => s.slug === slug || s.id === slug);
|
|
1482
|
+
if (!script) {
|
|
1483
|
+
return {
|
|
1484
|
+
content: [{ type: "text", text: `Script "${slug}" not found` }],
|
|
1485
|
+
isError: true
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
try {
|
|
1489
|
+
const result = await client.getScriptMetadata(script.id);
|
|
1490
|
+
if (!result.metadata) {
|
|
1491
|
+
return {
|
|
1492
|
+
content: [{
|
|
1493
|
+
type: "text",
|
|
1494
|
+
text: `Script "${script.name}" has no metadata.
|
|
1495
|
+
|
|
1496
|
+
Use kode_analyze_script to generate metadata, or provide metadata when creating/updating the script.`
|
|
1497
|
+
}]
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
let text = `Metadata for: ${script.name}
|
|
1501
|
+
|
|
1502
|
+
`;
|
|
1503
|
+
if (result.metadata.purpose) {
|
|
1504
|
+
text += `Purpose: ${result.metadata.purpose}
|
|
1505
|
+
|
|
1506
|
+
`;
|
|
1507
|
+
}
|
|
1508
|
+
if (result.aiSummary) {
|
|
1509
|
+
text += `AI Summary: ${result.aiSummary}
|
|
1510
|
+
|
|
1511
|
+
`;
|
|
1512
|
+
}
|
|
1513
|
+
if (result.metadata.triggers.length > 0) {
|
|
1514
|
+
text += `Triggers:
|
|
1515
|
+
`;
|
|
1516
|
+
for (const t of result.metadata.triggers) {
|
|
1517
|
+
text += ` - ${t.event}${t.selector ? ` on ${t.selector}` : ""}
|
|
1518
|
+
`;
|
|
1519
|
+
}
|
|
1520
|
+
text += "\n";
|
|
1521
|
+
}
|
|
1522
|
+
if (result.metadata.domTargets.length > 0) {
|
|
1523
|
+
text += `DOM Targets:
|
|
1524
|
+
`;
|
|
1525
|
+
for (const d of result.metadata.domTargets) {
|
|
1526
|
+
text += ` - ${d.selector} (${d.action})
|
|
1527
|
+
`;
|
|
1528
|
+
}
|
|
1529
|
+
text += "\n";
|
|
1530
|
+
}
|
|
1531
|
+
if (result.metadata.dependencies.length > 0) {
|
|
1532
|
+
text += `Dependencies:
|
|
1533
|
+
`;
|
|
1534
|
+
for (const dep of result.metadata.dependencies) {
|
|
1535
|
+
text += ` - ${dep.name}${dep.required ? " (required)" : ""}
|
|
1536
|
+
`;
|
|
1537
|
+
}
|
|
1538
|
+
text += "\n";
|
|
1539
|
+
}
|
|
1540
|
+
text += `Flags:
|
|
1541
|
+
`;
|
|
1542
|
+
text += ` - AI Generated: ${result.aiGenerated ? "Yes" : "No"}
|
|
1543
|
+
`;
|
|
1544
|
+
text += ` - Modifies DOM: ${result.metadata.modifiesDom ? "Yes" : "No"}
|
|
1545
|
+
`;
|
|
1546
|
+
text += ` - Event listeners: ${result.metadata.addsEventListeners ? "Yes" : "No"}
|
|
1547
|
+
`;
|
|
1548
|
+
if (result.lastAnalyzed) {
|
|
1549
|
+
text += `
|
|
1550
|
+
Last analyzed: ${result.lastAnalyzed}`;
|
|
1551
|
+
}
|
|
1552
|
+
return {
|
|
1553
|
+
content: [{ type: "text", text }]
|
|
1554
|
+
};
|
|
1555
|
+
} catch (error) {
|
|
1556
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1557
|
+
return {
|
|
1558
|
+
content: [{ type: "text", text: `Failed to get metadata: ${message}` }],
|
|
1559
|
+
isError: true
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1005
1563
|
case "kode_read_context": {
|
|
1006
1564
|
const contextPath = getContextPath();
|
|
1007
1565
|
if (!contextPath) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@curenorway/kode-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "MCP server for Cure Kode - enables AI agents to manage Webflow scripts",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "MCP server for Cure Kode CDN - enables AI agents to manage, deploy, and analyze Webflow scripts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cure-kode-mcp": "./dist/index.js"
|