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.
- package/dist/index.js +131 -6
- 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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
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