untitledui-mcp 0.1.3 → 0.1.4

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 +131 -6
  2. package/package.json +1 -1
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.4",
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",