@curenorway/kode-mcp 1.1.0 → 1.3.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 +568 -45
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -105,6 +105,16 @@ var KodeApiClient = class {
105
105
  async getDeploymentStatus(siteId) {
106
106
  return this.request(`/api/cdn/sites/${siteId}/deployments/status`);
107
107
  }
108
+ // v2.3: Production enabled toggle
109
+ async setProductionEnabled(siteId, enabled, productionDomain) {
110
+ return this.request(`/api/cdn/sites/${siteId}/production`, {
111
+ method: "POST",
112
+ body: JSON.stringify({
113
+ enabled,
114
+ productionDomain
115
+ })
116
+ });
117
+ }
108
118
  // HTML operations
109
119
  async fetchHtml(siteId, url) {
110
120
  return this.request("/api/cdn/fetch-html", {
@@ -121,6 +131,16 @@ var KodeApiClient = class {
121
131
  return false;
122
132
  }
123
133
  }
134
+ // Metadata operations
135
+ async analyzeScript(scriptId, options = {}) {
136
+ return this.request(`/api/cdn/scripts/${scriptId}/analyze`, {
137
+ method: "POST",
138
+ body: JSON.stringify(options)
139
+ });
140
+ }
141
+ async getScriptMetadata(scriptId) {
142
+ return this.request(`/api/cdn/scripts/${scriptId}/metadata`);
143
+ }
124
144
  };
125
145
 
126
146
  // src/config.ts
@@ -252,13 +272,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
252
272
  },
253
273
  {
254
274
  name: "kode_get_script",
255
- description: "Get the full content of a specific script by its slug (filename without extension) or ID.",
275
+ description: "Get a script by its slug or ID. By default returns full content. Use includeContent:false to get just metadata (saves context tokens for large scripts).",
256
276
  inputSchema: {
257
277
  type: "object",
258
278
  properties: {
259
279
  slug: {
260
280
  type: "string",
261
281
  description: 'Script slug (e.g., "init", "tracking") or script ID (UUID)'
282
+ },
283
+ includeContent: {
284
+ type: "boolean",
285
+ description: "Include full script content. Default: true. Set to false to get just metadata (name, type, scope, version, size) - useful for large scripts."
286
+ },
287
+ contentPreview: {
288
+ type: "number",
289
+ description: "If set, include only the first N characters of content. Useful for previewing large scripts without loading everything."
262
290
  }
263
291
  },
264
292
  required: ["slug"]
@@ -266,7 +294,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
266
294
  },
267
295
  {
268
296
  name: "kode_create_script",
269
- description: "Create a new script on the Cure Kode CDN. The script will be available at the CDN URL after deployment. Global scripts auto-load by default; page-specific scripts do not.",
297
+ 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.",
270
298
  inputSchema: {
271
299
  type: "object",
272
300
  properties: {
@@ -279,10 +307,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
279
307
  enum: ["javascript", "css"],
280
308
  description: "Script type"
281
309
  },
282
- content: {
283
- type: "string",
284
- description: "Script content (JavaScript or CSS code)"
285
- },
286
310
  scope: {
287
311
  type: "string",
288
312
  enum: ["global", "page-specific"],
@@ -290,15 +314,37 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
290
314
  },
291
315
  autoLoad: {
292
316
  type: "boolean",
293
- description: "Whether to auto-load the script. Default: true for global scripts, false for page-specific. Set to false for scripts you want to load manually via CK.loadScript()."
317
+ description: "Whether to auto-load the script. Default: true for global scripts, false for page-specific."
318
+ },
319
+ purpose: {
320
+ type: "string",
321
+ description: "Brief description of what the script does (for metadata)"
294
322
  }
295
323
  },
296
- required: ["name", "type", "content"]
324
+ required: ["name", "type"]
325
+ }
326
+ },
327
+ {
328
+ name: "kode_push",
329
+ 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.",
330
+ inputSchema: {
331
+ type: "object",
332
+ properties: {
333
+ scriptSlug: {
334
+ type: "string",
335
+ description: 'Specific script slug to push (e.g., "map"). If omitted, pushes all changed scripts.'
336
+ },
337
+ force: {
338
+ type: "boolean",
339
+ description: "Force push even if content appears unchanged. Default: false"
340
+ }
341
+ },
342
+ required: []
297
343
  }
298
344
  },
299
345
  {
300
346
  name: "kode_update_script",
301
- description: "Update an existing script's content or settings. This creates a new version of the script when content changes.",
347
+ 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.",
302
348
  inputSchema: {
303
349
  type: "object",
304
350
  properties: {
@@ -306,10 +352,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
306
352
  type: "string",
307
353
  description: "Script slug or ID to update"
308
354
  },
309
- content: {
310
- type: "string",
311
- description: "New script content"
312
- },
313
355
  autoLoad: {
314
356
  type: "boolean",
315
357
  description: "Whether to auto-load the script. Set to true to load automatically, false for manual loading via CK.loadScript()."
@@ -319,9 +361,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
319
361
  enum: ["global", "page-specific"],
320
362
  description: "Change script scope. Note: changing to page-specific requires assigning to pages."
321
363
  },
322
- changeSummary: {
364
+ purpose: {
323
365
  type: "string",
324
- description: "Brief description of the changes (for version history)"
366
+ description: "Update the script purpose description (shown in Chrome Extension)"
367
+ },
368
+ regenerateSummary: {
369
+ type: "boolean",
370
+ description: "Re-analyze the script content and regenerate the AI summary. Useful after significant changes."
325
371
  }
326
372
  },
327
373
  required: ["slug"]
@@ -371,7 +417,30 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
371
417
  },
372
418
  {
373
419
  name: "kode_status",
374
- description: "Get the current deployment status for both staging and production environments.",
420
+ description: "Get the current deployment status including production enabled state, staging and production environments.",
421
+ inputSchema: {
422
+ type: "object",
423
+ properties: {},
424
+ required: []
425
+ }
426
+ },
427
+ {
428
+ name: "kode_production_enable",
429
+ description: "Enable production environment for this site. Required before promoting to production. Sites start in staging-only mode by default.",
430
+ inputSchema: {
431
+ type: "object",
432
+ properties: {
433
+ productionDomain: {
434
+ type: "string",
435
+ description: 'Production domain (e.g., "example.com"). Optional - can be set later.'
436
+ }
437
+ },
438
+ required: []
439
+ }
440
+ },
441
+ {
442
+ name: "kode_production_disable",
443
+ description: "Disable production environment for this site. Production requests will return an empty script. Useful during development when only staging should be active.",
375
444
  inputSchema: {
376
445
  type: "object",
377
446
  properties: {},
@@ -450,6 +519,38 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
450
519
  required: []
451
520
  }
452
521
  },
522
+ {
523
+ name: "kode_analyze_script",
524
+ 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.",
525
+ inputSchema: {
526
+ type: "object",
527
+ properties: {
528
+ slug: {
529
+ type: "string",
530
+ description: "Script slug or ID to analyze"
531
+ },
532
+ saveToScript: {
533
+ type: "boolean",
534
+ description: "Save the generated metadata to the script. Default: false (preview only)"
535
+ }
536
+ },
537
+ required: ["slug"]
538
+ }
539
+ },
540
+ {
541
+ name: "kode_get_script_metadata",
542
+ description: "Get the metadata for a script, including detected DOM targets, triggers, dependencies, and AI summary. Useful for Chrome Extension visibility.",
543
+ inputSchema: {
544
+ type: "object",
545
+ properties: {
546
+ slug: {
547
+ type: "string",
548
+ description: "Script slug or ID to get metadata for"
549
+ }
550
+ },
551
+ required: ["slug"]
552
+ }
553
+ },
453
554
  {
454
555
  name: "kode_read_context",
455
556
  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.",
@@ -597,7 +698,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
597
698
  };
598
699
  }
599
700
  case "kode_get_script": {
600
- const { slug } = args;
701
+ const { slug, includeContent = true, contentPreview } = args;
601
702
  const scripts = await client.listScripts(siteId);
602
703
  const script = scripts.find((s) => s.slug === slug || s.id === slug);
603
704
  if (!script) {
@@ -606,6 +707,44 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
606
707
  isError: true
607
708
  };
608
709
  }
710
+ const contentLength = script.content?.length || 0;
711
+ const estimatedTokens = Math.ceil(contentLength / 4);
712
+ if (!includeContent && !contentPreview) {
713
+ return {
714
+ content: [
715
+ {
716
+ type: "text",
717
+ text: `Script: ${script.name}
718
+ Slug: ${script.slug}
719
+ Type: ${script.type}
720
+ Scope: ${script.scope}
721
+ Version: ${script.current_version}
722
+ Auto-load: ${script.auto_load ? "yes" : "no"}
723
+ Size: ${contentLength} chars (~${estimatedTokens} tokens)
724
+
725
+ Use includeContent:true or contentPreview:N to fetch content.`
726
+ }
727
+ ]
728
+ };
729
+ }
730
+ if (contentPreview && contentPreview > 0) {
731
+ const preview = script.content?.slice(0, contentPreview) || "";
732
+ const isTruncated = contentLength > contentPreview;
733
+ return {
734
+ content: [
735
+ {
736
+ type: "text",
737
+ text: `// Script: ${script.name} (${script.type})
738
+ // Version: ${script.current_version}
739
+ // Scope: ${script.scope}
740
+ // Size: ${contentLength} chars (~${estimatedTokens} tokens)${isTruncated ? `
741
+ // Showing first ${contentPreview} chars` : ""}
742
+
743
+ ${preview}${isTruncated ? "\n\n// ... (truncated) - use includeContent:true for full content" : ""}`
744
+ }
745
+ ]
746
+ };
747
+ }
609
748
  return {
610
749
  content: [
611
750
  {
@@ -613,6 +752,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
613
752
  text: `// Script: ${script.name} (${script.type})
614
753
  // Version: ${script.current_version}
615
754
  // Scope: ${script.scope}
755
+ // Size: ${contentLength} chars (~${estimatedTokens} tokens)
616
756
 
617
757
  ${script.content}`
618
758
  }
@@ -620,35 +760,155 @@ ${script.content}`
620
760
  };
621
761
  }
622
762
  case "kode_create_script": {
623
- const { name: scriptName, type, content, scope, autoLoad } = args;
763
+ const { name: scriptName, type, scope, autoLoad, purpose } = args;
624
764
  const scriptScope = scope || "global";
625
765
  const script = await client.createScript(siteId, {
626
766
  name: scriptName,
627
767
  slug: scriptName,
628
768
  type,
629
- content,
769
+ content: "",
770
+ // Empty - content comes from local files via kode_push
630
771
  scope: scriptScope,
631
- autoLoad
632
- // Let API handle default based on scope
772
+ autoLoad,
773
+ metadata: purpose ? { purpose } : void 0
633
774
  });
634
- const autoLoadStatus = script.auto_load ? "will auto-load" : "manual load only (use CK.loadScript())";
775
+ const scriptsDir = getScriptsDir();
776
+ const ext = type === "javascript" ? "js" : "css";
777
+ const localPath = scriptsDir ? `${scriptsDir}/${scriptName}.${ext}` : `.cure-kode-scripts/${scriptName}.${ext}`;
778
+ let responseText = `Created script "${script.name}" (${script.type})`;
779
+ responseText += `
780
+ Slug: ${script.slug}`;
781
+ responseText += `
782
+ Scope: ${script.scope}`;
783
+ responseText += `
784
+ Auto-load: ${script.auto_load ? "yes" : "no"}`;
785
+ if (purpose) responseText += `
786
+ Purpose: ${purpose}`;
787
+ responseText += `
788
+
789
+ Next steps:`;
790
+ responseText += `
791
+ 1. Create file: ${localPath}`;
792
+ responseText += `
793
+ 2. Write your ${type} code`;
794
+ responseText += `
795
+ 3. Run kode_push to upload content`;
796
+ responseText += `
797
+ 4. Run kode_deploy to make it live`;
635
798
  return {
636
799
  content: [
637
800
  {
638
801
  type: "text",
639
- text: `Created script "${script.name}" (${script.type})
640
- Slug: ${script.slug}
641
- Scope: ${script.scope}
642
- Auto-load: ${autoLoadStatus}
643
- Version: ${script.current_version}
644
-
645
- Note: Run kode_deploy to make it live.`
802
+ text: responseText
646
803
  }
647
804
  ]
648
805
  };
649
806
  }
807
+ case "kode_push": {
808
+ const { scriptSlug, force } = args;
809
+ const scriptsDir = getScriptsDir();
810
+ if (!scriptsDir || !fs2.existsSync(scriptsDir)) {
811
+ return {
812
+ content: [{
813
+ type: "text",
814
+ text: 'No .cure-kode-scripts/ folder found. Run "kode init" first or create the folder manually.'
815
+ }],
816
+ isError: true
817
+ };
818
+ }
819
+ const remoteScripts = await client.listScripts(siteId);
820
+ const localFiles = fs2.readdirSync(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css"));
821
+ if (scriptSlug) {
822
+ const ext = remoteScripts.find((s) => s.slug === scriptSlug)?.type === "css" ? "css" : "js";
823
+ const fileName = `${scriptSlug}.${ext}`;
824
+ const filePath = path2.join(scriptsDir, fileName);
825
+ if (!fs2.existsSync(filePath)) {
826
+ return {
827
+ content: [{
828
+ type: "text",
829
+ text: `File not found: ${filePath}
830
+
831
+ Available files: ${localFiles.join(", ") || "(none)"}`
832
+ }],
833
+ isError: true
834
+ };
835
+ }
836
+ const content = fs2.readFileSync(filePath, "utf-8");
837
+ const remote = remoteScripts.find((s) => s.slug === scriptSlug);
838
+ if (!remote) {
839
+ return {
840
+ content: [{
841
+ type: "text",
842
+ text: `Script "${scriptSlug}" not found on server. Create it first with kode_create_script.`
843
+ }],
844
+ isError: true
845
+ };
846
+ }
847
+ if (!force && remote.content === content) {
848
+ return {
849
+ content: [{
850
+ type: "text",
851
+ text: `Script "${scriptSlug}" is already up to date (${content.length} chars). Use force: true to push anyway.`
852
+ }]
853
+ };
854
+ }
855
+ const updated = await client.updateScript(remote.id, {
856
+ content,
857
+ changeSummary: "Pushed via MCP"
858
+ });
859
+ return {
860
+ content: [{
861
+ type: "text",
862
+ text: `Pushed "${scriptSlug}": ${content.length} chars \u2192 v${updated.current_version}
863
+
864
+ Run kode_deploy to make changes live.`
865
+ }]
866
+ };
867
+ }
868
+ const results = [];
869
+ let pushedCount = 0;
870
+ let skippedCount = 0;
871
+ for (const file of localFiles) {
872
+ const slug = file.replace(/\.(js|css)$/, "");
873
+ const filePath = path2.join(scriptsDir, file);
874
+ const content = fs2.readFileSync(filePath, "utf-8");
875
+ const remote = remoteScripts.find((s) => s.slug === slug);
876
+ if (!remote) {
877
+ results.push(`\u26A0\uFE0F ${slug}: not on server (create with kode_create_script first)`);
878
+ continue;
879
+ }
880
+ if (!force && remote.content === content) {
881
+ skippedCount++;
882
+ continue;
883
+ }
884
+ try {
885
+ const updated = await client.updateScript(remote.id, {
886
+ content,
887
+ changeSummary: "Pushed via MCP"
888
+ });
889
+ results.push(`\u2713 ${slug}: ${content.length} chars \u2192 v${updated.current_version}`);
890
+ pushedCount++;
891
+ } catch (err) {
892
+ results.push(`\u2717 ${slug}: ${err instanceof Error ? err.message : "failed"}`);
893
+ }
894
+ }
895
+ let responseText = `Push complete: ${pushedCount} updated, ${skippedCount} unchanged`;
896
+ if (results.length > 0) {
897
+ responseText += `
898
+
899
+ ${results.join("\n")}`;
900
+ }
901
+ if (pushedCount > 0) {
902
+ responseText += `
903
+
904
+ Run kode_deploy to make changes live.`;
905
+ }
906
+ return {
907
+ content: [{ type: "text", text: responseText }]
908
+ };
909
+ }
650
910
  case "kode_update_script": {
651
- const { slug, content, autoLoad, scope, changeSummary } = args;
911
+ const { slug, autoLoad, scope, purpose, regenerateSummary } = args;
652
912
  const scripts = await client.listScripts(siteId);
653
913
  const script = scripts.find((s) => s.slug === slug || s.id === slug);
654
914
  if (!script) {
@@ -657,24 +917,32 @@ Note: Run kode_deploy to make it live.`
657
917
  isError: true
658
918
  };
659
919
  }
660
- const updateData = {};
661
- if (content !== void 0) updateData.content = content;
920
+ const updateData = {
921
+ changeSummary: "Updated settings via MCP"
922
+ };
662
923
  if (autoLoad !== void 0) updateData.autoLoad = autoLoad;
663
924
  if (scope !== void 0) updateData.scope = scope;
664
- updateData.changeSummary = changeSummary || "Updated via MCP";
925
+ if (purpose !== void 0) updateData.metadata = { purpose };
926
+ if (regenerateSummary !== void 0) updateData.regenerateSummary = regenerateSummary;
665
927
  const updated = await client.updateScript(script.id, updateData);
666
928
  const changes = [];
667
- if (content !== void 0) changes.push(`content \u2192 v${updated.current_version}`);
668
- if (autoLoad !== void 0) changes.push(`auto_load \u2192 ${autoLoad}`);
929
+ if (autoLoad !== void 0) changes.push(`autoLoad \u2192 ${autoLoad}`);
669
930
  if (scope !== void 0) changes.push(`scope \u2192 ${scope}`);
931
+ if (purpose !== void 0) changes.push(`purpose updated`);
932
+ if (regenerateSummary) changes.push("AI summary regenerated");
933
+ let responseText = `Updated script "${updated.name}"`;
934
+ if (changes.length > 0) {
935
+ responseText += `
936
+ Changes: ${changes.join(", ")}`;
937
+ }
938
+ responseText += `
939
+
940
+ To update content: edit local file and run kode_push`;
670
941
  return {
671
942
  content: [
672
943
  {
673
944
  type: "text",
674
- text: `Updated script "${updated.name}"
675
- Changes: ${changes.join(", ")}
676
-
677
- Note: Run kode_deploy to make changes live.`
945
+ text: responseText
678
946
  }
679
947
  ]
680
948
  };
@@ -705,19 +973,54 @@ Note: Run kode_deploy to make changes live.`
705
973
  environment: environment || "staging",
706
974
  notes: notes || "Deployed via MCP"
707
975
  });
976
+ let responseText = `Deployment ${deployment.version} to ${deployment.environment}: ${deployment.status}`;
977
+ responseText += `
978
+ Started: ${deployment.started_at}`;
979
+ if (deployment.completed_at) {
980
+ responseText += `
981
+ Completed: ${deployment.completed_at}`;
982
+ }
983
+ const scriptSizes = deployment.stats?.scriptSizes;
984
+ if (scriptSizes && scriptSizes.length > 0) {
985
+ responseText += `
986
+
987
+ Scripts deployed (${scriptSizes.length}):`;
988
+ for (const s of scriptSizes) {
989
+ const flags = [
990
+ s.scope === "global" ? "G" : "P",
991
+ s.autoLoad ? "\u26A1" : "\u25CB"
992
+ ].join("");
993
+ responseText += `
994
+ ${s.slug} [${flags}]: ${s.contentSize} chars`;
995
+ if (s.contentSize === 0) {
996
+ responseText += " \u26A0\uFE0F EMPTY";
997
+ } else if (s.contentSize < 50 && s.scope === "global") {
998
+ responseText += " \u26A0\uFE0F very small";
999
+ }
1000
+ }
1001
+ }
708
1002
  return {
709
1003
  content: [
710
1004
  {
711
1005
  type: "text",
712
- text: `Deployment ${deployment.version} to ${deployment.environment}: ${deployment.status}
713
- Started: ${deployment.started_at}${deployment.completed_at ? `
714
- Completed: ${deployment.completed_at}` : ""}`
1006
+ text: responseText
715
1007
  }
716
1008
  ]
717
1009
  };
718
1010
  }
719
1011
  case "kode_promote": {
720
1012
  const status = await client.getDeploymentStatus(siteId);
1013
+ if (!status.productionEnabled) {
1014
+ return {
1015
+ content: [
1016
+ {
1017
+ type: "text",
1018
+ text: "Cannot promote: Production is not enabled for this site.\n\nRun kode_production_enable first to activate the production environment."
1019
+ }
1020
+ ],
1021
+ isError: true
1022
+ };
1023
+ }
721
1024
  if (!status.canPromote) {
722
1025
  return {
723
1026
  content: [
@@ -740,12 +1043,46 @@ Status: ${deployment.status}`
740
1043
  ]
741
1044
  };
742
1045
  }
1046
+ case "kode_production_enable": {
1047
+ const { productionDomain } = args;
1048
+ const result = await client.setProductionEnabled(siteId, true, productionDomain);
1049
+ let text = "Production environment enabled!\n\n";
1050
+ if (result.productionDomain) {
1051
+ text += `Domain: ${result.productionDomain}
1052
+ `;
1053
+ }
1054
+ text += "\nNext steps:\n";
1055
+ text += "1. Deploy to staging: kode_deploy\n";
1056
+ text += "2. Promote to production: kode_promote";
1057
+ return {
1058
+ content: [{ type: "text", text }]
1059
+ };
1060
+ }
1061
+ case "kode_production_disable": {
1062
+ await client.setProductionEnabled(siteId, false);
1063
+ return {
1064
+ content: [
1065
+ {
1066
+ type: "text",
1067
+ text: "Production environment disabled.\n\nOnly staging is now active. Production domain requests will receive an empty script with a warning."
1068
+ }
1069
+ ]
1070
+ };
1071
+ }
743
1072
  case "kode_status": {
744
1073
  const status = await client.getDeploymentStatus(siteId);
745
1074
  const config = getConfig();
746
1075
  let text = `Site: ${config?.siteName || "Unknown"}
747
1076
 
748
1077
  `;
1078
+ const productionEnabled = status.productionEnabled ?? false;
1079
+ text += `PRODUCTION STATUS: ${productionEnabled ? "\u2713 Enabled" : "\u25CB Disabled (staging only)"}
1080
+ `;
1081
+ if (productionEnabled && status.productionDomain) {
1082
+ text += ` Domain: ${status.productionDomain}
1083
+ `;
1084
+ }
1085
+ text += "\n";
749
1086
  text += `STAGING:
750
1087
  `;
751
1088
  if (status.staging.lastSuccessful) {
@@ -760,17 +1097,25 @@ Status: ${deployment.status}`
760
1097
  text += `
761
1098
  PRODUCTION:
762
1099
  `;
763
- if (status.production.lastSuccessful) {
1100
+ if (!productionEnabled) {
1101
+ text += ` (Disabled - use kode_production_enable to activate)
1102
+ `;
1103
+ } else if (status.production.lastSuccessful) {
764
1104
  text += ` Version: ${status.production.lastSuccessful.version}
765
1105
  `;
766
1106
  text += ` Deployed: ${status.production.lastSuccessful.completed_at}
767
1107
  `;
768
1108
  } else {
769
- text += ` Not deployed
1109
+ text += ` Enabled, not yet deployed
770
1110
  `;
771
1111
  }
772
- text += `
1112
+ if (productionEnabled) {
1113
+ text += `
773
1114
  Can promote staging to production: ${status.canPromote ? "Yes" : "No"}`;
1115
+ } else {
1116
+ text += `
1117
+ Enable production first (kode_production_enable) before promoting.`;
1118
+ }
774
1119
  return {
775
1120
  content: [{ type: "text", text }]
776
1121
  };
@@ -955,6 +1300,184 @@ Embed code:
955
1300
  content: [{ type: "text", text }]
956
1301
  };
957
1302
  }
1303
+ case "kode_analyze_script": {
1304
+ const { slug, saveToScript } = args;
1305
+ const scripts = await client.listScripts(siteId);
1306
+ const script = scripts.find((s) => s.slug === slug || s.id === slug);
1307
+ if (!script) {
1308
+ return {
1309
+ content: [{ type: "text", text: `Script "${slug}" not found` }],
1310
+ isError: true
1311
+ };
1312
+ }
1313
+ try {
1314
+ const result = await client.analyzeScript(script.id, { saveToScript });
1315
+ let text = `Script Analysis: ${script.name}
1316
+
1317
+ `;
1318
+ text += `Purpose: ${result.metadata.purpose || "(not detected)"}
1319
+
1320
+ `;
1321
+ if (result.metadata.triggers.length > 0) {
1322
+ text += `Triggers:
1323
+ `;
1324
+ for (const t of result.metadata.triggers) {
1325
+ text += ` - ${t.event}${t.selector ? ` on ${t.selector}` : ""}
1326
+ `;
1327
+ }
1328
+ text += "\n";
1329
+ }
1330
+ if (result.metadata.domTargets.length > 0) {
1331
+ text += `DOM Targets:
1332
+ `;
1333
+ for (const d of result.metadata.domTargets) {
1334
+ text += ` - ${d.selector} (${d.action})
1335
+ `;
1336
+ }
1337
+ text += "\n";
1338
+ }
1339
+ if (result.metadata.dependencies.length > 0) {
1340
+ text += `Dependencies:
1341
+ `;
1342
+ for (const dep of result.metadata.dependencies) {
1343
+ text += ` - ${dep.name}${dep.required ? " (required)" : ""}
1344
+ `;
1345
+ }
1346
+ text += "\n";
1347
+ }
1348
+ text += `Flags:
1349
+ `;
1350
+ text += ` - Modifies DOM: ${result.metadata.modifiesDom ? "Yes" : "No"}
1351
+ `;
1352
+ text += ` - Event listeners: ${result.metadata.addsEventListeners ? "Yes" : "No"}
1353
+ `;
1354
+ if (result.metadata.usesExternalApis) text += ` - Uses external APIs: Yes
1355
+ `;
1356
+ if (result.metadata.usesLocalStorage) text += ` - Uses localStorage: Yes
1357
+ `;
1358
+ if (result.metadata.usesCookies) text += ` - Uses cookies: Yes
1359
+ `;
1360
+ if (result.warnings.length > 0) {
1361
+ text += `
1362
+ Warnings:
1363
+ `;
1364
+ for (const w of result.warnings) {
1365
+ text += ` \u26A0\uFE0F ${w}
1366
+ `;
1367
+ }
1368
+ }
1369
+ if (result.suggestions.length > 0) {
1370
+ text += `
1371
+ Suggestions:
1372
+ `;
1373
+ for (const s of result.suggestions) {
1374
+ text += ` \u{1F4A1} ${s}
1375
+ `;
1376
+ }
1377
+ }
1378
+ if (saveToScript) {
1379
+ text += `
1380
+ \u2713 Metadata saved to script. Run kode_deploy to update CDN.`;
1381
+ } else {
1382
+ text += `
1383
+ Use saveToScript: true to save this metadata to the script.`;
1384
+ }
1385
+ return {
1386
+ content: [{ type: "text", text }]
1387
+ };
1388
+ } catch (error) {
1389
+ const message = error instanceof Error ? error.message : "Unknown error";
1390
+ return {
1391
+ content: [{ type: "text", text: `Failed to analyze script: ${message}` }],
1392
+ isError: true
1393
+ };
1394
+ }
1395
+ }
1396
+ case "kode_get_script_metadata": {
1397
+ const { slug } = args;
1398
+ const scripts = await client.listScripts(siteId);
1399
+ const script = scripts.find((s) => s.slug === slug || s.id === slug);
1400
+ if (!script) {
1401
+ return {
1402
+ content: [{ type: "text", text: `Script "${slug}" not found` }],
1403
+ isError: true
1404
+ };
1405
+ }
1406
+ try {
1407
+ const result = await client.getScriptMetadata(script.id);
1408
+ if (!result.metadata) {
1409
+ return {
1410
+ content: [{
1411
+ type: "text",
1412
+ text: `Script "${script.name}" has no metadata.
1413
+
1414
+ Use kode_analyze_script to generate metadata, or provide metadata when creating/updating the script.`
1415
+ }]
1416
+ };
1417
+ }
1418
+ let text = `Metadata for: ${script.name}
1419
+
1420
+ `;
1421
+ if (result.metadata.purpose) {
1422
+ text += `Purpose: ${result.metadata.purpose}
1423
+
1424
+ `;
1425
+ }
1426
+ if (result.aiSummary) {
1427
+ text += `AI Summary: ${result.aiSummary}
1428
+
1429
+ `;
1430
+ }
1431
+ if (result.metadata.triggers.length > 0) {
1432
+ text += `Triggers:
1433
+ `;
1434
+ for (const t of result.metadata.triggers) {
1435
+ text += ` - ${t.event}${t.selector ? ` on ${t.selector}` : ""}
1436
+ `;
1437
+ }
1438
+ text += "\n";
1439
+ }
1440
+ if (result.metadata.domTargets.length > 0) {
1441
+ text += `DOM Targets:
1442
+ `;
1443
+ for (const d of result.metadata.domTargets) {
1444
+ text += ` - ${d.selector} (${d.action})
1445
+ `;
1446
+ }
1447
+ text += "\n";
1448
+ }
1449
+ if (result.metadata.dependencies.length > 0) {
1450
+ text += `Dependencies:
1451
+ `;
1452
+ for (const dep of result.metadata.dependencies) {
1453
+ text += ` - ${dep.name}${dep.required ? " (required)" : ""}
1454
+ `;
1455
+ }
1456
+ text += "\n";
1457
+ }
1458
+ text += `Flags:
1459
+ `;
1460
+ text += ` - AI Generated: ${result.aiGenerated ? "Yes" : "No"}
1461
+ `;
1462
+ text += ` - Modifies DOM: ${result.metadata.modifiesDom ? "Yes" : "No"}
1463
+ `;
1464
+ text += ` - Event listeners: ${result.metadata.addsEventListeners ? "Yes" : "No"}
1465
+ `;
1466
+ if (result.lastAnalyzed) {
1467
+ text += `
1468
+ Last analyzed: ${result.lastAnalyzed}`;
1469
+ }
1470
+ return {
1471
+ content: [{ type: "text", text }]
1472
+ };
1473
+ } catch (error) {
1474
+ const message = error instanceof Error ? error.message : "Unknown error";
1475
+ return {
1476
+ content: [{ type: "text", text: `Failed to get metadata: ${message}` }],
1477
+ isError: true
1478
+ };
1479
+ }
1480
+ }
958
1481
  case "kode_read_context": {
959
1482
  const contextPath = getContextPath();
960
1483
  if (!contextPath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curenorway/kode-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "MCP server for Cure Kode - enables AI agents to manage Webflow scripts",
5
5
  "type": "module",
6
6
  "bin": {