@curenorway/kode-mcp 1.4.0 → 1.5.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.
Files changed (2) hide show
  1. package/dist/index.js +507 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -70,6 +70,26 @@ var KodeApiClient = class {
70
70
  async listPages(siteId) {
71
71
  return this.request(`/api/cdn/sites/${siteId}/pages`);
72
72
  }
73
+ async getPage(pageId) {
74
+ return this.request(`/api/cdn/pages/${pageId}`);
75
+ }
76
+ async createPage(siteId, data) {
77
+ return this.request(`/api/cdn/sites/${siteId}/pages`, {
78
+ method: "POST",
79
+ body: JSON.stringify(data)
80
+ });
81
+ }
82
+ async updatePage(pageId, data) {
83
+ return this.request(`/api/cdn/pages/${pageId}`, {
84
+ method: "PATCH",
85
+ body: JSON.stringify(data)
86
+ });
87
+ }
88
+ async deletePage(pageId) {
89
+ return this.request(`/api/cdn/pages/${pageId}`, {
90
+ method: "DELETE"
91
+ });
92
+ }
73
93
  async assignScriptToPage(pageId, scriptId, loadOrderOverride) {
74
94
  await this.request(`/api/cdn/pages/${pageId}/scripts`, {
75
95
  method: "POST",
@@ -81,6 +101,9 @@ var KodeApiClient = class {
81
101
  method: "DELETE"
82
102
  });
83
103
  }
104
+ async getPageScripts(pageId) {
105
+ return this.request(`/api/cdn/pages/${pageId}/scripts`);
106
+ }
84
107
  // Deployment operations
85
108
  async deploy(siteId, options = {}) {
86
109
  return this.request("/api/cdn/deploy", {
@@ -205,6 +228,9 @@ function getConfig() {
205
228
  function hasConfig() {
206
229
  return getConfig() !== void 0;
207
230
  }
231
+ function getProjectRoot() {
232
+ return findProjectRoot();
233
+ }
208
234
  function getScriptsDir() {
209
235
  const projectRoot = findProjectRoot();
210
236
  const projectConfig = loadProjectConfig();
@@ -492,13 +518,109 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
492
518
  },
493
519
  {
494
520
  name: "kode_list_pages",
495
- description: "List all page definitions for the site. Pages define URL patterns for page-specific scripts.",
521
+ description: "List all CDN page definitions for the site. Pages define URL patterns for page-specific scripts.",
496
522
  inputSchema: {
497
523
  type: "object",
498
524
  properties: {},
499
525
  required: []
500
526
  }
501
527
  },
528
+ {
529
+ name: "kode_create_page",
530
+ description: "Create a new CDN page with URL patterns. Pages are used to load page-specific scripts on matching URLs.",
531
+ inputSchema: {
532
+ type: "object",
533
+ properties: {
534
+ name: {
535
+ type: "string",
536
+ description: 'Page name (e.g., "About Page", "Contact")'
537
+ },
538
+ urlPatterns: {
539
+ type: "array",
540
+ items: { type: "string" },
541
+ description: 'URL patterns that match this page (e.g., ["/about", "/about/*"])'
542
+ },
543
+ patternType: {
544
+ type: "string",
545
+ enum: ["exact", "prefix", "wildcard", "regex"],
546
+ description: "How to match URL patterns. Default: prefix"
547
+ },
548
+ priority: {
549
+ type: "number",
550
+ description: "Priority for matching (higher = checked first). Default: 0"
551
+ },
552
+ parentSlug: {
553
+ type: "string",
554
+ description: "Optional parent page slug for nesting"
555
+ }
556
+ },
557
+ required: ["name", "urlPatterns"]
558
+ }
559
+ },
560
+ {
561
+ name: "kode_update_page",
562
+ description: "Update an existing CDN page. Can modify name, URL patterns, pattern type, priority, or parent.",
563
+ inputSchema: {
564
+ type: "object",
565
+ properties: {
566
+ pageSlug: {
567
+ type: "string",
568
+ description: "Page slug or name to update"
569
+ },
570
+ name: {
571
+ type: "string",
572
+ description: "New page name"
573
+ },
574
+ urlPatterns: {
575
+ type: "array",
576
+ items: { type: "string" },
577
+ description: "New URL patterns"
578
+ },
579
+ patternType: {
580
+ type: "string",
581
+ enum: ["exact", "prefix", "wildcard", "regex"],
582
+ description: "New pattern matching type"
583
+ },
584
+ priority: {
585
+ type: "number",
586
+ description: "New priority"
587
+ },
588
+ parentSlug: {
589
+ type: "string",
590
+ description: "New parent page slug, or null to make root-level"
591
+ }
592
+ },
593
+ required: ["pageSlug"]
594
+ }
595
+ },
596
+ {
597
+ name: "kode_delete_page",
598
+ description: "Delete a CDN page. Also removes all script assignments for that page.",
599
+ inputSchema: {
600
+ type: "object",
601
+ properties: {
602
+ pageSlug: {
603
+ type: "string",
604
+ description: "Page slug or name to delete"
605
+ }
606
+ },
607
+ required: ["pageSlug"]
608
+ }
609
+ },
610
+ {
611
+ name: "kode_get_page_scripts",
612
+ description: "List all scripts assigned to a specific page.",
613
+ inputSchema: {
614
+ type: "object",
615
+ properties: {
616
+ pageSlug: {
617
+ type: "string",
618
+ description: "Page slug or name to get scripts for"
619
+ }
620
+ },
621
+ required: ["pageSlug"]
622
+ }
623
+ },
502
624
  {
503
625
  name: "kode_assign_script_to_page",
504
626
  description: "Assign a page-specific script to a page. Required for page-specific scripts to load on their target pages.",
@@ -696,6 +818,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
696
818
  properties: {},
697
819
  required: []
698
820
  }
821
+ },
822
+ {
823
+ name: "kode_sync_documentation",
824
+ description: "Sync script documentation. Pulls latest script metadata from the API and updates .cure-kode/KODE.md with current scripts and pages. Also ensures CLAUDE.md has a reference to KODE.md.",
825
+ inputSchema: {
826
+ type: "object",
827
+ properties: {
828
+ updateClaudeMd: {
829
+ type: "boolean",
830
+ description: "Update KODE.md and ensure CLAUDE.md has reference. Default: true"
831
+ }
832
+ },
833
+ required: []
834
+ }
699
835
  }
700
836
  ]
701
837
  };
@@ -1248,25 +1384,216 @@ Webflow Components:
1248
1384
  const pages = await client.listPages(siteId);
1249
1385
  if (pages.length === 0) {
1250
1386
  return {
1251
- content: [{ type: "text", text: 'No pages defined. All scripts with scope "global" will load on all pages.' }]
1387
+ content: [{ type: "text", text: 'No pages defined. All scripts with scope "global" will load on all pages.\n\nUse kode_create_page to add page definitions for page-specific scripts.' }]
1252
1388
  };
1253
1389
  }
1254
- const formatted = pages.map((p) => ({
1255
- name: p.name,
1256
- slug: p.slug,
1257
- patterns: p.url_patterns,
1258
- patternType: p.pattern_type,
1259
- priority: p.priority
1260
- }));
1390
+ const rootPages = pages.filter((p) => !p.parent_id);
1391
+ const childPages = pages.filter((p) => p.parent_id);
1392
+ const formatPage = (p, indent = 0) => {
1393
+ const prefix = " ".repeat(indent);
1394
+ let text2 = `${prefix}${p.name} (${p.slug})
1395
+ `;
1396
+ text2 += `${prefix} Patterns: ${p.url_patterns.join(", ")} [${p.pattern_type}]
1397
+ `;
1398
+ text2 += `${prefix} Priority: ${p.priority}
1399
+ `;
1400
+ const children = childPages.filter((c) => c.parent_id === p.id);
1401
+ for (const child of children) {
1402
+ text2 += formatPage(child, indent + 1);
1403
+ }
1404
+ return text2;
1405
+ };
1406
+ let text = `CDN Pages (${pages.length}):
1407
+
1408
+ `;
1409
+ for (const page of rootPages) {
1410
+ text += formatPage(page);
1411
+ text += "\n";
1412
+ }
1261
1413
  return {
1262
- content: [
1263
- {
1264
- type: "text",
1265
- text: JSON.stringify(formatted, null, 2)
1266
- }
1267
- ]
1414
+ content: [{ type: "text", text }]
1268
1415
  };
1269
1416
  }
1417
+ case "kode_create_page": {
1418
+ const { name: name2, urlPatterns, patternType, priority, parentSlug } = args;
1419
+ let parentId;
1420
+ if (parentSlug) {
1421
+ const pages = await client.listPages(siteId);
1422
+ const parentPage = pages.find((p) => p.slug === parentSlug || p.name === parentSlug);
1423
+ if (!parentPage) {
1424
+ return {
1425
+ content: [{ type: "text", text: `Parent page "${parentSlug}" not found` }],
1426
+ isError: true
1427
+ };
1428
+ }
1429
+ parentId = parentPage.id;
1430
+ }
1431
+ try {
1432
+ const page = await client.createPage(siteId, {
1433
+ name: name2,
1434
+ urlPatterns,
1435
+ patternType: patternType || "prefix",
1436
+ priority: priority || 0,
1437
+ parentId
1438
+ });
1439
+ let text = `Page created: ${page.name} (${page.slug})
1440
+
1441
+ `;
1442
+ text += `URL Patterns: ${page.url_patterns.join(", ")}
1443
+ `;
1444
+ text += `Pattern Type: ${page.pattern_type}
1445
+ `;
1446
+ text += `Priority: ${page.priority}
1447
+ `;
1448
+ if (parentId) {
1449
+ text += `Parent: ${parentSlug}
1450
+ `;
1451
+ }
1452
+ text += "\nUse kode_assign_script_to_page to add scripts to this page.";
1453
+ return {
1454
+ content: [{ type: "text", text }]
1455
+ };
1456
+ } catch (error) {
1457
+ const message = error instanceof Error ? error.message : "Unknown error";
1458
+ return {
1459
+ content: [{ type: "text", text: `Failed to create page: ${message}` }],
1460
+ isError: true
1461
+ };
1462
+ }
1463
+ }
1464
+ case "kode_update_page": {
1465
+ const { pageSlug, name: name2, urlPatterns, patternType, priority, parentSlug } = args;
1466
+ const pages = await client.listPages(siteId);
1467
+ const page = pages.find((p) => p.slug === pageSlug || p.name === pageSlug || p.id === pageSlug);
1468
+ if (!page) {
1469
+ return {
1470
+ content: [{ type: "text", text: `Page "${pageSlug}" not found` }],
1471
+ isError: true
1472
+ };
1473
+ }
1474
+ const updates = {};
1475
+ if (name2 !== void 0) updates.name = name2;
1476
+ if (urlPatterns !== void 0) updates.urlPatterns = urlPatterns;
1477
+ if (patternType !== void 0) updates.patternType = patternType;
1478
+ if (priority !== void 0) updates.priority = priority;
1479
+ if (parentSlug === null) {
1480
+ updates.parentId = null;
1481
+ } else if (parentSlug !== void 0) {
1482
+ const parentPage = pages.find((p) => p.slug === parentSlug || p.name === parentSlug);
1483
+ if (!parentPage) {
1484
+ return {
1485
+ content: [{ type: "text", text: `Parent page "${parentSlug}" not found` }],
1486
+ isError: true
1487
+ };
1488
+ }
1489
+ updates.parentId = parentPage.id;
1490
+ }
1491
+ if (Object.keys(updates).length === 0) {
1492
+ return {
1493
+ content: [{ type: "text", text: "No updates specified. Provide at least one field to update." }],
1494
+ isError: true
1495
+ };
1496
+ }
1497
+ try {
1498
+ const updated = await client.updatePage(page.id, updates);
1499
+ let text = `Page updated: ${updated.name} (${updated.slug})
1500
+
1501
+ `;
1502
+ text += `URL Patterns: ${updated.url_patterns.join(", ")}
1503
+ `;
1504
+ text += `Pattern Type: ${updated.pattern_type}
1505
+ `;
1506
+ text += `Priority: ${updated.priority}
1507
+ `;
1508
+ text += "\nRun kode_deploy to make changes live.";
1509
+ return {
1510
+ content: [{ type: "text", text }]
1511
+ };
1512
+ } catch (error) {
1513
+ const message = error instanceof Error ? error.message : "Unknown error";
1514
+ return {
1515
+ content: [{ type: "text", text: `Failed to update page: ${message}` }],
1516
+ isError: true
1517
+ };
1518
+ }
1519
+ }
1520
+ case "kode_delete_page": {
1521
+ const { pageSlug } = args;
1522
+ const pages = await client.listPages(siteId);
1523
+ const page = pages.find((p) => p.slug === pageSlug || p.name === pageSlug || p.id === pageSlug);
1524
+ if (!page) {
1525
+ return {
1526
+ content: [{ type: "text", text: `Page "${pageSlug}" not found` }],
1527
+ isError: true
1528
+ };
1529
+ }
1530
+ try {
1531
+ await client.deletePage(page.id);
1532
+ return {
1533
+ content: [
1534
+ {
1535
+ type: "text",
1536
+ text: `Page deleted: ${page.name} (${page.slug})
1537
+
1538
+ All script assignments for this page have been removed.
1539
+ Run kode_deploy to make changes live.`
1540
+ }
1541
+ ]
1542
+ };
1543
+ } catch (error) {
1544
+ const message = error instanceof Error ? error.message : "Unknown error";
1545
+ return {
1546
+ content: [{ type: "text", text: `Failed to delete page: ${message}` }],
1547
+ isError: true
1548
+ };
1549
+ }
1550
+ }
1551
+ case "kode_get_page_scripts": {
1552
+ const { pageSlug } = args;
1553
+ const pages = await client.listPages(siteId);
1554
+ const page = pages.find((p) => p.slug === pageSlug || p.name === pageSlug || p.id === pageSlug);
1555
+ if (!page) {
1556
+ return {
1557
+ content: [{ type: "text", text: `Page "${pageSlug}" not found` }],
1558
+ isError: true
1559
+ };
1560
+ }
1561
+ try {
1562
+ const assignments = await client.getPageScripts(page.id);
1563
+ if (assignments.length === 0) {
1564
+ return {
1565
+ content: [
1566
+ {
1567
+ type: "text",
1568
+ text: `No scripts assigned to page "${page.name}".
1569
+
1570
+ Use kode_assign_script_to_page to add scripts.`
1571
+ }
1572
+ ]
1573
+ };
1574
+ }
1575
+ let text = `Scripts on "${page.name}" (${page.url_patterns.join(", ")}):
1576
+
1577
+ `;
1578
+ for (const assignment of assignments) {
1579
+ const script = assignment.script;
1580
+ const type = script.type === "javascript" ? "JS" : "CSS";
1581
+ const loadOrder = assignment.load_order_override !== null ? ` [order: ${assignment.load_order_override}]` : "";
1582
+ const enabled = assignment.is_enabled ? "" : " (disabled)";
1583
+ text += ` [${type}] ${script.name} (${script.slug})${loadOrder}${enabled}
1584
+ `;
1585
+ }
1586
+ return {
1587
+ content: [{ type: "text", text }]
1588
+ };
1589
+ } catch (error) {
1590
+ const message = error instanceof Error ? error.message : "Unknown error";
1591
+ return {
1592
+ content: [{ type: "text", text: `Failed to get page scripts: ${message}` }],
1593
+ isError: true
1594
+ };
1595
+ }
1596
+ }
1270
1597
  case "kode_assign_script_to_page": {
1271
1598
  const { scriptSlug, pageSlug, loadOrderOverride } = args;
1272
1599
  const scripts = await client.listScripts(siteId);
@@ -2184,6 +2511,171 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
2184
2511
  content: [{ type: "text", text }]
2185
2512
  };
2186
2513
  }
2514
+ case "kode_sync_documentation": {
2515
+ const { updateClaudeMd = true } = args;
2516
+ const config = getConfig();
2517
+ if (!config) {
2518
+ return {
2519
+ content: [{ type: "text", text: 'Cure Kode not configured. Run "kode init" first.' }],
2520
+ isError: true
2521
+ };
2522
+ }
2523
+ const projectRoot = getProjectRoot();
2524
+ if (!projectRoot) {
2525
+ return {
2526
+ content: [{ type: "text", text: "Not in a Cure Kode project." }],
2527
+ isError: true
2528
+ };
2529
+ }
2530
+ try {
2531
+ const scripts = await client.listScripts(siteId);
2532
+ const pages = await client.listPages(siteId);
2533
+ let text = `Documentation synced:
2534
+
2535
+ `;
2536
+ text += `Scripts: ${scripts.length}
2537
+ `;
2538
+ text += `Pages: ${pages.length}
2539
+
2540
+ `;
2541
+ if (scripts.length > 0) {
2542
+ text += `## Scripts
2543
+
2544
+ `;
2545
+ for (const s of scripts) {
2546
+ const type = s.type === "javascript" ? "JS" : "CSS";
2547
+ const scope = s.scope === "global" ? "Global" : "Page";
2548
+ const purpose = s.metadata?.purpose || s.description || "(no description)";
2549
+ text += `- **${s.slug}** [${type}, ${scope}]: ${purpose}
2550
+ `;
2551
+ }
2552
+ text += "\n";
2553
+ }
2554
+ if (pages.length > 0) {
2555
+ text += `## Pages
2556
+
2557
+ `;
2558
+ for (const p of pages) {
2559
+ text += `- **${p.name}** (${p.slug}): ${p.url_patterns.join(", ")}
2560
+ `;
2561
+ }
2562
+ text += "\n";
2563
+ }
2564
+ if (updateClaudeMd) {
2565
+ const kodeMdPath = path2.join(projectRoot, ".cure-kode", "KODE.md");
2566
+ const claudeMdPath = path2.join(projectRoot, "CLAUDE.md");
2567
+ const siteName = config.siteName || "Site";
2568
+ const siteSlug = config.siteSlug || "site";
2569
+ let kodeMd = `# Cure Kode: ${siteName}
2570
+
2571
+ `;
2572
+ kodeMd += `This project uses **Cure Kode** CDN for JavaScript/CSS delivery to Webflow.
2573
+
2574
+ `;
2575
+ kodeMd += `> **This file is auto-generated.** Do not edit manually.
2576
+
2577
+ ---
2578
+
2579
+ `;
2580
+ kodeMd += `## CDN URL
2581
+
2582
+ \`\`\`html
2583
+ <script defer src="https://app.cure.no/api/cdn/${siteSlug}/init.js"></script>
2584
+ \`\`\`
2585
+
2586
+ ---
2587
+
2588
+ `;
2589
+ kodeMd += `## Scripts
2590
+
2591
+ `;
2592
+ if (scripts.length > 0) {
2593
+ kodeMd += `| Script | Type | Scope | Auto | Purpose |
2594
+ `;
2595
+ kodeMd += `|--------|------|-------|------|--------|
2596
+ `;
2597
+ for (const s of scripts) {
2598
+ const type = s.type === "javascript" ? "JS" : "CSS";
2599
+ const scope = s.scope === "global" ? "Global" : "Page";
2600
+ const auto = s.auto_load ? "\u26A1" : "\u25CB";
2601
+ let purpose = s.metadata?.purpose || s.description || "\u2014";
2602
+ if (purpose.length > 50) purpose = purpose.slice(0, 47) + "...";
2603
+ kodeMd += `| \`${s.slug}\` | ${type} | ${scope} | ${auto} | ${purpose} |
2604
+ `;
2605
+ }
2606
+ kodeMd += `
2607
+ > \u26A1 = auto-loads, \u25CB = manual via \`CK.loadScript()\`
2608
+
2609
+ `;
2610
+ } else {
2611
+ kodeMd += `No scripts yet. Create one with \`kode push\`.
2612
+
2613
+ `;
2614
+ }
2615
+ if (pages.length > 0) {
2616
+ kodeMd += `---
2617
+
2618
+ ## Pages
2619
+
2620
+ `;
2621
+ for (const p of pages) {
2622
+ kodeMd += `- **${p.name}** (\`${p.slug}\`) \u2192 \`${p.url_patterns.join("`, `")}\`
2623
+ `;
2624
+ }
2625
+ kodeMd += "\n";
2626
+ }
2627
+ kodeMd += `---
2628
+
2629
+ ## Workflow
2630
+
2631
+ `;
2632
+ kodeMd += `**Before changes:** \`kode pull\`
2633
+ `;
2634
+ kodeMd += `**After changes:** \`kode push\` then \`kode deploy\`
2635
+ `;
2636
+ kodeMd += `**Check for updates:** \`kode doctor\`
2637
+ `;
2638
+ fs2.writeFileSync(kodeMdPath, kodeMd);
2639
+ text += `Updated .cure-kode/KODE.md with current scripts and pages.
2640
+ `;
2641
+ const kodeReference = `## Cure Kode
2642
+
2643
+ This project uses **Cure Kode** for JS/CSS delivery. **Before modifying scripts:**
2644
+
2645
+ 1. Run \`kode pull\` to get latest from server
2646
+ 2. Make your changes in \`.cure-kode-scripts/\`
2647
+ 3. Run \`kode push\` to upload, then \`kode deploy\` to publish
2648
+
2649
+ Full documentation: [.cure-kode/KODE.md](.cure-kode/KODE.md)
2650
+
2651
+ ---
2652
+
2653
+ `;
2654
+ if (fs2.existsSync(claudeMdPath)) {
2655
+ const claudeContent = fs2.readFileSync(claudeMdPath, "utf-8");
2656
+ const hasReference = claudeContent.includes("KODE.md") || claudeContent.includes(".cure-kode/KODE.md");
2657
+ if (!hasReference) {
2658
+ fs2.writeFileSync(claudeMdPath, kodeReference + claudeContent);
2659
+ text += `Added Kode reference to CLAUDE.md.
2660
+ `;
2661
+ }
2662
+ } else {
2663
+ fs2.writeFileSync(claudeMdPath, kodeReference);
2664
+ text += `Created CLAUDE.md with Kode reference.
2665
+ `;
2666
+ }
2667
+ }
2668
+ return {
2669
+ content: [{ type: "text", text }]
2670
+ };
2671
+ } catch (error) {
2672
+ const message = error instanceof Error ? error.message : "Unknown error";
2673
+ return {
2674
+ content: [{ type: "text", text: `Failed to sync documentation: ${message}` }],
2675
+ isError: true
2676
+ };
2677
+ }
2678
+ }
2187
2679
  default:
2188
2680
  return {
2189
2681
  content: [{ type: "text", text: `Unknown tool: ${name}` }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curenorway/kode-mcp",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
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": {