untitledui-mcp 0.1.1 → 0.1.3

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 (3) hide show
  1. package/README.md +15 -5
  2. package/dist/index.js +158 -23
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -74,7 +74,8 @@ Your AI gets these tools:
74
74
  | `list_components` | Browse a category |
75
75
  | `get_component_with_deps` | Fetch component + all dependencies |
76
76
  | `get_component` | Fetch component only |
77
- | `get_example` | Get complete page template |
77
+ | `list_examples` | Browse available page templates |
78
+ | `get_example` | Fetch a specific page template |
78
79
 
79
80
  ### Example: Add a modal
80
81
 
@@ -95,13 +96,22 @@ AI calls list_components { type: "application", subfolder: "sidebars" }
95
96
  → Returns all sidebar options for you to choose from
96
97
  ```
97
98
 
98
- ### Example: Start from template
99
+ ### Example: Start from a page template
99
100
 
100
101
  ```
101
- You: "Set up a dashboard layout"
102
+ You: "Show me available dashboard templates"
102
103
 
103
- AI calls get_example { name: "application" }
104
- → Returns complete dashboard with sidebar, header, and sample pages
104
+ AI calls list_examples { path: "" }
105
+ → Returns: application, marketing
106
+
107
+ AI calls list_examples { path: "application" }
108
+ → Returns: dashboards-01, dashboards-02, settings-01, ...
109
+
110
+ AI calls list_examples { path: "application/dashboards-01" }
111
+ → Returns: 01, 02, 03, ... (individual pages)
112
+
113
+ AI calls get_example { path: "application/dashboards-01/01" }
114
+ → Returns complete page with 27 files, all dependencies
105
115
  ```
106
116
 
107
117
  ## Response Format
package/dist/index.js CHANGED
@@ -126,17 +126,60 @@ var UntitledUIClient = class {
126
126
  }
127
127
  return data.components;
128
128
  }
129
- async fetchExample(name) {
129
+ /**
130
+ * Browse examples hierarchy or fetch example content.
131
+ * - "" → lists types (application, marketing)
132
+ * - "application" → lists examples (dashboards-01, settings-01, etc.)
133
+ * - "application/dashboards-01" → lists pages (01, 02, 03, ...)
134
+ * - "application/dashboards-01/01" → returns actual content
135
+ */
136
+ async fetchExample(path2) {
130
137
  const response = await fetch(ENDPOINTS.fetchExample, {
131
138
  method: "POST",
132
139
  headers: { "Content-Type": "application/json" },
133
140
  body: JSON.stringify({
134
- example: name,
141
+ example: path2,
135
142
  key: this.licenseKey
136
143
  })
137
144
  });
138
145
  return response.json();
139
146
  }
147
+ /**
148
+ * Fetch all pages in an example and combine them.
149
+ * Path should be "application/dashboards-01" (without page number).
150
+ */
151
+ async fetchFullExample(examplePath) {
152
+ const listing = await this.fetchExample(examplePath);
153
+ if (listing.type !== "directory" || !listing.results) {
154
+ throw new Error(`Invalid example path: ${examplePath}. Expected a directory with pages.`);
155
+ }
156
+ const pages = listing.results;
157
+ const allDeps = /* @__PURE__ */ new Set();
158
+ const allDevDeps = /* @__PURE__ */ new Set();
159
+ const fetchedPages = [];
160
+ for (const page of pages) {
161
+ const pageData = await this.fetchExample(`${examplePath}/${page}`);
162
+ if (pageData.type === "json-file" && pageData.content) {
163
+ const content = pageData.content;
164
+ fetchedPages.push({
165
+ page,
166
+ files: content.files || [],
167
+ dependencies: content.dependencies || [],
168
+ devDependencies: content.devDependencies || []
169
+ });
170
+ content.dependencies?.forEach((d) => allDeps.add(d));
171
+ content.devDependencies?.forEach((d) => allDevDeps.add(d));
172
+ }
173
+ }
174
+ const totalFiles = fetchedPages.reduce((sum, p) => sum + p.files.length, 0);
175
+ return {
176
+ name: examplePath,
177
+ pages: fetchedPages,
178
+ allDependencies: Array.from(allDeps),
179
+ allDevDependencies: Array.from(allDevDeps),
180
+ totalFiles
181
+ };
182
+ }
140
183
  };
141
184
 
142
185
  // src/cache/memory-cache.ts
@@ -289,6 +332,52 @@ function generateDescription(name, type) {
289
332
  return `UI component: ${name.replace(/-/g, " ")}`;
290
333
  }
291
334
 
335
+ // src/utils/parse-deps.ts
336
+ function parseComponentImports(files) {
337
+ const imports = /* @__PURE__ */ new Set();
338
+ const regex = /@\/components\/(base|application|foundations|shared-assets)\/([^"']+)/g;
339
+ for (const file of files) {
340
+ const code = file.content || file.code || "";
341
+ let match;
342
+ while ((match = regex.exec(code)) !== null) {
343
+ imports.add(match[0]);
344
+ }
345
+ }
346
+ const parsed = [];
347
+ for (const imp of imports) {
348
+ const match = imp.match(/@\/components\/(base|application|foundations|shared-assets)\/(.+)/);
349
+ if (match) {
350
+ parsed.push({
351
+ type: match[1],
352
+ path: match[2],
353
+ fullImport: imp
354
+ });
355
+ }
356
+ }
357
+ return parsed;
358
+ }
359
+ function extractBaseComponentPaths(files) {
360
+ const deps = parseComponentImports(files);
361
+ const basePaths = /* @__PURE__ */ new Set();
362
+ for (const dep of deps) {
363
+ if (dep.type === "base") {
364
+ basePaths.add(dep.path);
365
+ }
366
+ }
367
+ return Array.from(basePaths);
368
+ }
369
+ function getBaseComponentNames(files) {
370
+ const paths = extractBaseComponentPaths(files);
371
+ const names = /* @__PURE__ */ new Set();
372
+ for (const path2 of paths) {
373
+ const parts = path2.split("/");
374
+ if (parts.length >= 1) {
375
+ names.add(parts[0]);
376
+ }
377
+ }
378
+ return Array.from(names);
379
+ }
380
+
292
381
  // src/server.ts
293
382
  function createServer(licenseKey) {
294
383
  const client = new UntitledUIClient(licenseKey);
@@ -390,18 +479,23 @@ function createServer(licenseKey) {
390
479
  },
391
480
  {
392
481
  name: "list_examples",
393
- description: "List available page examples (dashboards, marketing pages, etc.)",
394
- inputSchema: { type: "object", properties: {} }
482
+ description: "Browse available page examples. Call without path to see categories, then drill down.",
483
+ inputSchema: {
484
+ type: "object",
485
+ properties: {
486
+ path: { type: "string", description: "Path to browse (e.g., '', 'application', 'application/dashboards-01')" }
487
+ }
488
+ }
395
489
  },
396
490
  {
397
491
  name: "get_example",
398
- description: "Get a complete page example with all files",
492
+ description: "Get a single example page with all files. Requires full path including page number.",
399
493
  inputSchema: {
400
494
  type: "object",
401
495
  properties: {
402
- name: { type: "string", description: "Example name (e.g., 'application', 'marketing')" }
496
+ path: { type: "string", description: "Full path to example page (e.g., 'application/dashboards-01/01')" }
403
497
  },
404
- required: ["name"]
498
+ required: ["path"]
405
499
  }
406
500
  },
407
501
  {
@@ -477,7 +571,7 @@ function createServer(licenseKey) {
477
571
  files: fetched.files,
478
572
  dependencies: fetched.dependencies || [],
479
573
  devDependencies: fetched.devDependencies || [],
480
- baseComponents: (fetched.components || []).map((c) => c.name)
574
+ baseComponents: getBaseComponentNames(fetched.files)
481
575
  };
482
576
  cache.set(cacheKey, component, CACHE_TTL.componentCode);
483
577
  }
@@ -500,7 +594,7 @@ function createServer(licenseKey) {
500
594
  }]
501
595
  };
502
596
  }
503
- const baseComponentNames = (primary.components || []).map((c) => c.name);
597
+ const baseComponentNames = getBaseComponentNames(primary.files);
504
598
  const baseComponents = baseComponentNames.length > 0 ? await client.fetchComponents("base", baseComponentNames) : [];
505
599
  const allDeps = /* @__PURE__ */ new Set();
506
600
  const allDevDeps = /* @__PURE__ */ new Set();
@@ -533,28 +627,69 @@ function createServer(licenseKey) {
533
627
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
534
628
  }
535
629
  case "list_examples": {
536
- return {
537
- content: [{
538
- type: "text",
539
- text: JSON.stringify({
540
- examples: [
541
- { name: "application", type: "application", description: "Dashboard application example" },
542
- { name: "marketing", type: "marketing", description: "Marketing landing page example" }
543
- ]
544
- }, null, 2)
545
- }]
546
- };
630
+ const { path: path2 = "" } = args;
631
+ const cacheKey = `examples:list:${path2}`;
632
+ let listing = cache.get(cacheKey);
633
+ if (!listing) {
634
+ listing = await client.fetchExample(path2);
635
+ if (listing) {
636
+ cache.set(cacheKey, listing, CACHE_TTL.componentList);
637
+ }
638
+ }
639
+ if (listing.type === "directory" && listing.results) {
640
+ return {
641
+ content: [{
642
+ type: "text",
643
+ text: JSON.stringify({
644
+ path: path2 || "(root)",
645
+ type: "directory",
646
+ items: listing.results,
647
+ hint: path2 === "" ? "Use list_examples with path='application' or path='marketing' to see available examples" : path2.split("/").length === 1 ? `Use list_examples with path='${path2}/<example>' to see pages` : `Use get_example with path='${path2}/<page>' to fetch a specific page`
648
+ }, null, 2)
649
+ }]
650
+ };
651
+ }
652
+ return { content: [{ type: "text", text: JSON.stringify(listing, null, 2) }] };
547
653
  }
548
654
  case "get_example": {
549
- const { name: exampleName } = args;
550
- const cacheKey = `example:${exampleName}`;
655
+ const { path: examplePath } = args;
656
+ const cacheKey = `example:${examplePath}`;
551
657
  let example = cache.get(cacheKey);
552
658
  if (!example) {
553
- example = await client.fetchExample(exampleName);
659
+ example = await client.fetchExample(examplePath);
554
660
  if (example) {
555
661
  cache.set(cacheKey, example, CACHE_TTL.examples);
556
662
  }
557
663
  }
664
+ if (example.type === "directory") {
665
+ return {
666
+ content: [{
667
+ type: "text",
668
+ text: JSON.stringify({
669
+ error: "Path is a directory, not a page",
670
+ path: examplePath,
671
+ availableItems: example.results,
672
+ hint: `Use get_example with path='${examplePath}/<item>' to fetch a specific page`
673
+ }, null, 2)
674
+ }]
675
+ };
676
+ }
677
+ if (example.type === "json-file" && example.content) {
678
+ return {
679
+ content: [{
680
+ type: "text",
681
+ text: JSON.stringify({
682
+ path: examplePath,
683
+ name: example.content.name,
684
+ files: example.content.files,
685
+ dependencies: example.content.dependencies || [],
686
+ devDependencies: example.content.devDependencies || [],
687
+ components: example.content.components || [],
688
+ fileCount: example.content.files?.length || 0
689
+ }, null, 2)
690
+ }]
691
+ };
692
+ }
558
693
  return { content: [{ type: "text", text: JSON.stringify(example, null, 2) }] };
559
694
  }
560
695
  case "validate_license": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "untitledui-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "MCP server for UntitledUI Pro components - browse, search, and retrieve UI components via Claude Code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",