untitledui-mcp 0.1.3 → 0.1.5

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 +113 -29
  2. package/dist/index.js +131 -6
  3. package/package.json +7 -2
package/README.md CHANGED
@@ -10,9 +10,32 @@ AI tools can generate functional UI, but the result often lacks the polish and c
10
10
 
11
11
  Instead of generating a modal from patterns it's seen, your AI fetches the actual UntitledUI modal with all its carefully crafted details intact.
12
12
 
13
- ## Setup
13
+ ## Quick Start
14
14
 
15
- ### Add MCP to your tool
15
+ ### 1. Start with a UntitledUI Starter Kit
16
+
17
+ UntitledUI components require specific Tailwind configuration, design tokens, and providers. The easiest way to get started is with an official starter kit:
18
+
19
+ **Next.js:**
20
+ ```bash
21
+ git clone https://github.com/untitleduico/untitledui-nextjs-starter-kit my-app
22
+ cd my-app && npm install
23
+ ```
24
+
25
+ **Vite:**
26
+ ```bash
27
+ git clone https://github.com/untitleduico/untitledui-vite-starter-kit my-app
28
+ cd my-app && npm install
29
+ ```
30
+
31
+ These starter kits come pre-configured with:
32
+ - Tailwind CSS with all UntitledUI design tokens
33
+ - Theme provider for light/dark mode
34
+ - Toast notifications
35
+ - Routing setup
36
+ - All required dependencies
37
+
38
+ ### 2. Add the MCP Server
16
39
 
17
40
  **Claude Code:**
18
41
  ```bash
@@ -25,7 +48,7 @@ claude mcp add untitledui -- npx untitledui-mcp
25
48
  "mcpServers": {
26
49
  "untitledui": {
27
50
  "command": "npx",
28
- "args": ["untitledui-mcp"]
51
+ "args": ["-y", "untitledui-mcp"]
29
52
  }
30
53
  }
31
54
  }
@@ -33,7 +56,7 @@ claude mcp add untitledui -- npx untitledui-mcp
33
56
 
34
57
  This gives you access to **base components** (button, input, select, avatar, badge, etc.) without authentication.
35
58
 
36
- ### For Pro components (optional)
59
+ ### 3. Authenticate for Pro Components (optional)
37
60
 
38
61
  To access application components (modals, sidebars, tables, dashboards) and marketing sections, authenticate with your UntitledUI Pro license:
39
62
 
@@ -49,7 +72,7 @@ Alternatively, set the key manually in your MCP config:
49
72
  "mcpServers": {
50
73
  "untitledui": {
51
74
  "command": "npx",
52
- "args": ["untitledui-mcp"],
75
+ "args": ["-y", "untitledui-mcp"],
53
76
  "env": {
54
77
  "UNTITLEDUI_LICENSE_KEY": "<your-key>"
55
78
  }
@@ -58,13 +81,26 @@ Alternatively, set the key manually in your MCP config:
58
81
  }
59
82
  ```
60
83
 
61
- ### Verify setup
84
+ ### 4. Verify Setup
62
85
 
63
86
  ```bash
64
87
  npx untitledui-mcp --test
65
88
  ```
66
89
 
67
- ## How It Works
90
+ ## Recommended Workflow
91
+
92
+ 1. **Start with a starter kit** — Don't try to add UntitledUI components to an existing project without the proper Tailwind setup. The starter kits handle all configuration.
93
+
94
+ 2. **Ask your AI to fetch components** — Once your project is set up, ask your AI to add specific components:
95
+ - "Add a settings modal"
96
+ - "I need a sidebar navigation"
97
+ - "Add the user profile dropdown"
98
+
99
+ 3. **Build complete pages from examples** — For larger features, start with a page template:
100
+ - "Show me available dashboard templates"
101
+ - "Fetch the landing page example"
102
+
103
+ ## Available Tools
68
104
 
69
105
  Your AI gets these tools:
70
106
 
@@ -74,44 +110,70 @@ Your AI gets these tools:
74
110
  | `list_components` | Browse a category |
75
111
  | `get_component_with_deps` | Fetch component + all dependencies |
76
112
  | `get_component` | Fetch component only |
113
+ | `get_component_file` | Fetch a single file (for large components) |
77
114
  | `list_examples` | Browse available page templates |
78
- | `get_example` | Fetch a specific page template |
115
+ | `get_example` | Fetch a complete page template |
79
116
 
80
- ### Example: Add a modal
117
+ ## What You Can Do
118
+
119
+ ### Recreate Any UI from a Screenshot
81
120
 
82
121
  ```
83
- You: "Add a settings modal"
122
+ You: [paste screenshot] "Recreate this with UntitledUI components"
84
123
 
85
- AI searchesfinds application/modals/settings-modal
86
- AI fetches gets modal + button, input, select base components
87
- AI addsplaces files in your project with correct imports
124
+ AI analyzes the design identifies matching components
125
+ AI fetches sidebar, header, cards, tables all with dependencies
126
+ AI assemblesproduction-ready page matching your screenshot
88
127
  ```
89
128
 
90
- ### Example: Browse options
129
+ ### Build Complete Pages in Seconds
91
130
 
92
131
  ```
93
- You: "What sidebar variants are there?"
132
+ You: "Build me a SaaS pricing page with 3 tiers and a FAQ section"
94
133
 
95
- AI calls list_components { type: "application", subfolder: "sidebars" }
96
- Returns all sidebar options for you to choose from
134
+ AI fetches marketing/pricing-sections + marketing/faq-sections
135
+ AI combines components complete pricing page with toggle for monthly/annual
136
+ Result: Professional pricing page with all interactions working
97
137
  ```
98
138
 
99
- ### Example: Start from a page template
139
+ ### Set Up Your Entire App Shell
100
140
 
101
141
  ```
102
- You: "Show me available dashboard templates"
142
+ You: "Set up the main app layout with collapsible sidebar and header with user dropdown"
103
143
 
104
- AI calls list_examples { path: "" }
105
- Returns: application, marketing
144
+ AI fetches application/sidebars + application/headers + base components
145
+ AI wires up navigation state, theme toggle, user menu
146
+ Result: Complete app shell ready for your content
147
+ ```
106
148
 
107
- AI calls list_examples { path: "application" }
108
- → Returns: dashboards-01, dashboards-02, settings-01, ...
149
+ ### Clone a Page from UntitledUI's Templates
109
150
 
110
- AI calls list_examples { path: "application/dashboards-01" }
111
- → Returns: 01, 02, 03, ... (individual pages)
151
+ ```
152
+ You: "I want a dashboard like the one in dashboards-01"
112
153
 
113
154
  AI calls get_example { path: "application/dashboards-01/01" }
114
- → Returns complete page with 27 files, all dependencies
155
+ → Returns 27 files: page layout, charts, tables, cards, all base components
156
+ → Ready to customize with your data
157
+ ```
158
+
159
+ ### Mix and Match Components
160
+
161
+ ```
162
+ You: "Create a settings page with sections for profile, notifications, billing, and team members"
163
+
164
+ AI searches → finds matching components for each section
165
+ AI fetches settings panels, forms, tables, modals
166
+ AI composes → cohesive settings page with consistent styling
167
+ ```
168
+
169
+ ### Rapid Feature Development
170
+
171
+ ```
172
+ You: "Add a command palette like Linear/Notion with keyboard shortcut"
173
+
174
+ AI fetches application/command-menus
175
+ → Complete command palette with search, keyboard navigation, sections
176
+ → Just wire up your actions
115
177
  ```
116
178
 
117
179
  ## Response Format
@@ -127,22 +189,44 @@ AI calls get_example { path: "application/dashboards-01/01" }
127
189
  { "name": "button", "files": [...] },
128
190
  { "name": "input", "files": [...] }
129
191
  ],
130
- "allDependencies": ["@headlessui/react", "clsx"]
192
+ "allDependencies": ["@headlessui/react", "clsx"],
193
+ "estimatedTokens": 12500,
194
+ "fileList": [
195
+ { "path": "settings-modal.tsx", "tokens": 850 },
196
+ { "path": "button/button.tsx", "tokens": 420 }
197
+ ]
131
198
  }
132
199
  ```
133
200
 
201
+ Responses include token estimates to help AI agents manage context limits. For very large responses (>25K tokens), the AI can use `get_component_file` to fetch individual files.
202
+
203
+ ## Troubleshooting
204
+
205
+ ### Components don't look right / missing styles
206
+
207
+ UntitledUI components use custom Tailwind classes like `bg-primary`, `text-display-md`, and design tokens that aren't part of standard Tailwind.
208
+
209
+ **Solution:** Use a [UntitledUI starter kit](#1-start-with-a-untitledui-starter-kit). The starter kits include all required Tailwind configuration and design tokens.
210
+
211
+ ### Import errors for base components
212
+
213
+ Pro components depend on base components (button, input, etc.). When fetching a component with `get_component_with_deps`, all dependencies are included automatically.
214
+
215
+ **Solution:** Make sure you're using `get_component_with_deps` instead of `get_component` for Pro components.
216
+
134
217
  ## Requirements
135
218
 
136
219
  - Node.js 18+
137
- - UntitledUI Pro license (only for Pro components)
220
+ - UntitledUI Pro license (for Pro components only)
138
221
 
139
222
  ## Credits
140
223
 
141
- [UntitledUI](https://www.untitledui.com) is created by [Jordan Hughes](https://jordanhughes.co).
224
+ [UntitledUI](https://www.untitledui.com?utm_source=untitledui-mcp&utm_medium=github&utm_campaign=readme) is created by [Jordan Hughes](https://jordanhughes.co?utm_source=untitledui-mcp&utm_medium=github&utm_campaign=readme).
142
225
 
143
226
  - [Twitter/X](https://x.com/jordanphughes)
144
227
  - [Dribbble](https://dribbble.com/jordanhughes)
145
228
  - [UntitledUI on X](https://x.com/UntitledUI)
229
+ - [Get UntitledUI Pro](https://www.untitledui.com/pricing?utm_source=untitledui-mcp&utm_medium=github&utm_campaign=readme)
146
230
 
147
231
  ## License
148
232
 
package/dist/index.js CHANGED
@@ -378,6 +378,28 @@ function getBaseComponentNames(files) {
378
378
  return Array.from(names);
379
379
  }
380
380
 
381
+ // src/utils/tokens.ts
382
+ function estimateTokens(content) {
383
+ return Math.ceil(content.length / 4);
384
+ }
385
+ function estimateFileTokens(file) {
386
+ const content = file.code || file.content || "";
387
+ return estimateTokens(content);
388
+ }
389
+ function estimateComponentTokens(files) {
390
+ return files.reduce((sum, file) => sum + estimateFileTokens(file), 0);
391
+ }
392
+ function getFileTokenList(files) {
393
+ return files.map((file) => ({
394
+ path: file.path,
395
+ tokens: estimateFileTokens(file)
396
+ }));
397
+ }
398
+ var CLAUDE_READ_TOKEN_LIMIT = 25e3;
399
+ function isLikelyTooLarge(estimatedTokens) {
400
+ return estimatedTokens > CLAUDE_READ_TOKEN_LIMIT;
401
+ }
402
+
381
403
  // src/server.ts
382
404
  function createServer(licenseKey) {
383
405
  const client = new UntitledUIClient(licenseKey);
@@ -455,7 +477,7 @@ function createServer(licenseKey) {
455
477
  },
456
478
  {
457
479
  name: "get_component",
458
- description: "Get a single component's code. Does NOT include dependencies - use get_component_with_deps for that.",
480
+ description: "Get a single component's code with token estimates. Returns estimatedTokens and file list. If estimatedTokens > 25000, consider using get_component_file for specific files instead.",
459
481
  inputSchema: {
460
482
  type: "object",
461
483
  properties: {
@@ -467,7 +489,7 @@ function createServer(licenseKey) {
467
489
  },
468
490
  {
469
491
  name: "get_component_with_deps",
470
- description: "Get a component with all its base component dependencies included",
492
+ description: "Get a component with all base dependencies. Returns estimatedTokens. If response is too large (>25000 tokens), use get_component_file to fetch specific files individually.",
471
493
  inputSchema: {
472
494
  type: "object",
473
495
  properties: {
@@ -477,6 +499,19 @@ function createServer(licenseKey) {
477
499
  required: ["type", "name"]
478
500
  }
479
501
  },
502
+ {
503
+ name: "get_component_file",
504
+ description: "Get a single file from a component. Use this when get_component or get_component_with_deps returns a large response (>25000 tokens). First call get_component to see the file list, then fetch specific files as needed.",
505
+ inputSchema: {
506
+ type: "object",
507
+ properties: {
508
+ type: { type: "string", description: "Component type" },
509
+ name: { type: "string", description: "Component name" },
510
+ file: { type: "string", description: "File path within the component (e.g., 'Button.tsx' or 'variants/InputPhone.tsx')" }
511
+ },
512
+ required: ["type", "name", "file"]
513
+ }
514
+ },
480
515
  {
481
516
  name: "list_examples",
482
517
  description: "Browse available page examples. Call without path to see categories, then drill down.",
@@ -575,7 +610,20 @@ function createServer(licenseKey) {
575
610
  };
576
611
  cache.set(cacheKey, component, CACHE_TTL.componentCode);
577
612
  }
578
- return { content: [{ type: "text", text: JSON.stringify(component, null, 2) }] };
613
+ const estimatedTokens = estimateComponentTokens(component.files);
614
+ const fileTokens = getFileTokenList(component.files);
615
+ const tooLarge = isLikelyTooLarge(estimatedTokens);
616
+ const result = {
617
+ ...component,
618
+ estimatedTokens,
619
+ fileCount: component.files.length,
620
+ fileList: fileTokens,
621
+ ...tooLarge && {
622
+ warning: `Response is large (${estimatedTokens} tokens). Consider using get_component_file for specific files.`,
623
+ hint: "Use get_component_file with type, name, and file path to fetch individual files."
624
+ }
625
+ };
626
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
579
627
  }
580
628
  case "get_component_with_deps": {
581
629
  const { type, name: componentName } = args;
@@ -602,12 +650,19 @@ function createServer(licenseKey) {
602
650
  c.dependencies?.forEach((d) => allDeps.add(d));
603
651
  c.devDependencies?.forEach((d) => allDevDeps.add(d));
604
652
  });
653
+ const allFiles = [
654
+ ...primary.files,
655
+ ...baseComponents.flatMap((c) => c.files)
656
+ ];
657
+ const estimatedTokens = estimateComponentTokens(allFiles);
658
+ const tooLarge = isLikelyTooLarge(estimatedTokens);
605
659
  const result = {
606
660
  primary: {
607
661
  name: primary.name,
608
662
  type,
609
663
  description: generateDescription(primary.name, type),
610
664
  files: primary.files,
665
+ fileList: getFileTokenList(primary.files),
611
666
  dependencies: primary.dependencies || [],
612
667
  devDependencies: primary.devDependencies || [],
613
668
  baseComponents: baseComponentNames
@@ -617,15 +672,77 @@ function createServer(licenseKey) {
617
672
  type: "base",
618
673
  description: generateDescription(c.name, "base"),
619
674
  files: c.files,
675
+ fileList: getFileTokenList(c.files),
620
676
  dependencies: c.dependencies || [],
621
677
  devDependencies: c.devDependencies || []
622
678
  })),
623
679
  totalFiles: primary.files.length + baseComponents.reduce((sum, c) => sum + c.files.length, 0),
680
+ estimatedTokens,
624
681
  allDependencies: Array.from(allDeps),
625
- allDevDependencies: Array.from(allDevDeps)
682
+ allDevDependencies: Array.from(allDevDeps),
683
+ ...tooLarge && {
684
+ warning: `Response is large (${estimatedTokens} tokens, limit is ${CLAUDE_READ_TOKEN_LIMIT}). Consider using get_component_file for specific files.`,
685
+ hint: "To fetch individual files, use get_component_file with the component type, name, and file path from fileList."
686
+ }
626
687
  };
627
688
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
628
689
  }
690
+ case "get_component_file": {
691
+ const { type, name: componentName, file: filePath } = args;
692
+ const cacheKey = `component:${type}:${componentName}`;
693
+ let files;
694
+ const cached = cache.get(cacheKey);
695
+ if (cached) {
696
+ files = cached.files;
697
+ } else {
698
+ const fetched = await client.fetchComponent(type, componentName);
699
+ if (!fetched) {
700
+ const index = await buildSearchIndex();
701
+ const suggestions = fuzzySearch(componentName, index, 5).map((r) => r.fullPath);
702
+ return {
703
+ content: [{
704
+ type: "text",
705
+ text: JSON.stringify({
706
+ error: `Component "${componentName}" not found`,
707
+ code: "NOT_FOUND",
708
+ suggestions
709
+ }, null, 2)
710
+ }]
711
+ };
712
+ }
713
+ files = fetched.files;
714
+ }
715
+ const file = files.find(
716
+ (f) => f.path === filePath || f.path.endsWith(`/${filePath}`) || f.path.endsWith(filePath)
717
+ );
718
+ if (!file) {
719
+ const availableFiles = files.map((f) => f.path);
720
+ return {
721
+ content: [{
722
+ type: "text",
723
+ text: JSON.stringify({
724
+ error: `File "${filePath}" not found in ${type}/${componentName}`,
725
+ code: "FILE_NOT_FOUND",
726
+ availableFiles,
727
+ hint: "Use one of the file paths from availableFiles"
728
+ }, null, 2)
729
+ }]
730
+ };
731
+ }
732
+ return {
733
+ content: [{
734
+ type: "text",
735
+ text: JSON.stringify({
736
+ component: `${type}/${componentName}`,
737
+ file: {
738
+ path: file.path,
739
+ code: file.code
740
+ },
741
+ estimatedTokens: estimateComponentTokens([file])
742
+ }, null, 2)
743
+ }]
744
+ };
745
+ }
629
746
  case "list_examples": {
630
747
  const { path: path2 = "" } = args;
631
748
  const cacheKey = `examples:list:${path2}`;
@@ -675,17 +792,25 @@ function createServer(licenseKey) {
675
792
  };
676
793
  }
677
794
  if (example.type === "json-file" && example.content) {
795
+ const files = example.content.files || [];
796
+ const estimatedTokens = estimateComponentTokens(files);
797
+ const tooLarge = isLikelyTooLarge(estimatedTokens);
678
798
  return {
679
799
  content: [{
680
800
  type: "text",
681
801
  text: JSON.stringify({
682
802
  path: examplePath,
683
803
  name: example.content.name,
684
- files: example.content.files,
804
+ files,
805
+ fileList: getFileTokenList(files),
685
806
  dependencies: example.content.dependencies || [],
686
807
  devDependencies: example.content.devDependencies || [],
687
808
  components: example.content.components || [],
688
- fileCount: example.content.files?.length || 0
809
+ fileCount: files.length,
810
+ estimatedTokens,
811
+ ...tooLarge && {
812
+ warning: `Response is large (${estimatedTokens} tokens). Consider fetching specific component files instead.`
813
+ }
689
814
  }, null, 2)
690
815
  }]
691
816
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "untitledui-mcp",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
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",
@@ -25,7 +25,12 @@
25
25
  "test:watch": "vitest",
26
26
  "start": "node dist/index.js"
27
27
  },
28
- "keywords": ["mcp", "untitledui", "ui-components", "claude"],
28
+ "keywords": [
29
+ "mcp",
30
+ "untitledui",
31
+ "ui-components",
32
+ "claude"
33
+ ],
29
34
  "author": "Steffen Bilde",
30
35
  "license": "MIT",
31
36
  "dependencies": {