@curenorway/kode-mcp 1.7.0 → 2.0.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 +880 -76
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -10,6 +10,387 @@ import {
10
10
  ReadResourceRequestSchema
11
11
  } from "@modelcontextprotocol/sdk/types.js";
12
12
 
13
+ // src/file-utils.ts
14
+ import * as fs from "fs";
15
+ import * as path from "path";
16
+ var SUPPORTED_EXTENSIONS = [".js", ".css", ".ts", ".tsx", ".jsx"];
17
+ var BUNDLEABLE_EXTENSIONS = [".ts", ".tsx", ".jsx"];
18
+ var SKIP_DIRS = ["packages", "node_modules", ".git", "dist", "build"];
19
+ function isSupportedFile(fileName) {
20
+ return SUPPORTED_EXTENSIONS.some((ext) => fileName.endsWith(ext));
21
+ }
22
+ function needsBundling(fileName) {
23
+ return BUNDLEABLE_EXTENSIONS.some((ext) => fileName.endsWith(ext));
24
+ }
25
+ function slugFromFileName(fileName) {
26
+ return fileName.replace(/\.(js|ts|tsx|jsx|css)$/, "");
27
+ }
28
+ function extFromFileName(fileName) {
29
+ const ext = path.extname(fileName);
30
+ return ext.startsWith(".") ? ext.slice(1) : ext;
31
+ }
32
+ function mimeTypeFromFileName(fileName) {
33
+ if (fileName.endsWith(".css")) return "text/css";
34
+ if (fileName.endsWith(".ts")) return "application/typescript";
35
+ if (fileName.endsWith(".tsx")) return "text/tsx";
36
+ if (fileName.endsWith(".jsx")) return "text/jsx";
37
+ return "application/javascript";
38
+ }
39
+ function buildFileInfo(filePath, scriptsDir, isModule) {
40
+ const fileName = path.basename(filePath);
41
+ return {
42
+ fileName,
43
+ slug: slugFromFileName(fileName),
44
+ ext: extFromFileName(fileName),
45
+ filePath,
46
+ needsBundling: needsBundling(fileName),
47
+ isModule,
48
+ relativePath: path.relative(scriptsDir, filePath)
49
+ };
50
+ }
51
+ function getEntryFiles(scriptsDir) {
52
+ if (!fs.existsSync(scriptsDir)) return [];
53
+ return fs.readdirSync(scriptsDir).filter((f) => {
54
+ const fullPath = path.join(scriptsDir, f);
55
+ return fs.statSync(fullPath).isFile() && isSupportedFile(f);
56
+ }).map((f) => buildFileInfo(path.join(scriptsDir, f), scriptsDir, false));
57
+ }
58
+ function getModuleFiles(scriptsDir) {
59
+ if (!fs.existsSync(scriptsDir)) return [];
60
+ const modules = [];
61
+ function scanDir(dir) {
62
+ const entries2 = fs.readdirSync(dir, { withFileTypes: true });
63
+ for (const entry of entries2) {
64
+ const fullPath = path.join(dir, entry.name);
65
+ if (entry.isDirectory()) {
66
+ if (!SKIP_DIRS.includes(entry.name)) {
67
+ scanDir(fullPath);
68
+ }
69
+ } else if (entry.isFile() && isSupportedFile(entry.name)) {
70
+ modules.push(buildFileInfo(fullPath, scriptsDir, true));
71
+ }
72
+ }
73
+ }
74
+ const entries = fs.readdirSync(scriptsDir, { withFileTypes: true });
75
+ for (const entry of entries) {
76
+ if (entry.isDirectory() && !SKIP_DIRS.includes(entry.name)) {
77
+ scanDir(path.join(scriptsDir, entry.name));
78
+ }
79
+ }
80
+ return modules;
81
+ }
82
+
83
+ // src/bundler.ts
84
+ import * as esbuild from "esbuild";
85
+ import * as fs2 from "fs";
86
+ import * as path2 from "path";
87
+ async function bundleEntryPoint(options) {
88
+ const {
89
+ entryPoint,
90
+ scriptsDir,
91
+ minify = true,
92
+ packageAliases = {}
93
+ } = options;
94
+ try {
95
+ const result = await esbuild.build({
96
+ entryPoints: [entryPoint],
97
+ bundle: true,
98
+ format: "iife",
99
+ platform: "browser",
100
+ target: ["es2020"],
101
+ minify,
102
+ write: false,
103
+ // React is loaded from CDN at runtime, not bundled
104
+ external: ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"],
105
+ // Modern JSX transform — no need for React import in every file
106
+ jsx: "automatic",
107
+ jsxImportSource: "react",
108
+ // Resolve imports relative to scripts directory
109
+ absWorkingDir: scriptsDir,
110
+ // File type loaders
111
+ loader: {
112
+ ".ts": "ts",
113
+ ".tsx": "tsx",
114
+ ".jsx": "jsx",
115
+ ".js": "js",
116
+ ".css": "css"
117
+ },
118
+ // Try these extensions when resolving imports without extension
119
+ resolveExtensions: [".tsx", ".ts", ".jsx", ".js", ".css"],
120
+ // Package aliases for @kode/* imports (populated in Step 5)
121
+ alias: packageAliases,
122
+ // Shim require() for external React in browser IIFE context
123
+ // Maps require("react") → window.React, require("react-dom") → window.ReactDOM, etc.
124
+ banner: {
125
+ js: `var require=function(m){var g={"react":window.React,"react-dom":window.ReactDOM,"react-dom/client":window.ReactDOM,"react/jsx-runtime":window.React,"react/jsx-dev-runtime":window.React};if(g[m])return g[m];throw new Error("Cannot find module '"+m+"'")};`
126
+ },
127
+ // Readable output names for debugging
128
+ logLevel: "silent"
129
+ });
130
+ const code = result.outputFiles?.[0]?.text || "";
131
+ const warnings = result.warnings.map((w) => formatMessage(w));
132
+ const sourceCode = fs2.readFileSync(entryPoint, "utf-8");
133
+ return {
134
+ code,
135
+ warnings,
136
+ errors: [],
137
+ usesReact: detectReactUsage(sourceCode),
138
+ size: code.length
139
+ };
140
+ } catch (err) {
141
+ if (err && typeof err === "object" && "errors" in err) {
142
+ const buildErr = err;
143
+ return {
144
+ code: "",
145
+ warnings: buildErr.warnings.map((w) => formatMessage(w)),
146
+ errors: buildErr.errors.map((e) => formatMessage(e)),
147
+ usesReact: false,
148
+ size: 0
149
+ };
150
+ }
151
+ return {
152
+ code: "",
153
+ warnings: [],
154
+ errors: [err instanceof Error ? err.message : String(err)],
155
+ usesReact: false,
156
+ size: 0
157
+ };
158
+ }
159
+ }
160
+ function detectReactUsage(sourceCode) {
161
+ return /from\s+['"]react['"]/.test(sourceCode) || /from\s+['"]react-dom/.test(sourceCode) || /import\s+React/.test(sourceCode) || /require\s*\(\s*['"]react/.test(sourceCode) || /React\.createElement/.test(sourceCode);
162
+ }
163
+ function formatMessage(msg) {
164
+ let text = msg.text;
165
+ if (msg.location) {
166
+ const loc = msg.location;
167
+ text = `${loc.file}:${loc.line}:${loc.column}: ${text}`;
168
+ }
169
+ return text;
170
+ }
171
+ function getPackageAliases(scriptsDir) {
172
+ const pkgDir = path2.join(scriptsDir, "packages", "@kode");
173
+ if (!fs2.existsSync(pkgDir)) return {};
174
+ const aliases = {};
175
+ const ENTRY_FILES = ["index.ts", "index.tsx", "index.jsx", "index.js"];
176
+ for (const name of fs2.readdirSync(pkgDir)) {
177
+ const pkgPath = path2.join(pkgDir, name);
178
+ if (!fs2.statSync(pkgPath).isDirectory()) continue;
179
+ for (const entry of ENTRY_FILES) {
180
+ const entryPath = path2.join(pkgPath, entry);
181
+ if (fs2.existsSync(entryPath)) {
182
+ aliases[`@kode/${name}`] = entryPath;
183
+ break;
184
+ }
185
+ }
186
+ }
187
+ return aliases;
188
+ }
189
+
190
+ // src/pkg.ts
191
+ import * as fs3 from "fs";
192
+ import * as path3 from "path";
193
+ var PACKAGES_DIR = "packages/@kode";
194
+ function getPackagesDir(scriptsDir) {
195
+ return path3.join(scriptsDir, PACKAGES_DIR);
196
+ }
197
+ function listInstalledPackages(scriptsDir) {
198
+ const pkgDir = getPackagesDir(scriptsDir);
199
+ if (!fs3.existsSync(pkgDir)) return [];
200
+ return fs3.readdirSync(pkgDir).filter((name) => {
201
+ return fs3.statSync(path3.join(pkgDir, name)).isDirectory();
202
+ });
203
+ }
204
+ function isPackageInstalled(scriptsDir, slug) {
205
+ return fs3.existsSync(path3.join(getPackagesDir(scriptsDir), slug));
206
+ }
207
+ async function pkgAdd(client, scriptsDir, packageSlug) {
208
+ let pkg;
209
+ try {
210
+ pkg = await client.getPackage(packageSlug);
211
+ } catch {
212
+ return { success: false, message: `Package "${packageSlug}" not found in registry.` };
213
+ }
214
+ if (!pkg.files || pkg.files.length === 0) {
215
+ return { success: false, message: `Package "${packageSlug}" has no files.` };
216
+ }
217
+ const pkgDir = path3.join(getPackagesDir(scriptsDir), pkg.slug);
218
+ fs3.mkdirSync(pkgDir, { recursive: true });
219
+ for (const file of pkg.files) {
220
+ const filePath = path3.join(pkgDir, file.path);
221
+ fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
222
+ fs3.writeFileSync(filePath, file.content, "utf-8");
223
+ }
224
+ const manifest = {
225
+ name: pkg.name,
226
+ slug: pkg.slug,
227
+ version: pkg.version,
228
+ description: pkg.description,
229
+ entryPoint: pkg.entry_point,
230
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
231
+ };
232
+ fs3.writeFileSync(path3.join(pkgDir, "kode.json"), JSON.stringify(manifest, null, 2) + "\n");
233
+ const already = isPackageInstalled(scriptsDir, pkg.slug) ? " (updated)" : "";
234
+ return {
235
+ success: true,
236
+ message: `\u2705 Installed @kode/${pkg.slug} v${pkg.version}${already}
237
+ ${pkg.files.length} file(s) \u2192 .cure-kode-scripts/packages/@kode/${pkg.slug}/
238
+ Import with: import { ... } from '@kode/${pkg.slug}'`,
239
+ data: manifest
240
+ };
241
+ }
242
+ async function pkgRemove(scriptsDir, packageSlug) {
243
+ const pkgDir = path3.join(getPackagesDir(scriptsDir), packageSlug);
244
+ if (!fs3.existsSync(pkgDir)) {
245
+ return { success: false, message: `Package "@kode/${packageSlug}" is not installed.` };
246
+ }
247
+ fs3.rmSync(pkgDir, { recursive: true, force: true });
248
+ return {
249
+ success: true,
250
+ message: `\u2705 Removed @kode/${packageSlug}`
251
+ };
252
+ }
253
+ async function pkgPublish(client, scriptsDir, options) {
254
+ const sourcePath = path3.join(scriptsDir, options.path);
255
+ if (!fs3.existsSync(sourcePath)) {
256
+ return { success: false, message: `Path not found: ${options.path}` };
257
+ }
258
+ const files = [];
259
+ const stat = fs3.statSync(sourcePath);
260
+ if (stat.isFile()) {
261
+ const ext = path3.extname(sourcePath);
262
+ const content = fs3.readFileSync(sourcePath, "utf-8");
263
+ files.push({ path: `index${ext}`, content });
264
+ } else if (stat.isDirectory()) {
265
+ collectFiles(sourcePath, sourcePath, files);
266
+ }
267
+ if (files.length === 0) {
268
+ return { success: false, message: `No files found at "${options.path}".` };
269
+ }
270
+ let entryPoint = options.entryPoint;
271
+ if (!entryPoint) {
272
+ const entryNames = ["index.ts", "index.tsx", "index.jsx", "index.js"];
273
+ const found = files.find((f) => entryNames.includes(f.path));
274
+ if (found) {
275
+ entryPoint = found.path;
276
+ } else {
277
+ entryPoint = files[0].path;
278
+ }
279
+ }
280
+ const displayName = options.name || options.slug.split("-").map(
281
+ (w) => w.charAt(0).toUpperCase() + w.slice(1)
282
+ ).join(" ");
283
+ try {
284
+ const result = await client.publishPackage({
285
+ name: displayName,
286
+ slug: options.slug,
287
+ description: options.description,
288
+ entryPoint,
289
+ files,
290
+ tags: options.tags
291
+ });
292
+ const verb = result.created ? "Published" : "Updated";
293
+ return {
294
+ success: true,
295
+ message: `\u2705 ${verb} @kode/${result.slug} v${result.version}
296
+ ${files.length} file(s), entry: ${entryPoint}
297
+ Install with: kode_pkg add ${result.slug}`,
298
+ data: { slug: result.slug, version: result.version, files: files.length }
299
+ };
300
+ } catch (err) {
301
+ return {
302
+ success: false,
303
+ message: `Failed to publish: ${err instanceof Error ? err.message : String(err)}`
304
+ };
305
+ }
306
+ }
307
+ async function pkgList(client, scriptsDir, options) {
308
+ const queryParts = [];
309
+ if (options?.search) queryParts.push(`search=${encodeURIComponent(options.search)}`);
310
+ if (options?.category) queryParts.push(`category=${encodeURIComponent(options.category)}`);
311
+ const qs = queryParts.length > 0 ? queryParts.join("&") : void 0;
312
+ const packages = await client.listPackages(qs);
313
+ const installed = scriptsDir ? listInstalledPackages(scriptsDir) : [];
314
+ if (packages.length === 0) {
315
+ return { success: true, message: "No packages found in the registry." };
316
+ }
317
+ const lines = packages.map((p) => {
318
+ const isInstalled = installed.includes(p.slug);
319
+ const status = isInstalled ? " \u2705" : "";
320
+ const tags = p.tags?.length ? ` [${p.tags.join(", ")}]` : "";
321
+ return ` ${p.slug} v${p.version}${status} \u2014 ${p.description || "No description"}${tags}`;
322
+ });
323
+ return {
324
+ success: true,
325
+ message: `\u{1F4E6} Packages in registry (${packages.length}):
326
+ ${lines.join("\n")}`,
327
+ data: packages
328
+ };
329
+ }
330
+ async function pkgInfo(client, scriptsDir, packageSlug) {
331
+ let pkg;
332
+ try {
333
+ pkg = await client.getPackage(packageSlug);
334
+ } catch {
335
+ return { success: false, message: `Package "${packageSlug}" not found.` };
336
+ }
337
+ const installed = scriptsDir ? isPackageInstalled(scriptsDir, pkg.slug) : false;
338
+ const fileList = pkg.files?.map((f) => ` ${f.path}`).join("\n") || " (no files)";
339
+ const info = [
340
+ `\u{1F4E6} @kode/${pkg.slug} v${pkg.version}`,
341
+ ` Name: ${pkg.name}`,
342
+ pkg.description ? ` Description: ${pkg.description}` : null,
343
+ ` Entry: ${pkg.entry_point}`,
344
+ pkg.tags?.length ? ` Tags: ${pkg.tags.join(", ")}` : null,
345
+ ` Installs: ${pkg.install_count}`,
346
+ ` Status: ${installed ? "\u2705 Installed locally" : "Not installed"}`,
347
+ ` Files:
348
+ ${fileList}`,
349
+ pkg.example_usage ? `
350
+ Example:
351
+ ${pkg.example_usage}` : null
352
+ ].filter(Boolean).join("\n");
353
+ return { success: true, message: info, data: pkg };
354
+ }
355
+ async function pkgUpdate(client, scriptsDir, packageSlug) {
356
+ if (!isPackageInstalled(scriptsDir, packageSlug)) {
357
+ return { success: false, message: `Package "@kode/${packageSlug}" is not installed.` };
358
+ }
359
+ const manifestPath = path3.join(getPackagesDir(scriptsDir), packageSlug, "kode.json");
360
+ let currentVersion = 0;
361
+ if (fs3.existsSync(manifestPath)) {
362
+ try {
363
+ const manifest = JSON.parse(fs3.readFileSync(manifestPath, "utf-8"));
364
+ currentVersion = manifest.version || 0;
365
+ } catch {
366
+ }
367
+ }
368
+ let pkg;
369
+ try {
370
+ pkg = await client.getPackage(packageSlug);
371
+ } catch {
372
+ return { success: false, message: `Package "${packageSlug}" not found in registry.` };
373
+ }
374
+ if (pkg.version <= currentVersion) {
375
+ return { success: true, message: `@kode/${packageSlug} is already up to date (v${currentVersion}).` };
376
+ }
377
+ return pkgAdd(client, scriptsDir, packageSlug);
378
+ }
379
+ function collectFiles(baseDir, currentDir, files) {
380
+ for (const entry of fs3.readdirSync(currentDir, { withFileTypes: true })) {
381
+ const fullPath = path3.join(currentDir, entry.name);
382
+ if (entry.isDirectory()) {
383
+ if (!["node_modules", ".git", "dist"].includes(entry.name)) {
384
+ collectFiles(baseDir, fullPath, files);
385
+ }
386
+ } else if (entry.isFile()) {
387
+ const relativePath = path3.relative(baseDir, fullPath);
388
+ const content = fs3.readFileSync(fullPath, "utf-8");
389
+ files.push({ path: relativePath, content });
390
+ }
391
+ }
392
+ }
393
+
13
394
  // src/api.ts
14
395
  var KodeApiClient = class {
15
396
  apiUrl;
@@ -18,8 +399,8 @@ var KodeApiClient = class {
18
399
  this.apiUrl = apiUrl.replace(/\/$/, "");
19
400
  this.apiKey = apiKey;
20
401
  }
21
- async request(path3, options = {}) {
22
- const url = `${this.apiUrl}${path3}`;
402
+ async request(path6, options = {}) {
403
+ const url = `${this.apiUrl}${path6}`;
23
404
  const headers = {
24
405
  "Content-Type": "application/json",
25
406
  "X-API-Key": this.apiKey,
@@ -154,6 +535,33 @@ var KodeApiClient = class {
154
535
  body: JSON.stringify({ siteId, url })
155
536
  });
156
537
  }
538
+ // Library operations
539
+ async listLibrary(queryString) {
540
+ return this.request(`/api/cdn/library${queryString || ""}`);
541
+ }
542
+ async getLibrarySnippet(slugOrId) {
543
+ return this.request(`/api/cdn/library/${encodeURIComponent(slugOrId)}`);
544
+ }
545
+ async createLibrarySnippet(data) {
546
+ return this.request("/api/cdn/library", {
547
+ method: "POST",
548
+ body: JSON.stringify(data)
549
+ });
550
+ }
551
+ async updateLibrarySnippet(slugOrId, data) {
552
+ return this.request(`/api/cdn/library/${encodeURIComponent(slugOrId)}`, {
553
+ method: "PATCH",
554
+ body: JSON.stringify(data)
555
+ });
556
+ }
557
+ async deleteLibrarySnippet(slugOrId) {
558
+ await this.request(`/api/cdn/library/${encodeURIComponent(slugOrId)}`, {
559
+ method: "DELETE"
560
+ });
561
+ }
562
+ async listLibraryFolders() {
563
+ return this.request("/api/cdn/library/folders");
564
+ }
157
565
  // Validate API key
158
566
  async validateKey() {
159
567
  try {
@@ -173,32 +581,60 @@ var KodeApiClient = class {
173
581
  async getScriptMetadata(scriptId) {
174
582
  return this.request(`/api/cdn/scripts/${scriptId}/metadata`);
175
583
  }
584
+ // ==================== Packages ====================
585
+ async listPackages(queryString) {
586
+ const qs = queryString ? `?${queryString}` : "";
587
+ return this.request(`/api/cdn/packages${qs}`);
588
+ }
589
+ async getPackage(slugOrId) {
590
+ return this.request(`/api/cdn/packages/${slugOrId}`);
591
+ }
592
+ async publishPackage(data) {
593
+ return this.request("/api/cdn/packages", {
594
+ method: "POST",
595
+ body: JSON.stringify(data)
596
+ });
597
+ }
598
+ async updatePackage(slugOrId, data) {
599
+ return this.request(`/api/cdn/packages/${slugOrId}`, {
600
+ method: "PATCH",
601
+ body: JSON.stringify(data)
602
+ });
603
+ }
604
+ async deletePackage(slugOrId) {
605
+ return this.request(`/api/cdn/packages/${slugOrId}`, {
606
+ method: "DELETE"
607
+ });
608
+ }
609
+ async getCmsTypes(siteId) {
610
+ return this.request(`/api/cdn/sites/${siteId}/cms-types`);
611
+ }
176
612
  };
177
613
 
178
614
  // src/config.ts
179
- import * as fs from "fs";
180
- import * as path from "path";
615
+ import * as fs4 from "fs";
616
+ import * as path4 from "path";
181
617
  var DEFAULT_API_URL = "https://app.cure.no";
182
618
  var CONFIG_DIR = ".cure-kode";
183
619
  var CONFIG_FILE = "config.json";
184
620
  function findProjectRoot(startDir = process.cwd()) {
185
621
  let currentDir = startDir;
186
- while (currentDir !== path.dirname(currentDir)) {
187
- const configPath = path.join(currentDir, CONFIG_DIR, CONFIG_FILE);
188
- if (fs.existsSync(configPath)) {
622
+ while (currentDir !== path4.dirname(currentDir)) {
623
+ const configPath = path4.join(currentDir, CONFIG_DIR, CONFIG_FILE);
624
+ if (fs4.existsSync(configPath)) {
189
625
  return currentDir;
190
626
  }
191
- currentDir = path.dirname(currentDir);
627
+ currentDir = path4.dirname(currentDir);
192
628
  }
193
629
  return void 0;
194
630
  }
195
631
  function loadProjectConfig() {
196
632
  const projectRoot = findProjectRoot();
197
633
  if (!projectRoot) return void 0;
198
- const configPath = path.join(projectRoot, CONFIG_DIR, CONFIG_FILE);
199
- if (!fs.existsSync(configPath)) return void 0;
634
+ const configPath = path4.join(projectRoot, CONFIG_DIR, CONFIG_FILE);
635
+ if (!fs4.existsSync(configPath)) return void 0;
200
636
  try {
201
- const content = fs.readFileSync(configPath, "utf-8");
637
+ const content = fs4.readFileSync(configPath, "utf-8");
202
638
  return JSON.parse(content);
203
639
  } catch {
204
640
  return void 0;
@@ -235,22 +671,22 @@ function getScriptsDir() {
235
671
  const projectRoot = findProjectRoot();
236
672
  const projectConfig = loadProjectConfig();
237
673
  if (!projectRoot || !projectConfig) return void 0;
238
- return path.join(projectRoot, projectConfig.scriptsDir);
674
+ return path4.join(projectRoot, projectConfig.scriptsDir);
239
675
  }
240
676
  function getContextPath() {
241
677
  const projectRoot = findProjectRoot();
242
678
  if (!projectRoot) return void 0;
243
- return path.join(projectRoot, CONFIG_DIR, "context.md");
679
+ return path4.join(projectRoot, CONFIG_DIR, "context.md");
244
680
  }
245
681
  function getPagesDir() {
246
682
  const projectRoot = findProjectRoot();
247
683
  if (!projectRoot) return void 0;
248
- return path.join(projectRoot, CONFIG_DIR, "pages");
684
+ return path4.join(projectRoot, CONFIG_DIR, "pages");
249
685
  }
250
686
 
251
687
  // src/index.ts
252
- import * as fs2 from "fs";
253
- import * as path2 from "path";
688
+ import * as fs5 from "fs";
689
+ import * as path5 from "path";
254
690
  function urlToSlug(url) {
255
691
  try {
256
692
  const parsed = new URL(url);
@@ -329,7 +765,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
329
765
  },
330
766
  {
331
767
  name: "kode_create_script",
332
- 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.",
768
+ description: "Create a new script entry (metadata only). After creating, write the script file locally to .cure-kode-scripts/{name}.{ext} and use kode_push to upload content. Supports JS, TS, TSX, JSX, and CSS files.",
333
769
  inputSchema: {
334
770
  type: "object",
335
771
  properties: {
@@ -340,7 +776,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
340
776
  type: {
341
777
  type: "string",
342
778
  enum: ["javascript", "css"],
343
- description: "Script type"
779
+ description: 'Script type (CDN type). Use "javascript" for JS/TS/TSX/JSX files.'
780
+ },
781
+ sourceType: {
782
+ type: "string",
783
+ enum: ["js", "ts", "tsx", "jsx", "css"],
784
+ description: 'Source file extension. Determines the local file type. Default: "js" for javascript, "css" for css.'
344
785
  },
345
786
  scope: {
346
787
  type: "string",
@@ -832,6 +1273,169 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
832
1273
  },
833
1274
  required: []
834
1275
  }
1276
+ },
1277
+ {
1278
+ name: "kode_library_list",
1279
+ description: "List all snippets in the global Kode library (Bibliotek). Returns reusable code patterns like GSAP animations, form handlers, scroll effects. Use this before writing common patterns from scratch.",
1280
+ inputSchema: {
1281
+ type: "object",
1282
+ properties: {
1283
+ category: {
1284
+ type: "string",
1285
+ description: "Filter by category: animations, forms, utilities, tracking, analytics, consent, integrations, other"
1286
+ },
1287
+ search: {
1288
+ type: "string",
1289
+ description: "Search by name or tags"
1290
+ },
1291
+ folder: {
1292
+ type: "string",
1293
+ description: "Filter by folder ID"
1294
+ }
1295
+ },
1296
+ required: []
1297
+ }
1298
+ },
1299
+ {
1300
+ name: "kode_library_get",
1301
+ description: "Get a library snippet by slug or ID. Returns full code, description, tags, example usage, and version.",
1302
+ inputSchema: {
1303
+ type: "object",
1304
+ properties: {
1305
+ slug: {
1306
+ type: "string",
1307
+ description: "Snippet slug or ID"
1308
+ }
1309
+ },
1310
+ required: ["slug"]
1311
+ }
1312
+ },
1313
+ {
1314
+ name: "kode_library_use",
1315
+ description: "Copy a library snippet into the current project as a new script file. The snippet code is written to .cure-kode-scripts/{slug}.{ext}. Use kode_push afterwards to upload to CDN.",
1316
+ inputSchema: {
1317
+ type: "object",
1318
+ properties: {
1319
+ snippetSlug: {
1320
+ type: "string",
1321
+ description: "Library snippet slug to copy"
1322
+ },
1323
+ newName: {
1324
+ type: "string",
1325
+ description: "Optional new filename (without extension). Defaults to the snippet slug."
1326
+ }
1327
+ },
1328
+ required: ["snippetSlug"]
1329
+ }
1330
+ },
1331
+ {
1332
+ name: "kode_library_create",
1333
+ description: "Create a new snippet in the global Kode library. Requires a unique slug.",
1334
+ inputSchema: {
1335
+ type: "object",
1336
+ properties: {
1337
+ name: { type: "string", description: "Display name" },
1338
+ slug: { type: "string", description: "URL-safe slug (must be unique)" },
1339
+ type: { type: "string", enum: ["javascript", "css"], description: "Script type" },
1340
+ code: { type: "string", description: "Script code" },
1341
+ description: { type: "string", description: "Short description" },
1342
+ category: { type: "string", description: "Category: animations, forms, utilities, tracking, analytics, consent, integrations, other" },
1343
+ tags: { type: "array", items: { type: "string" }, description: "Tags for discovery" },
1344
+ folderId: { type: "string", description: "Target folder ID" }
1345
+ },
1346
+ required: ["name", "slug", "type", "code"]
1347
+ }
1348
+ },
1349
+ {
1350
+ name: "kode_library_update",
1351
+ description: "Update an existing library snippet (code, metadata, or both). Version auto-increments when code changes.",
1352
+ inputSchema: {
1353
+ type: "object",
1354
+ properties: {
1355
+ slug: { type: "string", description: "Snippet slug or ID" },
1356
+ code: { type: "string", description: "New code (triggers version bump)" },
1357
+ name: { type: "string", description: "New display name" },
1358
+ description: { type: "string", description: "New description" },
1359
+ category: { type: "string", description: "New category" },
1360
+ tags: { type: "array", items: { type: "string" }, description: "New tags" },
1361
+ folderId: { type: ["string", "null"], description: "Move to folder (null = root)" }
1362
+ },
1363
+ required: ["slug"]
1364
+ }
1365
+ },
1366
+ {
1367
+ name: "kode_library_delete",
1368
+ description: "Soft-delete a library snippet (moves to trash, recoverable for 30 days).",
1369
+ inputSchema: {
1370
+ type: "object",
1371
+ properties: {
1372
+ slug: { type: "string", description: "Snippet slug or ID to delete" }
1373
+ },
1374
+ required: ["slug"]
1375
+ }
1376
+ },
1377
+ {
1378
+ name: "kode_library_folders",
1379
+ description: "List all folders in the Kode library. Returns folder hierarchy with snippet counts.",
1380
+ inputSchema: {
1381
+ type: "object",
1382
+ properties: {},
1383
+ required: []
1384
+ }
1385
+ },
1386
+ {
1387
+ name: "kode_pkg",
1388
+ description: "Package manager for Cure Kode. Like npm but for @kode/* packages. Actions: add (install a package), remove (uninstall), publish (upload a component to registry), list (browse registry), info (package details), update (update installed package).",
1389
+ inputSchema: {
1390
+ type: "object",
1391
+ properties: {
1392
+ action: {
1393
+ type: "string",
1394
+ enum: ["add", "remove", "publish", "list", "info", "update"],
1395
+ description: "The action to perform."
1396
+ },
1397
+ name: {
1398
+ type: "string",
1399
+ description: "Package slug. Required for add, remove, info, update."
1400
+ },
1401
+ // publish-specific options
1402
+ path: {
1403
+ type: "string",
1404
+ description: 'For publish: path relative to .cure-kode-scripts/ (e.g., "components/Button" or "lib/utils.ts").'
1405
+ },
1406
+ slug: {
1407
+ type: "string",
1408
+ description: "For publish: package slug to publish as."
1409
+ },
1410
+ displayName: {
1411
+ type: "string",
1412
+ description: "For publish: human-readable name."
1413
+ },
1414
+ description: {
1415
+ type: "string",
1416
+ description: "For publish: package description."
1417
+ },
1418
+ entryPoint: {
1419
+ type: "string",
1420
+ description: "For publish: entry point file (auto-detected if not set)."
1421
+ },
1422
+ tags: {
1423
+ type: "array",
1424
+ items: { type: "string" },
1425
+ description: "For publish: tags for discoverability."
1426
+ },
1427
+ // list-specific options
1428
+ search: {
1429
+ type: "string",
1430
+ description: "For list: search query."
1431
+ },
1432
+ category: {
1433
+ type: "string",
1434
+ description: "For list: filter by category."
1435
+ }
1436
+ },
1437
+ required: ["action"]
1438
+ }
835
1439
  }
836
1440
  ]
837
1441
  };
@@ -935,7 +1539,7 @@ ${script.content}`
935
1539
  };
936
1540
  }
937
1541
  case "kode_create_script": {
938
- const { name: scriptName, type, scope, autoLoad, purpose } = args;
1542
+ const { name: scriptName, type, sourceType, scope, autoLoad, purpose } = args;
939
1543
  const scriptScope = scope || "global";
940
1544
  const script = await client.createScript(siteId, {
941
1545
  name: scriptName,
@@ -948,7 +1552,7 @@ ${script.content}`
948
1552
  metadata: purpose ? { purpose } : void 0
949
1553
  });
950
1554
  const scriptsDir = getScriptsDir();
951
- const ext = type === "javascript" ? "js" : "css";
1555
+ const ext = sourceType || (type === "javascript" ? "js" : "css");
952
1556
  const localPath = scriptsDir ? `${scriptsDir}/${scriptName}.${ext}` : `.cure-kode-scripts/${scriptName}.${ext}`;
953
1557
  let responseText = `Created script "${script.name}" (${script.type})`;
954
1558
  responseText += `
@@ -957,6 +1561,10 @@ Slug: ${script.slug}`;
957
1561
  Scope: ${script.scope}`;
958
1562
  responseText += `
959
1563
  Auto-load: ${script.auto_load ? "yes" : "no"}`;
1564
+ if (sourceType && sourceType !== "js" && sourceType !== "css") {
1565
+ responseText += `
1566
+ Source type: ${sourceType.toUpperCase()}`;
1567
+ }
960
1568
  if (purpose) responseText += `
961
1569
  Purpose: ${purpose}`;
962
1570
  responseText += `
@@ -965,7 +1573,7 @@ Next steps:`;
965
1573
  responseText += `
966
1574
  1. Create file: ${localPath}`;
967
1575
  responseText += `
968
- 2. Write your ${type} code`;
1576
+ 2. Write your ${ext.toUpperCase()} code`;
969
1577
  responseText += `
970
1578
  3. Run kode_push to upload content`;
971
1579
  responseText += `
@@ -982,7 +1590,7 @@ Next steps:`;
982
1590
  case "kode_push": {
983
1591
  const { scriptSlug, force } = args;
984
1592
  const scriptsDir = getScriptsDir();
985
- if (!scriptsDir || !fs2.existsSync(scriptsDir)) {
1593
+ if (!scriptsDir || !fs5.existsSync(scriptsDir)) {
986
1594
  return {
987
1595
  content: [{
988
1596
  type: "text",
@@ -992,23 +1600,21 @@ Next steps:`;
992
1600
  };
993
1601
  }
994
1602
  const remoteScripts = await client.listScripts(siteId);
995
- const localFiles = fs2.readdirSync(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css"));
1603
+ const entryFiles = getEntryFiles(scriptsDir);
996
1604
  if (scriptSlug) {
997
- const ext = remoteScripts.find((s) => s.slug === scriptSlug)?.type === "css" ? "css" : "js";
998
- const fileName = `${scriptSlug}.${ext}`;
999
- const filePath = path2.join(scriptsDir, fileName);
1000
- if (!fs2.existsSync(filePath)) {
1605
+ const entry = entryFiles.find((f) => f.slug === scriptSlug);
1606
+ if (!entry) {
1607
+ const available = entryFiles.map((f) => f.fileName).join(", ") || "(none)";
1001
1608
  return {
1002
1609
  content: [{
1003
1610
  type: "text",
1004
- text: `File not found: ${filePath}
1611
+ text: `No entry file found for slug "${scriptSlug}"
1005
1612
 
1006
- Available files: ${localFiles.join(", ") || "(none)"}`
1613
+ Available entry files: ${available}`
1007
1614
  }],
1008
1615
  isError: true
1009
1616
  };
1010
1617
  }
1011
- const content = fs2.readFileSync(filePath, "utf-8");
1012
1618
  const remote = remoteScripts.find((s) => s.slug === scriptSlug);
1013
1619
  if (!remote) {
1014
1620
  return {
@@ -1019,6 +1625,36 @@ Available files: ${localFiles.join(", ") || "(none)"}`
1019
1625
  isError: true
1020
1626
  };
1021
1627
  }
1628
+ const sourceContent = fs5.readFileSync(entry.filePath, "utf-8");
1629
+ let content = sourceContent;
1630
+ let bundleNote = "";
1631
+ let usesReact = false;
1632
+ if (entry.needsBundling) {
1633
+ const aliases2 = getPackageAliases(scriptsDir);
1634
+ const result = await bundleEntryPoint({
1635
+ entryPoint: entry.filePath,
1636
+ scriptsDir,
1637
+ packageAliases: aliases2
1638
+ });
1639
+ if (result.errors.length > 0) {
1640
+ return {
1641
+ content: [{
1642
+ type: "text",
1643
+ text: `Build failed for "${scriptSlug}":
1644
+
1645
+ ${result.errors.join("\n")}`
1646
+ }],
1647
+ isError: true
1648
+ };
1649
+ }
1650
+ content = result.code;
1651
+ usesReact = result.usesReact;
1652
+ bundleNote = `
1653
+ \u{1F4E6} Bundled from ${entry.ext.toUpperCase()}: ${result.size} bytes`;
1654
+ if (usesReact) bundleNote += " (uses React)";
1655
+ if (result.warnings.length > 0) bundleNote += `
1656
+ \u26A0\uFE0F ${result.warnings.join(", ")}`;
1657
+ }
1022
1658
  if (!force && remote.content === content) {
1023
1659
  return {
1024
1660
  content: [{
@@ -1027,14 +1663,20 @@ Available files: ${localFiles.join(", ") || "(none)"}`
1027
1663
  }]
1028
1664
  };
1029
1665
  }
1666
+ const existingMetadata = remote.metadata || {};
1030
1667
  const updated = await client.updateScript(remote.id, {
1031
1668
  content,
1032
- changeSummary: "Pushed via MCP"
1669
+ changeSummary: `Pushed via MCP (${entry.ext})`,
1670
+ ...entry.needsBundling ? {
1671
+ metadata: { ...existingMetadata, usesReact },
1672
+ sourceContent,
1673
+ sourceType: entry.ext
1674
+ } : {}
1033
1675
  });
1034
1676
  return {
1035
1677
  content: [{
1036
1678
  type: "text",
1037
- text: `Pushed "${scriptSlug}": ${content.length} chars \u2192 v${updated.current_version}
1679
+ text: `Pushed "${scriptSlug}": ${content.length} chars \u2192 v${updated.current_version}${bundleNote}
1038
1680
 
1039
1681
  Run kode_deploy to make changes live.`
1040
1682
  }]
@@ -1043,28 +1685,48 @@ Run kode_deploy to make changes live.`
1043
1685
  const results = [];
1044
1686
  let pushedCount = 0;
1045
1687
  let skippedCount = 0;
1046
- for (const file of localFiles) {
1047
- const slug = file.replace(/\.(js|css)$/, "");
1048
- const filePath = path2.join(scriptsDir, file);
1049
- const content = fs2.readFileSync(filePath, "utf-8");
1050
- const remote = remoteScripts.find((s) => s.slug === slug);
1688
+ const aliases = getPackageAliases(scriptsDir);
1689
+ for (const entry of entryFiles) {
1690
+ const remote = remoteScripts.find((s) => s.slug === entry.slug);
1051
1691
  if (!remote) {
1052
- results.push(`\u26A0\uFE0F ${slug}: not on server (create with kode_create_script first)`);
1692
+ results.push(`\u26A0\uFE0F ${entry.slug}: not on server (create with kode_create_script first)`);
1053
1693
  continue;
1054
1694
  }
1695
+ const sourceContent = fs5.readFileSync(entry.filePath, "utf-8");
1696
+ let content = sourceContent;
1697
+ let usesReact = false;
1698
+ if (entry.needsBundling) {
1699
+ const bundleResult = await bundleEntryPoint({
1700
+ entryPoint: entry.filePath,
1701
+ scriptsDir,
1702
+ packageAliases: aliases
1703
+ });
1704
+ if (bundleResult.errors.length > 0) {
1705
+ results.push(`\u2717 ${entry.slug}: build failed: ${bundleResult.errors[0]}`);
1706
+ continue;
1707
+ }
1708
+ content = bundleResult.code;
1709
+ usesReact = bundleResult.usesReact;
1710
+ }
1055
1711
  if (!force && remote.content === content) {
1056
1712
  skippedCount++;
1057
1713
  continue;
1058
1714
  }
1059
1715
  try {
1716
+ const existingMeta = remote.metadata || {};
1060
1717
  const updated = await client.updateScript(remote.id, {
1061
1718
  content,
1062
- changeSummary: "Pushed via MCP"
1719
+ changeSummary: `Pushed via MCP (${entry.ext})`,
1720
+ ...entry.needsBundling ? {
1721
+ metadata: { ...existingMeta, usesReact },
1722
+ sourceContent,
1723
+ sourceType: entry.ext
1724
+ } : {}
1063
1725
  });
1064
- results.push(`\u2713 ${slug}: ${content.length} chars \u2192 v${updated.current_version}`);
1726
+ results.push(`\u2713 ${entry.slug}: ${content.length} chars \u2192 v${updated.current_version}`);
1065
1727
  pushedCount++;
1066
1728
  } catch (err) {
1067
- results.push(`\u2717 ${slug}: ${err instanceof Error ? err.message : "failed"}`);
1729
+ results.push(`\u2717 ${entry.slug}: ${err instanceof Error ? err.message : "failed"}`);
1068
1730
  }
1069
1731
  }
1070
1732
  let responseText = `Push complete: ${pushedCount} updated, ${skippedCount} unchanged`;
@@ -1905,13 +2567,13 @@ Last analyzed: ${result.lastAnalyzed}`;
1905
2567
  isError: true
1906
2568
  };
1907
2569
  }
1908
- if (!fs2.existsSync(contextPath)) {
2570
+ if (!fs5.existsSync(contextPath)) {
1909
2571
  return {
1910
2572
  content: [{ type: "text", text: 'No context file found. Run "kode context --refresh" to create one.' }],
1911
2573
  isError: true
1912
2574
  };
1913
2575
  }
1914
- const content = fs2.readFileSync(contextPath, "utf-8");
2576
+ const content = fs5.readFileSync(contextPath, "utf-8");
1915
2577
  return {
1916
2578
  content: [{ type: "text", text: content }]
1917
2579
  };
@@ -1924,14 +2586,14 @@ Last analyzed: ${result.lastAnalyzed}`;
1924
2586
  isError: true
1925
2587
  };
1926
2588
  }
1927
- if (!fs2.existsSync(contextPath)) {
2589
+ if (!fs5.existsSync(contextPath)) {
1928
2590
  return {
1929
2591
  content: [{ type: "text", text: 'No context file found. Run "kode context --refresh" to create one.' }],
1930
2592
  isError: true
1931
2593
  };
1932
2594
  }
1933
2595
  const { addNote, addSession, updateScriptPurpose } = args;
1934
- let content = fs2.readFileSync(contextPath, "utf-8");
2596
+ let content = fs5.readFileSync(contextPath, "utf-8");
1935
2597
  const updates = [];
1936
2598
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1937
2599
  content = content.replace(
@@ -1992,7 +2654,7 @@ ${sessionMd}`
1992
2654
  updates.push(`Updated purpose for "${slug}": "${purpose}"`);
1993
2655
  }
1994
2656
  }
1995
- fs2.writeFileSync(contextPath, content, "utf-8");
2657
+ fs5.writeFileSync(contextPath, content, "utf-8");
1996
2658
  return {
1997
2659
  content: [
1998
2660
  {
@@ -2100,10 +2762,10 @@ ${result.htmlPreview.slice(0, 5e3)}`;
2100
2762
  };
2101
2763
  }
2102
2764
  const slug = urlToSlug(url);
2103
- const cachePath = path2.join(pagesDir, `${slug}.json`);
2104
- if (!force && fs2.existsSync(cachePath)) {
2765
+ const cachePath = path5.join(pagesDir, `${slug}.json`);
2766
+ if (!force && fs5.existsSync(cachePath)) {
2105
2767
  try {
2106
- const cached = JSON.parse(fs2.readFileSync(cachePath, "utf-8"));
2768
+ const cached = JSON.parse(fs5.readFileSync(cachePath, "utf-8"));
2107
2769
  return {
2108
2770
  content: [{
2109
2771
  type: "text",
@@ -2143,12 +2805,12 @@ CMS Collections: ${cached.cmsPatterns.length}`
2143
2805
  };
2144
2806
  }
2145
2807
  const structure = await response.json();
2146
- if (!fs2.existsSync(pagesDir)) {
2147
- fs2.mkdirSync(pagesDir, { recursive: true });
2808
+ if (!fs5.existsSync(pagesDir)) {
2809
+ fs5.mkdirSync(pagesDir, { recursive: true });
2148
2810
  }
2149
- fs2.writeFileSync(cachePath, JSON.stringify(structure, null, 2), "utf-8");
2811
+ fs5.writeFileSync(cachePath, JSON.stringify(structure, null, 2), "utf-8");
2150
2812
  const contextPath = getContextPath();
2151
- if (contextPath && fs2.existsSync(contextPath)) {
2813
+ if (contextPath && fs5.existsSync(contextPath)) {
2152
2814
  }
2153
2815
  let text = `Page cached: ${slug}
2154
2816
  Path: ${cachePath}
@@ -2216,8 +2878,8 @@ CMS Collections (${structure.cmsPatterns.length}):
2216
2878
  };
2217
2879
  }
2218
2880
  const slug = urlOrSlug.startsWith("http") ? urlToSlug(urlOrSlug) : urlOrSlug;
2219
- const cachePath = path2.join(pagesDir, `${slug}.json`);
2220
- if (!fs2.existsSync(cachePath)) {
2881
+ const cachePath = path5.join(pagesDir, `${slug}.json`);
2882
+ if (!fs5.existsSync(cachePath)) {
2221
2883
  return {
2222
2884
  content: [{
2223
2885
  type: "text",
@@ -2228,7 +2890,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
2228
2890
  isError: true
2229
2891
  };
2230
2892
  }
2231
- const context = JSON.parse(fs2.readFileSync(cachePath, "utf-8"));
2893
+ const context = JSON.parse(fs5.readFileSync(cachePath, "utf-8"));
2232
2894
  const cacheAge = Date.now() - new Date(context.extractedAt).getTime();
2233
2895
  const cacheAgeDays = Math.floor(cacheAge / (1e3 * 60 * 60 * 24));
2234
2896
  const isStale = cacheAgeDays > 7;
@@ -2472,7 +3134,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
2472
3134
  isError: true
2473
3135
  };
2474
3136
  }
2475
- if (!fs2.existsSync(pagesDir)) {
3137
+ if (!fs5.existsSync(pagesDir)) {
2476
3138
  return {
2477
3139
  content: [{
2478
3140
  type: "text",
@@ -2480,7 +3142,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
2480
3142
  }]
2481
3143
  };
2482
3144
  }
2483
- const files = fs2.readdirSync(pagesDir).filter((f) => f.endsWith(".json"));
3145
+ const files = fs5.readdirSync(pagesDir).filter((f) => f.endsWith(".json"));
2484
3146
  if (files.length === 0) {
2485
3147
  return {
2486
3148
  content: [{
@@ -2495,7 +3157,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
2495
3157
  for (const file of files) {
2496
3158
  const slug = file.replace(".json", "");
2497
3159
  try {
2498
- const context = JSON.parse(fs2.readFileSync(path2.join(pagesDir, file), "utf-8"));
3160
+ const context = JSON.parse(fs5.readFileSync(path5.join(pagesDir, file), "utf-8"));
2499
3161
  const urlPath = new URL(context.url).pathname;
2500
3162
  text += `${urlPath} [${slug}]
2501
3163
  `;
@@ -2576,8 +3238,8 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
2576
3238
  text += "\n";
2577
3239
  }
2578
3240
  if (updateClaudeMd) {
2579
- const kodeMdPath = path2.join(projectRoot, ".cure-kode", "KODE.md");
2580
- const claudeMdPath = path2.join(projectRoot, "CLAUDE.md");
3241
+ const kodeMdPath = path5.join(projectRoot, ".cure-kode", "KODE.md");
3242
+ const claudeMdPath = path5.join(projectRoot, "CLAUDE.md");
2581
3243
  const siteName = config.siteName || "Site";
2582
3244
  const siteSlug = config.siteSlug || "site";
2583
3245
  let kodeMd = `# Cure Kode: ${siteName}
@@ -2597,7 +3259,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
2597
3259
  kodeMd += `## CDN URL
2598
3260
 
2599
3261
  \`\`\`html
2600
- <script defer src="https://app.cure.no/api/cdn/${siteSlug}/init.js"></script>
3262
+ <script src="https://app.cure.no/api/cdn/${siteSlug}/init.js"></script>
2601
3263
  \`\`\`
2602
3264
 
2603
3265
  ---
@@ -2652,7 +3314,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
2652
3314
  `;
2653
3315
  kodeMd += `**Check for updates:** \`kode doctor\`
2654
3316
  `;
2655
- fs2.writeFileSync(kodeMdPath, kodeMd);
3317
+ fs5.writeFileSync(kodeMdPath, kodeMd);
2656
3318
  text += `Updated .cure-kode/KODE.md with current scripts and pages.
2657
3319
  `;
2658
3320
  const kodeReference = `## Cure Kode
@@ -2668,16 +3330,16 @@ Full documentation: [.cure-kode/KODE.md](.cure-kode/KODE.md)
2668
3330
  ---
2669
3331
 
2670
3332
  `;
2671
- if (fs2.existsSync(claudeMdPath)) {
2672
- const claudeContent = fs2.readFileSync(claudeMdPath, "utf-8");
3333
+ if (fs5.existsSync(claudeMdPath)) {
3334
+ const claudeContent = fs5.readFileSync(claudeMdPath, "utf-8");
2673
3335
  const hasReference = claudeContent.includes("KODE.md") || claudeContent.includes(".cure-kode/KODE.md");
2674
3336
  if (!hasReference) {
2675
- fs2.writeFileSync(claudeMdPath, kodeReference + claudeContent);
3337
+ fs5.writeFileSync(claudeMdPath, kodeReference + claudeContent);
2676
3338
  text += `Added Kode reference to CLAUDE.md.
2677
3339
  `;
2678
3340
  }
2679
3341
  } else {
2680
- fs2.writeFileSync(claudeMdPath, kodeReference);
3342
+ fs5.writeFileSync(claudeMdPath, kodeReference);
2681
3343
  text += `Created CLAUDE.md with Kode reference.
2682
3344
  `;
2683
3345
  }
@@ -2693,6 +3355,147 @@ Full documentation: [.cure-kode/KODE.md](.cure-kode/KODE.md)
2693
3355
  };
2694
3356
  }
2695
3357
  }
3358
+ case "kode_library_list": {
3359
+ const { category, search, folder } = args;
3360
+ const params = new URLSearchParams();
3361
+ if (category) params.set("category", category);
3362
+ if (search) params.set("search", search);
3363
+ if (folder) params.set("folder", folder);
3364
+ const snippets = await client.listLibrary(params.toString() ? `?${params.toString()}` : "");
3365
+ return {
3366
+ content: [{ type: "text", text: JSON.stringify(snippets, null, 2) }]
3367
+ };
3368
+ }
3369
+ case "kode_library_get": {
3370
+ const { slug } = args;
3371
+ const snippet = await client.getLibrarySnippet(slug);
3372
+ return {
3373
+ content: [{ type: "text", text: JSON.stringify(snippet, null, 2) }]
3374
+ };
3375
+ }
3376
+ case "kode_library_use": {
3377
+ const { snippetSlug, newName } = args;
3378
+ const snippet = await client.getLibrarySnippet(snippetSlug);
3379
+ const fileName = newName || snippet.slug;
3380
+ const ext = snippet.type === "javascript" ? "js" : "css";
3381
+ const scriptsDir = getScriptsDir();
3382
+ if (!scriptsDir) {
3383
+ return {
3384
+ content: [{ type: "text", text: "Scripts directory not found. Run `kode init` first." }],
3385
+ isError: true
3386
+ };
3387
+ }
3388
+ const filePath = path5.join(scriptsDir, `${fileName}.${ext}`);
3389
+ if (fs5.existsSync(filePath)) {
3390
+ return {
3391
+ content: [{ type: "text", text: `File already exists: ${fileName}.${ext}. Use a different newName or delete the existing file.` }],
3392
+ isError: true
3393
+ };
3394
+ }
3395
+ if (!fs5.existsSync(scriptsDir)) {
3396
+ fs5.mkdirSync(scriptsDir, { recursive: true });
3397
+ }
3398
+ fs5.writeFileSync(filePath, snippet.code);
3399
+ return {
3400
+ content: [{ type: "text", text: `Copied "${snippet.name}" to ${fileName}.${ext}
3401
+
3402
+ Run \`kode push\` to upload to CDN.` }]
3403
+ };
3404
+ }
3405
+ case "kode_library_create": {
3406
+ const { name: name2, slug, type, code, description, category, tags, folderId } = args;
3407
+ const result = await client.createLibrarySnippet({
3408
+ name: name2,
3409
+ slug,
3410
+ type,
3411
+ code,
3412
+ description,
3413
+ category,
3414
+ tags,
3415
+ folderId
3416
+ });
3417
+ return {
3418
+ content: [{ type: "text", text: `Created library snippet "${name2}" (${result.slug})` }]
3419
+ };
3420
+ }
3421
+ case "kode_library_update": {
3422
+ const { slug, ...updates } = args;
3423
+ const result = await client.updateLibrarySnippet(slug, updates);
3424
+ return {
3425
+ content: [{ type: "text", text: `Updated library snippet "${result.slug}" (now v${result.version})` }]
3426
+ };
3427
+ }
3428
+ case "kode_library_delete": {
3429
+ const { slug } = args;
3430
+ await client.deleteLibrarySnippet(slug);
3431
+ return {
3432
+ content: [{ type: "text", text: `Soft-deleted library snippet "${slug}". It can be restored from trash within 30 days.` }]
3433
+ };
3434
+ }
3435
+ case "kode_library_folders": {
3436
+ const folders = await client.listLibraryFolders();
3437
+ return {
3438
+ content: [{ type: "text", text: JSON.stringify(folders, null, 2) }]
3439
+ };
3440
+ }
3441
+ case "kode_pkg": {
3442
+ const action = args?.action;
3443
+ const scriptsDir = getScriptsDir();
3444
+ switch (action) {
3445
+ case "add": {
3446
+ const pkgName = args?.name;
3447
+ if (!pkgName) return { content: [{ type: "text", text: "Error: name is required for add." }], isError: true };
3448
+ if (!scriptsDir) return { content: [{ type: "text", text: "Error: Not in a Kode project. Run kode init first." }], isError: true };
3449
+ const result = await pkgAdd(client, scriptsDir, pkgName);
3450
+ return { content: [{ type: "text", text: result.message }], isError: !result.success };
3451
+ }
3452
+ case "remove": {
3453
+ const pkgName = args?.name;
3454
+ if (!pkgName) return { content: [{ type: "text", text: "Error: name is required for remove." }], isError: true };
3455
+ if (!scriptsDir) return { content: [{ type: "text", text: "Error: Not in a Kode project." }], isError: true };
3456
+ const result = await pkgRemove(scriptsDir, pkgName);
3457
+ return { content: [{ type: "text", text: result.message }], isError: !result.success };
3458
+ }
3459
+ case "publish": {
3460
+ const pubPath = args?.path;
3461
+ const pubSlug = args?.slug || args?.name;
3462
+ if (!pubPath) return { content: [{ type: "text", text: "Error: path is required for publish." }], isError: true };
3463
+ if (!pubSlug) return { content: [{ type: "text", text: "Error: slug (or name) is required for publish." }], isError: true };
3464
+ if (!scriptsDir) return { content: [{ type: "text", text: "Error: Not in a Kode project." }], isError: true };
3465
+ const result = await pkgPublish(client, scriptsDir, {
3466
+ path: pubPath,
3467
+ slug: pubSlug,
3468
+ name: args?.displayName,
3469
+ description: args?.description,
3470
+ entryPoint: args?.entryPoint,
3471
+ tags: args?.tags
3472
+ });
3473
+ return { content: [{ type: "text", text: result.message }], isError: !result.success };
3474
+ }
3475
+ case "list": {
3476
+ const result = await pkgList(client, scriptsDir, {
3477
+ search: args?.search,
3478
+ category: args?.category
3479
+ });
3480
+ return { content: [{ type: "text", text: result.message }] };
3481
+ }
3482
+ case "info": {
3483
+ const pkgName = args?.name;
3484
+ if (!pkgName) return { content: [{ type: "text", text: "Error: name is required for info." }], isError: true };
3485
+ const result = await pkgInfo(client, scriptsDir, pkgName);
3486
+ return { content: [{ type: "text", text: result.message }], isError: !result.success };
3487
+ }
3488
+ case "update": {
3489
+ const pkgName = args?.name;
3490
+ if (!pkgName) return { content: [{ type: "text", text: "Error: name is required for update." }], isError: true };
3491
+ if (!scriptsDir) return { content: [{ type: "text", text: "Error: Not in a Kode project." }], isError: true };
3492
+ const result = await pkgUpdate(client, scriptsDir, pkgName);
3493
+ return { content: [{ type: "text", text: result.message }], isError: !result.success };
3494
+ }
3495
+ default:
3496
+ return { content: [{ type: "text", text: `Unknown kode_pkg action: "${action}". Use: add, remove, publish, list, info, update.` }], isError: true };
3497
+ }
3498
+ }
2696
3499
  default:
2697
3500
  return {
2698
3501
  content: [{ type: "text", text: `Unknown tool: ${name}` }],
@@ -2713,14 +3516,15 @@ Full documentation: [.cure-kode/KODE.md](.cure-kode/KODE.md)
2713
3516
  });
2714
3517
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
2715
3518
  const scriptsDir = getScriptsDir();
2716
- if (!scriptsDir || !fs2.existsSync(scriptsDir)) {
3519
+ if (!scriptsDir || !fs5.existsSync(scriptsDir)) {
2717
3520
  return { resources: [] };
2718
3521
  }
2719
- const files = fs2.readdirSync(scriptsDir);
2720
- const resources = files.filter((f) => f.endsWith(".js") || f.endsWith(".css")).map((f) => ({
2721
- uri: `file://${path2.join(scriptsDir, f)}`,
2722
- name: f,
2723
- mimeType: f.endsWith(".js") ? "application/javascript" : "text/css"
3522
+ const entries = getEntryFiles(scriptsDir);
3523
+ const modules = getModuleFiles(scriptsDir);
3524
+ const resources = [...entries, ...modules].map((f) => ({
3525
+ uri: `file://${f.filePath}`,
3526
+ name: f.isModule ? f.relativePath : f.fileName,
3527
+ mimeType: mimeTypeFromFileName(f.fileName)
2724
3528
  }));
2725
3529
  return { resources };
2726
3530
  });
@@ -2730,11 +3534,11 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
2730
3534
  throw new Error("Invalid resource URI");
2731
3535
  }
2732
3536
  const filePath = uri.replace("file://", "");
2733
- if (!fs2.existsSync(filePath)) {
3537
+ if (!fs5.existsSync(filePath)) {
2734
3538
  throw new Error("File not found");
2735
3539
  }
2736
- const content = fs2.readFileSync(filePath, "utf-8");
2737
- const mimeType = filePath.endsWith(".js") ? "application/javascript" : "text/css";
3540
+ const content = fs5.readFileSync(filePath, "utf-8");
3541
+ const mimeType = mimeTypeFromFileName(filePath);
2738
3542
  return {
2739
3543
  contents: [
2740
3544
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curenorway/kode-mcp",
3
- "version": "1.7.0",
3
+ "version": "2.0.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": {
@@ -18,7 +18,8 @@
18
18
  "prepublishOnly": "pnpm build"
19
19
  },
20
20
  "dependencies": {
21
- "@modelcontextprotocol/sdk": "^1.0.0"
21
+ "@modelcontextprotocol/sdk": "^1.0.0",
22
+ "esbuild": "^0.24.2"
22
23
  },
23
24
  "devDependencies": {
24
25
  "@types/node": "^20.10.0",