@curenorway/kode-mcp 1.8.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 +782 -81
  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,
@@ -161,6 +542,26 @@ var KodeApiClient = class {
161
542
  async getLibrarySnippet(slugOrId) {
162
543
  return this.request(`/api/cdn/library/${encodeURIComponent(slugOrId)}`);
163
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
+ }
164
565
  // Validate API key
165
566
  async validateKey() {
166
567
  try {
@@ -180,32 +581,60 @@ var KodeApiClient = class {
180
581
  async getScriptMetadata(scriptId) {
181
582
  return this.request(`/api/cdn/scripts/${scriptId}/metadata`);
182
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
+ }
183
612
  };
184
613
 
185
614
  // src/config.ts
186
- import * as fs from "fs";
187
- import * as path from "path";
615
+ import * as fs4 from "fs";
616
+ import * as path4 from "path";
188
617
  var DEFAULT_API_URL = "https://app.cure.no";
189
618
  var CONFIG_DIR = ".cure-kode";
190
619
  var CONFIG_FILE = "config.json";
191
620
  function findProjectRoot(startDir = process.cwd()) {
192
621
  let currentDir = startDir;
193
- while (currentDir !== path.dirname(currentDir)) {
194
- const configPath = path.join(currentDir, CONFIG_DIR, CONFIG_FILE);
195
- 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)) {
196
625
  return currentDir;
197
626
  }
198
- currentDir = path.dirname(currentDir);
627
+ currentDir = path4.dirname(currentDir);
199
628
  }
200
629
  return void 0;
201
630
  }
202
631
  function loadProjectConfig() {
203
632
  const projectRoot = findProjectRoot();
204
633
  if (!projectRoot) return void 0;
205
- const configPath = path.join(projectRoot, CONFIG_DIR, CONFIG_FILE);
206
- 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;
207
636
  try {
208
- const content = fs.readFileSync(configPath, "utf-8");
637
+ const content = fs4.readFileSync(configPath, "utf-8");
209
638
  return JSON.parse(content);
210
639
  } catch {
211
640
  return void 0;
@@ -242,22 +671,22 @@ function getScriptsDir() {
242
671
  const projectRoot = findProjectRoot();
243
672
  const projectConfig = loadProjectConfig();
244
673
  if (!projectRoot || !projectConfig) return void 0;
245
- return path.join(projectRoot, projectConfig.scriptsDir);
674
+ return path4.join(projectRoot, projectConfig.scriptsDir);
246
675
  }
247
676
  function getContextPath() {
248
677
  const projectRoot = findProjectRoot();
249
678
  if (!projectRoot) return void 0;
250
- return path.join(projectRoot, CONFIG_DIR, "context.md");
679
+ return path4.join(projectRoot, CONFIG_DIR, "context.md");
251
680
  }
252
681
  function getPagesDir() {
253
682
  const projectRoot = findProjectRoot();
254
683
  if (!projectRoot) return void 0;
255
- return path.join(projectRoot, CONFIG_DIR, "pages");
684
+ return path4.join(projectRoot, CONFIG_DIR, "pages");
256
685
  }
257
686
 
258
687
  // src/index.ts
259
- import * as fs2 from "fs";
260
- import * as path2 from "path";
688
+ import * as fs5 from "fs";
689
+ import * as path5 from "path";
261
690
  function urlToSlug(url) {
262
691
  try {
263
692
  const parsed = new URL(url);
@@ -336,7 +765,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
336
765
  },
337
766
  {
338
767
  name: "kode_create_script",
339
- 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.",
340
769
  inputSchema: {
341
770
  type: "object",
342
771
  properties: {
@@ -347,7 +776,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
347
776
  type: {
348
777
  type: "string",
349
778
  enum: ["javascript", "css"],
350
- 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.'
351
785
  },
352
786
  scope: {
353
787
  type: "string",
@@ -853,6 +1287,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
853
1287
  search: {
854
1288
  type: "string",
855
1289
  description: "Search by name or tags"
1290
+ },
1291
+ folder: {
1292
+ type: "string",
1293
+ description: "Filter by folder ID"
856
1294
  }
857
1295
  },
858
1296
  required: []
@@ -889,6 +1327,115 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
889
1327
  },
890
1328
  required: ["snippetSlug"]
891
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
+ }
892
1439
  }
893
1440
  ]
894
1441
  };
@@ -992,7 +1539,7 @@ ${script.content}`
992
1539
  };
993
1540
  }
994
1541
  case "kode_create_script": {
995
- const { name: scriptName, type, scope, autoLoad, purpose } = args;
1542
+ const { name: scriptName, type, sourceType, scope, autoLoad, purpose } = args;
996
1543
  const scriptScope = scope || "global";
997
1544
  const script = await client.createScript(siteId, {
998
1545
  name: scriptName,
@@ -1005,7 +1552,7 @@ ${script.content}`
1005
1552
  metadata: purpose ? { purpose } : void 0
1006
1553
  });
1007
1554
  const scriptsDir = getScriptsDir();
1008
- const ext = type === "javascript" ? "js" : "css";
1555
+ const ext = sourceType || (type === "javascript" ? "js" : "css");
1009
1556
  const localPath = scriptsDir ? `${scriptsDir}/${scriptName}.${ext}` : `.cure-kode-scripts/${scriptName}.${ext}`;
1010
1557
  let responseText = `Created script "${script.name}" (${script.type})`;
1011
1558
  responseText += `
@@ -1014,6 +1561,10 @@ Slug: ${script.slug}`;
1014
1561
  Scope: ${script.scope}`;
1015
1562
  responseText += `
1016
1563
  Auto-load: ${script.auto_load ? "yes" : "no"}`;
1564
+ if (sourceType && sourceType !== "js" && sourceType !== "css") {
1565
+ responseText += `
1566
+ Source type: ${sourceType.toUpperCase()}`;
1567
+ }
1017
1568
  if (purpose) responseText += `
1018
1569
  Purpose: ${purpose}`;
1019
1570
  responseText += `
@@ -1022,7 +1573,7 @@ Next steps:`;
1022
1573
  responseText += `
1023
1574
  1. Create file: ${localPath}`;
1024
1575
  responseText += `
1025
- 2. Write your ${type} code`;
1576
+ 2. Write your ${ext.toUpperCase()} code`;
1026
1577
  responseText += `
1027
1578
  3. Run kode_push to upload content`;
1028
1579
  responseText += `
@@ -1039,7 +1590,7 @@ Next steps:`;
1039
1590
  case "kode_push": {
1040
1591
  const { scriptSlug, force } = args;
1041
1592
  const scriptsDir = getScriptsDir();
1042
- if (!scriptsDir || !fs2.existsSync(scriptsDir)) {
1593
+ if (!scriptsDir || !fs5.existsSync(scriptsDir)) {
1043
1594
  return {
1044
1595
  content: [{
1045
1596
  type: "text",
@@ -1049,23 +1600,21 @@ Next steps:`;
1049
1600
  };
1050
1601
  }
1051
1602
  const remoteScripts = await client.listScripts(siteId);
1052
- const localFiles = fs2.readdirSync(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css"));
1603
+ const entryFiles = getEntryFiles(scriptsDir);
1053
1604
  if (scriptSlug) {
1054
- const ext = remoteScripts.find((s) => s.slug === scriptSlug)?.type === "css" ? "css" : "js";
1055
- const fileName = `${scriptSlug}.${ext}`;
1056
- const filePath = path2.join(scriptsDir, fileName);
1057
- 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)";
1058
1608
  return {
1059
1609
  content: [{
1060
1610
  type: "text",
1061
- text: `File not found: ${filePath}
1611
+ text: `No entry file found for slug "${scriptSlug}"
1062
1612
 
1063
- Available files: ${localFiles.join(", ") || "(none)"}`
1613
+ Available entry files: ${available}`
1064
1614
  }],
1065
1615
  isError: true
1066
1616
  };
1067
1617
  }
1068
- const content = fs2.readFileSync(filePath, "utf-8");
1069
1618
  const remote = remoteScripts.find((s) => s.slug === scriptSlug);
1070
1619
  if (!remote) {
1071
1620
  return {
@@ -1076,6 +1625,36 @@ Available files: ${localFiles.join(", ") || "(none)"}`
1076
1625
  isError: true
1077
1626
  };
1078
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
+ }
1079
1658
  if (!force && remote.content === content) {
1080
1659
  return {
1081
1660
  content: [{
@@ -1084,14 +1663,20 @@ Available files: ${localFiles.join(", ") || "(none)"}`
1084
1663
  }]
1085
1664
  };
1086
1665
  }
1666
+ const existingMetadata = remote.metadata || {};
1087
1667
  const updated = await client.updateScript(remote.id, {
1088
1668
  content,
1089
- 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
+ } : {}
1090
1675
  });
1091
1676
  return {
1092
1677
  content: [{
1093
1678
  type: "text",
1094
- text: `Pushed "${scriptSlug}": ${content.length} chars \u2192 v${updated.current_version}
1679
+ text: `Pushed "${scriptSlug}": ${content.length} chars \u2192 v${updated.current_version}${bundleNote}
1095
1680
 
1096
1681
  Run kode_deploy to make changes live.`
1097
1682
  }]
@@ -1100,28 +1685,48 @@ Run kode_deploy to make changes live.`
1100
1685
  const results = [];
1101
1686
  let pushedCount = 0;
1102
1687
  let skippedCount = 0;
1103
- for (const file of localFiles) {
1104
- const slug = file.replace(/\.(js|css)$/, "");
1105
- const filePath = path2.join(scriptsDir, file);
1106
- const content = fs2.readFileSync(filePath, "utf-8");
1107
- 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);
1108
1691
  if (!remote) {
1109
- 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)`);
1110
1693
  continue;
1111
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
+ }
1112
1711
  if (!force && remote.content === content) {
1113
1712
  skippedCount++;
1114
1713
  continue;
1115
1714
  }
1116
1715
  try {
1716
+ const existingMeta = remote.metadata || {};
1117
1717
  const updated = await client.updateScript(remote.id, {
1118
1718
  content,
1119
- 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
+ } : {}
1120
1725
  });
1121
- 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}`);
1122
1727
  pushedCount++;
1123
1728
  } catch (err) {
1124
- results.push(`\u2717 ${slug}: ${err instanceof Error ? err.message : "failed"}`);
1729
+ results.push(`\u2717 ${entry.slug}: ${err instanceof Error ? err.message : "failed"}`);
1125
1730
  }
1126
1731
  }
1127
1732
  let responseText = `Push complete: ${pushedCount} updated, ${skippedCount} unchanged`;
@@ -1962,13 +2567,13 @@ Last analyzed: ${result.lastAnalyzed}`;
1962
2567
  isError: true
1963
2568
  };
1964
2569
  }
1965
- if (!fs2.existsSync(contextPath)) {
2570
+ if (!fs5.existsSync(contextPath)) {
1966
2571
  return {
1967
2572
  content: [{ type: "text", text: 'No context file found. Run "kode context --refresh" to create one.' }],
1968
2573
  isError: true
1969
2574
  };
1970
2575
  }
1971
- const content = fs2.readFileSync(contextPath, "utf-8");
2576
+ const content = fs5.readFileSync(contextPath, "utf-8");
1972
2577
  return {
1973
2578
  content: [{ type: "text", text: content }]
1974
2579
  };
@@ -1981,14 +2586,14 @@ Last analyzed: ${result.lastAnalyzed}`;
1981
2586
  isError: true
1982
2587
  };
1983
2588
  }
1984
- if (!fs2.existsSync(contextPath)) {
2589
+ if (!fs5.existsSync(contextPath)) {
1985
2590
  return {
1986
2591
  content: [{ type: "text", text: 'No context file found. Run "kode context --refresh" to create one.' }],
1987
2592
  isError: true
1988
2593
  };
1989
2594
  }
1990
2595
  const { addNote, addSession, updateScriptPurpose } = args;
1991
- let content = fs2.readFileSync(contextPath, "utf-8");
2596
+ let content = fs5.readFileSync(contextPath, "utf-8");
1992
2597
  const updates = [];
1993
2598
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1994
2599
  content = content.replace(
@@ -2049,7 +2654,7 @@ ${sessionMd}`
2049
2654
  updates.push(`Updated purpose for "${slug}": "${purpose}"`);
2050
2655
  }
2051
2656
  }
2052
- fs2.writeFileSync(contextPath, content, "utf-8");
2657
+ fs5.writeFileSync(contextPath, content, "utf-8");
2053
2658
  return {
2054
2659
  content: [
2055
2660
  {
@@ -2157,10 +2762,10 @@ ${result.htmlPreview.slice(0, 5e3)}`;
2157
2762
  };
2158
2763
  }
2159
2764
  const slug = urlToSlug(url);
2160
- const cachePath = path2.join(pagesDir, `${slug}.json`);
2161
- if (!force && fs2.existsSync(cachePath)) {
2765
+ const cachePath = path5.join(pagesDir, `${slug}.json`);
2766
+ if (!force && fs5.existsSync(cachePath)) {
2162
2767
  try {
2163
- const cached = JSON.parse(fs2.readFileSync(cachePath, "utf-8"));
2768
+ const cached = JSON.parse(fs5.readFileSync(cachePath, "utf-8"));
2164
2769
  return {
2165
2770
  content: [{
2166
2771
  type: "text",
@@ -2200,12 +2805,12 @@ CMS Collections: ${cached.cmsPatterns.length}`
2200
2805
  };
2201
2806
  }
2202
2807
  const structure = await response.json();
2203
- if (!fs2.existsSync(pagesDir)) {
2204
- fs2.mkdirSync(pagesDir, { recursive: true });
2808
+ if (!fs5.existsSync(pagesDir)) {
2809
+ fs5.mkdirSync(pagesDir, { recursive: true });
2205
2810
  }
2206
- fs2.writeFileSync(cachePath, JSON.stringify(structure, null, 2), "utf-8");
2811
+ fs5.writeFileSync(cachePath, JSON.stringify(structure, null, 2), "utf-8");
2207
2812
  const contextPath = getContextPath();
2208
- if (contextPath && fs2.existsSync(contextPath)) {
2813
+ if (contextPath && fs5.existsSync(contextPath)) {
2209
2814
  }
2210
2815
  let text = `Page cached: ${slug}
2211
2816
  Path: ${cachePath}
@@ -2273,8 +2878,8 @@ CMS Collections (${structure.cmsPatterns.length}):
2273
2878
  };
2274
2879
  }
2275
2880
  const slug = urlOrSlug.startsWith("http") ? urlToSlug(urlOrSlug) : urlOrSlug;
2276
- const cachePath = path2.join(pagesDir, `${slug}.json`);
2277
- if (!fs2.existsSync(cachePath)) {
2881
+ const cachePath = path5.join(pagesDir, `${slug}.json`);
2882
+ if (!fs5.existsSync(cachePath)) {
2278
2883
  return {
2279
2884
  content: [{
2280
2885
  type: "text",
@@ -2285,7 +2890,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
2285
2890
  isError: true
2286
2891
  };
2287
2892
  }
2288
- const context = JSON.parse(fs2.readFileSync(cachePath, "utf-8"));
2893
+ const context = JSON.parse(fs5.readFileSync(cachePath, "utf-8"));
2289
2894
  const cacheAge = Date.now() - new Date(context.extractedAt).getTime();
2290
2895
  const cacheAgeDays = Math.floor(cacheAge / (1e3 * 60 * 60 * 24));
2291
2896
  const isStale = cacheAgeDays > 7;
@@ -2529,7 +3134,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
2529
3134
  isError: true
2530
3135
  };
2531
3136
  }
2532
- if (!fs2.existsSync(pagesDir)) {
3137
+ if (!fs5.existsSync(pagesDir)) {
2533
3138
  return {
2534
3139
  content: [{
2535
3140
  type: "text",
@@ -2537,7 +3142,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
2537
3142
  }]
2538
3143
  };
2539
3144
  }
2540
- const files = fs2.readdirSync(pagesDir).filter((f) => f.endsWith(".json"));
3145
+ const files = fs5.readdirSync(pagesDir).filter((f) => f.endsWith(".json"));
2541
3146
  if (files.length === 0) {
2542
3147
  return {
2543
3148
  content: [{
@@ -2552,7 +3157,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
2552
3157
  for (const file of files) {
2553
3158
  const slug = file.replace(".json", "");
2554
3159
  try {
2555
- 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"));
2556
3161
  const urlPath = new URL(context.url).pathname;
2557
3162
  text += `${urlPath} [${slug}]
2558
3163
  `;
@@ -2633,8 +3238,8 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
2633
3238
  text += "\n";
2634
3239
  }
2635
3240
  if (updateClaudeMd) {
2636
- const kodeMdPath = path2.join(projectRoot, ".cure-kode", "KODE.md");
2637
- 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");
2638
3243
  const siteName = config.siteName || "Site";
2639
3244
  const siteSlug = config.siteSlug || "site";
2640
3245
  let kodeMd = `# Cure Kode: ${siteName}
@@ -2709,7 +3314,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
2709
3314
  `;
2710
3315
  kodeMd += `**Check for updates:** \`kode doctor\`
2711
3316
  `;
2712
- fs2.writeFileSync(kodeMdPath, kodeMd);
3317
+ fs5.writeFileSync(kodeMdPath, kodeMd);
2713
3318
  text += `Updated .cure-kode/KODE.md with current scripts and pages.
2714
3319
  `;
2715
3320
  const kodeReference = `## Cure Kode
@@ -2725,16 +3330,16 @@ Full documentation: [.cure-kode/KODE.md](.cure-kode/KODE.md)
2725
3330
  ---
2726
3331
 
2727
3332
  `;
2728
- if (fs2.existsSync(claudeMdPath)) {
2729
- const claudeContent = fs2.readFileSync(claudeMdPath, "utf-8");
3333
+ if (fs5.existsSync(claudeMdPath)) {
3334
+ const claudeContent = fs5.readFileSync(claudeMdPath, "utf-8");
2730
3335
  const hasReference = claudeContent.includes("KODE.md") || claudeContent.includes(".cure-kode/KODE.md");
2731
3336
  if (!hasReference) {
2732
- fs2.writeFileSync(claudeMdPath, kodeReference + claudeContent);
3337
+ fs5.writeFileSync(claudeMdPath, kodeReference + claudeContent);
2733
3338
  text += `Added Kode reference to CLAUDE.md.
2734
3339
  `;
2735
3340
  }
2736
3341
  } else {
2737
- fs2.writeFileSync(claudeMdPath, kodeReference);
3342
+ fs5.writeFileSync(claudeMdPath, kodeReference);
2738
3343
  text += `Created CLAUDE.md with Kode reference.
2739
3344
  `;
2740
3345
  }
@@ -2751,10 +3356,11 @@ Full documentation: [.cure-kode/KODE.md](.cure-kode/KODE.md)
2751
3356
  }
2752
3357
  }
2753
3358
  case "kode_library_list": {
2754
- const { category, search } = args;
3359
+ const { category, search, folder } = args;
2755
3360
  const params = new URLSearchParams();
2756
3361
  if (category) params.set("category", category);
2757
3362
  if (search) params.set("search", search);
3363
+ if (folder) params.set("folder", folder);
2758
3364
  const snippets = await client.listLibrary(params.toString() ? `?${params.toString()}` : "");
2759
3365
  return {
2760
3366
  content: [{ type: "text", text: JSON.stringify(snippets, null, 2) }]
@@ -2779,23 +3385,117 @@ Full documentation: [.cure-kode/KODE.md](.cure-kode/KODE.md)
2779
3385
  isError: true
2780
3386
  };
2781
3387
  }
2782
- const filePath = path2.join(scriptsDir, `${fileName}.${ext}`);
2783
- if (fs2.existsSync(filePath)) {
3388
+ const filePath = path5.join(scriptsDir, `${fileName}.${ext}`);
3389
+ if (fs5.existsSync(filePath)) {
2784
3390
  return {
2785
3391
  content: [{ type: "text", text: `File already exists: ${fileName}.${ext}. Use a different newName or delete the existing file.` }],
2786
3392
  isError: true
2787
3393
  };
2788
3394
  }
2789
- if (!fs2.existsSync(scriptsDir)) {
2790
- fs2.mkdirSync(scriptsDir, { recursive: true });
3395
+ if (!fs5.existsSync(scriptsDir)) {
3396
+ fs5.mkdirSync(scriptsDir, { recursive: true });
2791
3397
  }
2792
- fs2.writeFileSync(filePath, snippet.code);
3398
+ fs5.writeFileSync(filePath, snippet.code);
2793
3399
  return {
2794
3400
  content: [{ type: "text", text: `Copied "${snippet.name}" to ${fileName}.${ext}
2795
3401
 
2796
3402
  Run \`kode push\` to upload to CDN.` }]
2797
3403
  };
2798
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
+ }
2799
3499
  default:
2800
3500
  return {
2801
3501
  content: [{ type: "text", text: `Unknown tool: ${name}` }],
@@ -2816,14 +3516,15 @@ Run \`kode push\` to upload to CDN.` }]
2816
3516
  });
2817
3517
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
2818
3518
  const scriptsDir = getScriptsDir();
2819
- if (!scriptsDir || !fs2.existsSync(scriptsDir)) {
3519
+ if (!scriptsDir || !fs5.existsSync(scriptsDir)) {
2820
3520
  return { resources: [] };
2821
3521
  }
2822
- const files = fs2.readdirSync(scriptsDir);
2823
- const resources = files.filter((f) => f.endsWith(".js") || f.endsWith(".css")).map((f) => ({
2824
- uri: `file://${path2.join(scriptsDir, f)}`,
2825
- name: f,
2826
- 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)
2827
3528
  }));
2828
3529
  return { resources };
2829
3530
  });
@@ -2833,11 +3534,11 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
2833
3534
  throw new Error("Invalid resource URI");
2834
3535
  }
2835
3536
  const filePath = uri.replace("file://", "");
2836
- if (!fs2.existsSync(filePath)) {
3537
+ if (!fs5.existsSync(filePath)) {
2837
3538
  throw new Error("File not found");
2838
3539
  }
2839
- const content = fs2.readFileSync(filePath, "utf-8");
2840
- const mimeType = filePath.endsWith(".js") ? "application/javascript" : "text/css";
3540
+ const content = fs5.readFileSync(filePath, "utf-8");
3541
+ const mimeType = mimeTypeFromFileName(filePath);
2841
3542
  return {
2842
3543
  contents: [
2843
3544
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curenorway/kode-mcp",
3
- "version": "1.8.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",