@fragments-sdk/mcp 0.7.0 → 0.7.1

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.
@@ -4,12 +4,13 @@ import {
4
4
  } from "./chunk-4SVS3AA3.js";
5
5
  import {
6
6
  componentNames,
7
+ findComponent,
7
8
  findComponentByName,
8
9
  getGuidanceWhen,
9
10
  getGuidanceWhenNot,
10
11
  listBlocks,
11
12
  listComponents
12
- } from "./chunk-3IHXJEMM.js";
13
+ } from "./chunk-YSRGQDEB.js";
13
14
 
14
15
  // src/server.ts
15
16
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -19,7 +20,7 @@ import {
19
20
  ListToolsRequestSchema
20
21
  } from "@modelcontextprotocol/sdk/types.js";
21
22
  import { existsSync as existsSync9 } from "fs";
22
- import { readFileSync as readFileSync7 } from "fs";
23
+ import { readFileSync as readFileSync6 } from "fs";
23
24
  import { join as join8 } from "path";
24
25
  import { fileURLToPath } from "url";
25
26
 
@@ -653,17 +654,20 @@ function buildLocalSearchData(data, indexes) {
653
654
  }
654
655
  async function buildImportStatements(components, resolvePackageName) {
655
656
  const grouped = /* @__PURE__ */ new Map();
656
- for (const component of components) {
657
- if (!component) continue;
658
- const packageName = await resolvePackageName(component);
657
+ const uniqueComponents = [...new Set(components.filter(Boolean))];
658
+ const resolvedPackages = await Promise.all(
659
+ uniqueComponents.map(async (component) => ({
660
+ component,
661
+ packageName: await resolvePackageName(component)
662
+ }))
663
+ );
664
+ for (const { component, packageName } of resolvedPackages) {
659
665
  const existing = grouped.get(packageName);
660
666
  if (!existing) {
661
667
  grouped.set(packageName, [component]);
662
668
  continue;
663
669
  }
664
- if (!existing.includes(component)) {
665
- existing.push(component);
666
- }
670
+ existing.push(component);
667
671
  }
668
672
  return Array.from(grouped.entries()).map(
669
673
  ([packageName, componentNames2]) => `import { ${componentNames2.join(", ")} } from '${packageName}';`
@@ -687,6 +691,56 @@ function limitTokensPerCategory(categories, limit) {
687
691
  return { categories: limited, total };
688
692
  }
689
693
 
694
+ // src/search-helpers.ts
695
+ var STOP_WORDS = /* @__PURE__ */ new Set([
696
+ "a",
697
+ "an",
698
+ "and",
699
+ "bar",
700
+ "build",
701
+ "button",
702
+ "for",
703
+ "form",
704
+ "i",
705
+ "login",
706
+ "me",
707
+ "need",
708
+ "of",
709
+ "or",
710
+ "the",
711
+ "to",
712
+ "use",
713
+ "with"
714
+ ]);
715
+ function normalizeTerms(value) {
716
+ return value.toLowerCase().split(/[^a-z0-9]+/g).filter((term) => term.length > 1 && !STOP_WORDS.has(term));
717
+ }
718
+ function hasDirectQueryOverlap(query, component) {
719
+ const queryTerms = normalizeTerms(query);
720
+ if (queryTerms.length === 0) return true;
721
+ const haystack = new Set(
722
+ normalizeTerms(
723
+ [
724
+ component.name,
725
+ component.description,
726
+ component.category,
727
+ component.tags.join(" "),
728
+ getGuidanceWhen(component).join(" "),
729
+ getGuidanceWhenNot(component).join(" "),
730
+ component.propsSummary.join(" ")
731
+ ].join(" ")
732
+ )
733
+ );
734
+ return queryTerms.some((term) => haystack.has(term));
735
+ }
736
+ function getRankingBonus(component) {
737
+ let bonus = 0;
738
+ if (component.isCanonical) bonus += 30;
739
+ if (component.tier === "core") bonus += 20;
740
+ if (component.status === "stable") bonus += 5;
741
+ return bonus;
742
+ }
743
+
690
744
  // src/tools/discover.ts
691
745
  function renderContextMarkdown(args) {
692
746
  const lines = ["# Design System Context", ""];
@@ -769,11 +823,13 @@ var discoverHandler = async (args, ctx) => {
769
823
  const compact = args?.compact ?? false;
770
824
  const includeCode = args?.includeCode ?? false;
771
825
  const includeRelations = args?.includeRelations ?? false;
826
+ const depth = args?.depth ?? "quick";
772
827
  const suggestLimit = typeof args?.limit === "number" ? Math.min(Math.max(args.limit, 1), 25) : 10;
773
828
  const listLimit = typeof args?.limit === "number" ? Math.min(Math.max(args.limit, 1), 25) : void 0;
774
829
  const verbosity = args?.verbosity ?? (compact ? "compact" : "standard");
775
- if (args?.format && !useCase && !componentForAlts) {
776
- let components2 = listComponents(data.snapshot);
830
+ const allSnapshotComponents = listComponents(data.snapshot);
831
+ if (!useCase && !componentForAlts && (args?.format || includeCode || includeRelations)) {
832
+ let components2 = allSnapshotComponents;
777
833
  const allBlocks = listBlocks(data.snapshot);
778
834
  if (category) {
779
835
  components2 = components2.filter(
@@ -781,12 +837,16 @@ var discoverHandler = async (args, ctx) => {
781
837
  );
782
838
  }
783
839
  if (search2) {
784
- components2 = components2.filter((component) => {
785
- const tagMatch = component.tags.some(
786
- (tag) => tag.toLowerCase().includes(search2)
787
- );
788
- return component.name.toLowerCase().includes(search2) || component.description.toLowerCase().includes(search2) || tagMatch;
789
- });
840
+ const scored = keywordScoreComponents(
841
+ search2,
842
+ components2,
843
+ ctx.indexes.componentIndex ?? void 0
844
+ );
845
+ const allowedNames = new Set(components2.map((component) => component.name));
846
+ const sortedNames = scored.filter((result) => allowedNames.has(result.name)).map((result) => result.name.toLowerCase());
847
+ components2 = components2.filter((component) => sortedNames.includes(component.name.toLowerCase())).sort(
848
+ (a, b) => sortedNames.indexOf(a.name.toLowerCase()) - sortedNames.indexOf(b.name.toLowerCase())
849
+ );
790
850
  }
791
851
  if (status) {
792
852
  components2 = components2.filter((component) => component.status === status);
@@ -838,6 +898,14 @@ var discoverHandler = async (args, ctx) => {
838
898
  "component",
839
899
  ctx.config.searchApiKey
840
900
  );
901
+ const filteredSearchResults = searchResults.filter((result) => {
902
+ const component = componentsByName.get(result.name.toLowerCase());
903
+ if (!component) return false;
904
+ if (category && !categoryMatches(component.category, category)) return false;
905
+ if (status && component.status !== status) return false;
906
+ result.score += getRankingBonus(component);
907
+ return true;
908
+ });
841
909
  const blockMatches = keywordScoreBlocks(
842
910
  fullQuery,
843
911
  allBlocks,
@@ -848,10 +916,10 @@ var discoverHandler = async (args, ctx) => {
848
916
  (match) => allBlocks.find((block) => block.name.toLowerCase() === match.name.toLowerCase())
849
917
  ).filter(Boolean);
850
918
  const blockFreq = buildBlockComponentFrequency(matchedBlocks);
851
- boostByBlockFrequency(searchResults, blockFreq);
919
+ boostByBlockFrequency(filteredSearchResults, blockFreq);
852
920
  }
853
- const maxScore = searchResults.length > 0 ? searchResults[0].score : 0;
854
- const scored = searchResults.map((result) => {
921
+ const maxScore = filteredSearchResults.length > 0 ? filteredSearchResults[0].score : 0;
922
+ const scored = filteredSearchResults.map((result) => {
855
923
  const component = componentsByName.get(result.name.toLowerCase());
856
924
  if (!component) return null;
857
925
  return {
@@ -864,6 +932,11 @@ var discoverHandler = async (args, ctx) => {
864
932
  when: getGuidanceWhen(component).slice(0, 3),
865
933
  whenNot: getGuidanceWhenNot(component).slice(0, 2)
866
934
  },
935
+ publicRef: component.publicRef,
936
+ componentKey: component.id,
937
+ tier: component.tier,
938
+ isCanonical: component.isCanonical ?? false,
939
+ sourcePath: component.sourcePath,
867
940
  exampleCount: component.examples.length,
868
941
  status: component.status
869
942
  };
@@ -902,7 +975,10 @@ var discoverHandler = async (args, ctx) => {
902
975
  const isStyleQuery = styleKeywords.some(
903
976
  (keyword) => useCaseLower.includes(keyword)
904
977
  );
905
- const noMatch = suggestions.length === 0;
978
+ const noMatch = suggestions.length === 0 || !suggestions.some((item) => {
979
+ const component = componentsByName.get(item.component.toLowerCase());
980
+ return component ? hasDirectQueryOverlap(useCase, component) : false;
981
+ });
906
982
  const belowThreshold = !noMatch && maxScore > 1 && !meetsMinimumThreshold(maxScore);
907
983
  const weakMatch = !noMatch && (belowThreshold || suggestions.every((item) => item.confidence === "low"));
908
984
  let recommendation;
@@ -922,6 +998,80 @@ var discoverHandler = async (args, ctx) => {
922
998
  (match) => allBlocks.find((block) => block.name.toLowerCase() === match.name.toLowerCase())
923
999
  ).filter(Boolean).slice(0, 3).map((block) => block.name);
924
1000
  const blockHint = blockNames.length > 0 ? `Related blocks: ${blockNames.join(", ")}. Use ${ctx.toolNames.blocks}(search: "${useCase}") for ready-to-use patterns.` : void 0;
1001
+ let fullBlocks;
1002
+ let fullTokens;
1003
+ let fullImports;
1004
+ if (depth === "full" && !noMatch) {
1005
+ const tokenData = ctx.data.tokens;
1006
+ const [blockSearchResults, tokenSearchResults] = await Promise.all([
1007
+ hybridSearch(fullQuery, localData, 5, "block", ctx.config.searchApiKey),
1008
+ tokenData ? hybridSearch(fullQuery, localData, 10, "token", ctx.config.searchApiKey) : Promise.resolve([])
1009
+ ]);
1010
+ const topBlockScore = blockSearchResults.length > 0 ? blockSearchResults[0].score : 0;
1011
+ const relevantBlockResults = blockSearchResults.filter(
1012
+ (result) => result.score >= topBlockScore * 0.3
1013
+ );
1014
+ if (relevantBlockResults.length > 0) {
1015
+ fullBlocks = (await Promise.all(
1016
+ relevantBlockResults.slice(0, 5).map(async (result) => {
1017
+ const block = allBlocks.find(
1018
+ (entry) => entry.name.toLowerCase() === result.name.toLowerCase()
1019
+ );
1020
+ if (!block) return null;
1021
+ const imports = await buildImportStatements(
1022
+ block.components,
1023
+ async (componentName) => ctx.resolvePackageName(componentName)
1024
+ );
1025
+ const codeLines = block.code.split("\n");
1026
+ const code = codeLines.length > 30 ? `${codeLines.slice(0, 20).join("\n")}
1027
+ // ... truncated (${codeLines.length} lines total)` : block.code;
1028
+ return {
1029
+ name: block.name,
1030
+ description: block.description,
1031
+ components: block.components,
1032
+ code,
1033
+ imports
1034
+ };
1035
+ })
1036
+ )).filter(Boolean);
1037
+ }
1038
+ if (tokenSearchResults.length > 0 && tokenData) {
1039
+ fullTokens = {};
1040
+ const tokensByName = /* @__PURE__ */ new Map();
1041
+ for (const [cat, tokens] of Object.entries(tokenData.categories)) {
1042
+ for (const token of tokens) {
1043
+ tokensByName.set(token.name, cat);
1044
+ }
1045
+ }
1046
+ for (const result of tokenSearchResults) {
1047
+ const cat = tokensByName.get(result.name);
1048
+ if (cat) {
1049
+ if (!fullTokens[cat]) fullTokens[cat] = [];
1050
+ fullTokens[cat].push(result.name);
1051
+ }
1052
+ }
1053
+ if (Object.keys(fullTokens).length === 0) fullTokens = void 0;
1054
+ }
1055
+ if (!fullTokens && tokenData) {
1056
+ const categories = extractTokenCategories(fullQuery);
1057
+ fullTokens = {};
1058
+ for (const cat of categories) {
1059
+ const tokens = tokenData.categories[cat];
1060
+ if (tokens && tokens.length > 0) {
1061
+ fullTokens[cat] = tokens.slice(0, 5).map((token) => token.name);
1062
+ }
1063
+ }
1064
+ if (Object.keys(fullTokens).length === 0) fullTokens = void 0;
1065
+ }
1066
+ if (suggestions.length > 0) {
1067
+ fullImports = {};
1068
+ for (const item of suggestions) {
1069
+ if (!item) continue;
1070
+ const pkgName = ctx.resolvePackageName(item.component);
1071
+ fullImports[item.component] = `import { ${item.component} } from '${pkgName}';`;
1072
+ }
1073
+ }
1074
+ }
925
1075
  const suggestResponse = verbosity === "compact" ? {
926
1076
  useCase,
927
1077
  suggestions: suggestions.map((item) => ({
@@ -933,14 +1083,19 @@ var discoverHandler = async (args, ctx) => {
933
1083
  } : {
934
1084
  useCase,
935
1085
  context: context || void 0,
936
- suggestions,
1086
+ suggestions: depth === "full" ? suggestions.map((item) => ({
1087
+ ...item,
1088
+ import: fullImports?.[item.component]
1089
+ })) : suggestions,
937
1090
  noMatch,
938
1091
  weakMatch,
939
1092
  recommendation,
940
1093
  compositionHint,
941
1094
  ...tokenHint && { tokenHint },
942
1095
  ...blockHint && { blockHint },
943
- nextStep
1096
+ nextStep,
1097
+ ...fullBlocks && fullBlocks.length > 0 && { blocks: fullBlocks },
1098
+ ...fullTokens && { tokens: fullTokens }
944
1099
  };
945
1100
  return {
946
1101
  content: [
@@ -952,33 +1107,39 @@ var discoverHandler = async (args, ctx) => {
952
1107
  };
953
1108
  }
954
1109
  if (componentForAlts) {
955
- const component = findComponentByName(data.snapshot, componentForAlts);
1110
+ const component = findComponent(data.snapshot, componentForAlts);
956
1111
  if (!component) {
957
1112
  const closest = findClosestMatch(
958
1113
  componentForAlts,
959
1114
  componentNames(data.snapshot)
960
1115
  );
961
1116
  const suggestion = closest ? ` Did you mean "${closest}"?` : "";
962
- throw new Error(
963
- `Component "${componentForAlts}" not found.${suggestion} Use ${ctx.toolNames.discover} to see available components.`
964
- );
1117
+ return {
1118
+ content: [
1119
+ {
1120
+ type: "text",
1121
+ text: JSON.stringify({
1122
+ error: `Component "${componentForAlts}" not found.${suggestion} Use ${ctx.toolNames.discover} to see available components.`
1123
+ })
1124
+ }
1125
+ ],
1126
+ isError: true
1127
+ };
965
1128
  }
966
1129
  const relations = component.relations;
967
- const referencedBy = listComponents(data.snapshot).filter(
968
- (entry) => entry.relations.some(
969
- (relation) => relation.componentName.toLowerCase() === componentForAlts.toLowerCase()
970
- )
971
- ).map((entry) => ({
972
- component: entry.name,
973
- relationship: entry.relations.find(
974
- (relation) => relation.componentName.toLowerCase() === componentForAlts.toLowerCase()
975
- )?.relationship,
976
- note: entry.relations.find(
977
- (relation) => relation.componentName.toLowerCase() === componentForAlts.toLowerCase()
978
- )?.note
979
- }));
980
- const sameCategory = listComponents(data.snapshot).filter(
981
- (entry) => entry.category === component.category && entry.name.toLowerCase() !== componentForAlts.toLowerCase()
1130
+ const referencedBy = allSnapshotComponents.map((entry) => {
1131
+ const relation = entry.relations.find(
1132
+ (candidate) => candidate.componentName.toLowerCase() === component.name.toLowerCase()
1133
+ );
1134
+ if (!relation) return null;
1135
+ return {
1136
+ component: entry.name,
1137
+ relationship: relation.relationship,
1138
+ note: relation.note
1139
+ };
1140
+ }).filter(Boolean);
1141
+ const sameCategory = allSnapshotComponents.filter(
1142
+ (entry) => entry.category === component.category && entry.name.toLowerCase() !== component.name.toLowerCase()
982
1143
  ).map((entry) => ({
983
1144
  component: entry.name,
984
1145
  description: entry.description
@@ -1001,25 +1162,44 @@ var discoverHandler = async (args, ctx) => {
1001
1162
  ]
1002
1163
  };
1003
1164
  }
1004
- const filteredComponents = listComponents(data.snapshot).filter((component) => {
1165
+ let filteredComponents = allSnapshotComponents.filter((component) => {
1005
1166
  if (category && !categoryMatches(component.category, category)) return false;
1006
1167
  if (status && (component.status ?? "stable") !== status) return false;
1007
- if (search2) {
1008
- const nameMatch = component.name.toLowerCase().includes(search2);
1009
- const descMatch = component.description.toLowerCase().includes(search2);
1010
- const tagMatch = component.tags.some(
1011
- (tag) => tag.toLowerCase().includes(search2)
1012
- );
1013
- if (!nameMatch && !descMatch && !tagMatch) return false;
1014
- }
1015
1168
  return true;
1016
1169
  });
1170
+ if (search2) {
1171
+ const scored = keywordScoreComponents(
1172
+ search2,
1173
+ filteredComponents,
1174
+ ctx.indexes.componentIndex ?? void 0
1175
+ ).map((result) => {
1176
+ const component = filteredComponents.find(
1177
+ (entry) => entry.name.toLowerCase() === result.name.toLowerCase()
1178
+ );
1179
+ if (!component) return null;
1180
+ return {
1181
+ component,
1182
+ score: result.score + getRankingBonus(component)
1183
+ };
1184
+ }).filter(Boolean);
1185
+ filteredComponents = scored.sort((a, b) => b.score - a.score).map((entry) => entry.component);
1186
+ } else {
1187
+ filteredComponents = filteredComponents.sort((a, b) => {
1188
+ const bonusDiff = getRankingBonus(b) - getRankingBonus(a);
1189
+ if (bonusDiff !== 0) return bonusDiff;
1190
+ return a.name.localeCompare(b.name);
1191
+ });
1192
+ }
1017
1193
  const limitedComponents = listLimit === void 0 ? filteredComponents : filteredComponents.slice(0, listLimit);
1018
1194
  const components = limitedComponents.map((component) => {
1019
1195
  if (verbosity === "compact") {
1020
1196
  return {
1021
1197
  name: component.name,
1022
1198
  category: component.category,
1199
+ publicRef: component.publicRef,
1200
+ componentKey: component.id,
1201
+ tier: component.tier,
1202
+ isCanonical: component.isCanonical ?? false,
1023
1203
  ...component.propsSummary.length > 0 && {
1024
1204
  propsSummary: component.propsSummary
1025
1205
  }
@@ -1030,6 +1210,11 @@ var discoverHandler = async (args, ctx) => {
1030
1210
  category: component.category,
1031
1211
  description: component.description,
1032
1212
  status: component.status ?? "stable",
1213
+ publicRef: component.publicRef,
1214
+ componentKey: component.id,
1215
+ tier: component.tier,
1216
+ isCanonical: component.isCanonical ?? false,
1217
+ sourcePath: component.sourcePath,
1033
1218
  exampleCount: component.examples.length,
1034
1219
  tags: component.tags,
1035
1220
  ...(includeCode || verbosity === "full") && component.examples[0]?.code ? {
@@ -1054,7 +1239,8 @@ var discoverHandler = async (args, ctx) => {
1054
1239
  };
1055
1240
 
1056
1241
  // src/tools/inspect.ts
1057
- import { readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
1242
+ import { promises as fs } from "fs";
1243
+ import { existsSync as existsSync2 } from "fs";
1058
1244
  import { join as join2 } from "path";
1059
1245
 
1060
1246
  // src/utils.ts
@@ -1090,13 +1276,13 @@ function projectFields(obj, fields) {
1090
1276
  }
1091
1277
 
1092
1278
  // src/tools/inspect.ts
1093
- function getSourceCode(component, projectRoot) {
1279
+ async function getSourceCode(component, projectRoot) {
1094
1280
  const sourcePath = component.sourcePath;
1095
1281
  if (!sourcePath) return void 0;
1096
1282
  const fullPath = join2(projectRoot, sourcePath);
1097
1283
  if (!existsSync2(fullPath)) return { path: sourcePath, code: null };
1098
1284
  try {
1099
- const code = readFileSync3(fullPath, "utf-8");
1285
+ const code = await fs.readFile(fullPath, "utf-8");
1100
1286
  return { path: sourcePath, code };
1101
1287
  } catch {
1102
1288
  return { path: sourcePath, code: null };
@@ -1110,18 +1296,34 @@ var inspectHandler = async (args, ctx) => {
1110
1296
  const maxLines = args?.maxLines;
1111
1297
  const verbosity = args?.verbosity ?? "standard";
1112
1298
  if (!componentName) {
1113
- throw new Error("component is required");
1299
+ return {
1300
+ content: [
1301
+ {
1302
+ type: "text",
1303
+ text: JSON.stringify({ error: "component is required" })
1304
+ }
1305
+ ],
1306
+ isError: true
1307
+ };
1114
1308
  }
1115
- const component = findComponentByName(ctx.data.snapshot, componentName);
1309
+ const component = findComponent(ctx.data.snapshot, componentName);
1116
1310
  if (!component) {
1117
1311
  const closest = findClosestMatch(
1118
1312
  componentName,
1119
1313
  componentNames(ctx.data.snapshot)
1120
1314
  );
1121
1315
  const suggestion = closest ? ` Did you mean "${closest}"? Use ${ctx.toolNames.inspect}("${closest}") to inspect it.` : "";
1122
- throw new Error(
1123
- `Component "${componentName}" not found.${suggestion} Use ${ctx.toolNames.discover} to see available components.`
1124
- );
1316
+ return {
1317
+ content: [
1318
+ {
1319
+ type: "text",
1320
+ text: JSON.stringify({
1321
+ error: `Component "${componentName}" not found.${suggestion} Use ${ctx.toolNames.discover} to see available components.`
1322
+ })
1323
+ }
1324
+ ],
1325
+ isError: true
1326
+ };
1125
1327
  }
1126
1328
  const pkgName = ctx.resolvePackageName(component.name);
1127
1329
  let examples = component.examples;
@@ -1141,25 +1343,41 @@ var inspectHandler = async (args, ctx) => {
1141
1343
  if (filtered.length > 0) {
1142
1344
  examples = filtered;
1143
1345
  } else {
1144
- throw new Error(
1145
- `Example "${exampleName}" not found for ${componentName}. Available: ${component.examples.map((example) => example.name).join(", ")}`
1146
- );
1346
+ return {
1347
+ content: [
1348
+ {
1349
+ type: "text",
1350
+ text: JSON.stringify({
1351
+ error: `Example "${exampleName}" not found for ${componentName}. Available: ${component.examples.map((example) => example.name).join(", ")}`
1352
+ })
1353
+ }
1354
+ ],
1355
+ isError: true
1356
+ };
1147
1357
  }
1148
1358
  }
1149
1359
  if (maxExamples && maxExamples > 0) {
1150
1360
  examples = examples.slice(0, maxExamples);
1151
1361
  }
1152
1362
  const truncateCode = (code) => {
1153
- if (!maxLines || maxLines <= 0) return code;
1363
+ if (!maxLines || maxLines <= 0) {
1364
+ return { code, truncated: false, remainingLines: 0 };
1365
+ }
1154
1366
  const lines = code.split("\n");
1155
- if (lines.length <= maxLines) return code;
1156
- return `${lines.slice(0, maxLines).join("\n")}
1157
- // ... truncated`;
1367
+ if (lines.length <= maxLines) {
1368
+ return { code, truncated: false, remainingLines: 0 };
1369
+ }
1370
+ return {
1371
+ code: lines.slice(0, maxLines).join("\n"),
1372
+ truncated: true,
1373
+ remainingLines: lines.length - maxLines
1374
+ };
1158
1375
  };
1159
1376
  const renderedExamples = examples.map((example) => ({
1377
+ ...example.code ? truncateCode(example.code) : { truncated: false, remainingLines: 0 },
1160
1378
  variant: example.name,
1161
1379
  description: example.description,
1162
- code: example.code ? truncateCode(example.code) : `<${component.name} />`,
1380
+ code: example.code ? truncateCode(example.code).code : `<${component.name} />`,
1163
1381
  ...example.code ? {} : {
1164
1382
  note: "No code example provided. Refer to props for customization."
1165
1383
  }
@@ -1189,13 +1407,13 @@ var inspectHandler = async (args, ctx) => {
1189
1407
  status: component.status,
1190
1408
  publicRef: component.publicRef,
1191
1409
  publicSlug: component.publicSlug,
1410
+ isCanonical: component.isCanonical ?? false,
1192
1411
  tier: component.tier
1193
1412
  },
1194
- props: component.props,
1413
+ props: propsReference,
1195
1414
  examples: {
1196
1415
  import: `import { ${component.name} } from '${pkgName}';`,
1197
- code: renderedExamples,
1198
- propsReference
1416
+ code: renderedExamples
1199
1417
  },
1200
1418
  relations: component.relations,
1201
1419
  compoundChildren: component.compoundChildren,
@@ -1215,7 +1433,7 @@ var inspectHandler = async (args, ctx) => {
1215
1433
  })) ?? []
1216
1434
  },
1217
1435
  metadata: component.metadata,
1218
- source: getSourceCode(component, ctx.config.projectRoot)
1436
+ source: await getSourceCode(component, ctx.config.projectRoot)
1219
1437
  };
1220
1438
  const aliasMap = { usage: "guidance" };
1221
1439
  const resolvedFields = fields?.map((field) => {
@@ -1395,6 +1613,44 @@ function resolveCategoryKeys(categories, requestedCategory) {
1395
1613
  }
1396
1614
  return keys.filter((key) => (normalizeCategoryValue(key) ?? "").includes(normalized));
1397
1615
  }
1616
+ function canonicalizeCategory(category, tokens) {
1617
+ const normalizedCategory = normalizeCategoryValue(category);
1618
+ const candidates = [
1619
+ normalizedCategory,
1620
+ ...tokens.flatMap((token) => [
1621
+ normalizeCategoryValue(token.category),
1622
+ normalizeCategoryValue(token.path?.[0]),
1623
+ normalizeCategoryValue(token.name.split(/[.:/-]/)[0])
1624
+ ])
1625
+ ].filter(Boolean);
1626
+ for (const candidate of candidates) {
1627
+ for (const [canonical, aliases] of Object.entries(TOKEN_CATEGORY_ALIASES)) {
1628
+ if (candidate === canonical || aliases.some(
1629
+ (alias) => candidate === alias || candidate.includes(alias) || alias.includes(candidate)
1630
+ )) {
1631
+ return canonical;
1632
+ }
1633
+ }
1634
+ }
1635
+ return normalizedCategory || "other";
1636
+ }
1637
+ function normalizeTokenCategories(categories) {
1638
+ const normalized = {};
1639
+ for (const [category, tokens] of Object.entries(categories)) {
1640
+ const canonical = canonicalizeCategory(category, tokens);
1641
+ if (!normalized[canonical]) normalized[canonical] = [];
1642
+ normalized[canonical].push(
1643
+ ...tokens.map((token) => ({
1644
+ ...token,
1645
+ category: canonical
1646
+ }))
1647
+ );
1648
+ }
1649
+ for (const tokens of Object.values(normalized)) {
1650
+ tokens.sort((a, b) => a.name.localeCompare(b.name));
1651
+ }
1652
+ return normalized;
1653
+ }
1398
1654
  var tokensHandler = async (args, ctx) => {
1399
1655
  const category = args?.category;
1400
1656
  const search2 = args?.search?.toLowerCase() ?? void 0;
@@ -1412,12 +1668,13 @@ var tokensHandler = async (args, ctx) => {
1412
1668
  }]
1413
1669
  };
1414
1670
  }
1671
+ const normalizedCategories = normalizeTokenCategories(tokenData.categories);
1415
1672
  let filteredCategories = {};
1416
1673
  let filteredTotal = 0;
1417
- const resolvedCategoryKeys = resolveCategoryKeys(tokenData.categories, category);
1674
+ const resolvedCategoryKeys = resolveCategoryKeys(normalizedCategories, category);
1418
1675
  const friendlyCategories = Object.keys(TOKEN_CATEGORY_ALIASES);
1419
- const searchMatchesCategory = search2 ? resolveCategoryKeys(tokenData.categories, search2) : [];
1420
- for (const [cat, tokens] of Object.entries(tokenData.categories)) {
1676
+ const searchMatchesCategory = search2 ? resolveCategoryKeys(normalizedCategories, search2) : [];
1677
+ for (const [cat, tokens] of Object.entries(normalizedCategories)) {
1421
1678
  if (category && !resolvedCategoryKeys.includes(cat)) continue;
1422
1679
  let filtered = tokens;
1423
1680
  if (search2) {
@@ -1425,7 +1682,7 @@ var tokensHandler = async (args, ctx) => {
1425
1682
  filtered = tokens;
1426
1683
  } else {
1427
1684
  filtered = tokens.filter(
1428
- (t) => t.name.toLowerCase().includes(search2) || t.description && t.description.toLowerCase().includes(search2) || (normalizeCategoryValue(cat) ?? "").includes(search2)
1685
+ (t) => t.name.toLowerCase().includes(search2) || t.description && t.description.toLowerCase().includes(search2) || (normalizeCategoryValue(cat) ?? "").includes(search2) || t.value && t.value.toLowerCase().includes(search2) || t.path && t.path.join(" ").toLowerCase().includes(search2)
1429
1686
  );
1430
1687
  }
1431
1688
  }
@@ -1443,7 +1700,7 @@ var tokensHandler = async (args, ctx) => {
1443
1700
  if (filteredTotal === 0) {
1444
1701
  if (category && search2) {
1445
1702
  const categoryTotal = resolvedCategoryKeys.reduce(
1446
- (sum, key) => sum + (tokenData.categories[key]?.length ?? 0),
1703
+ (sum, key) => sum + (normalizedCategories[key]?.length ?? 0),
1447
1704
  0
1448
1705
  );
1449
1706
  hint = categoryTotal > 0 ? `No tokens matching "${search2}" in category "${category}" (${categoryTotal} tokens in this category). Try a broader search or remove the search term.` : `Category "${category}" not found. Try categories like: ${friendlyCategories.join(", ")}.`;
@@ -1465,7 +1722,7 @@ var tokensHandler = async (args, ctx) => {
1465
1722
  categories: filteredCategories,
1466
1723
  ...hint && { hint },
1467
1724
  ...!category && !search2 && {
1468
- availableCategories: Object.entries(tokenData.categories).map(([cat, tokens]) => ({
1725
+ availableCategories: Object.entries(normalizedCategories).map(([cat, tokens]) => ({
1469
1726
  category: cat,
1470
1727
  count: tokens.length
1471
1728
  }))
@@ -1475,177 +1732,6 @@ var tokensHandler = async (args, ctx) => {
1475
1732
  };
1476
1733
  };
1477
1734
 
1478
- // src/tools/implement.ts
1479
- var implementHandler = async (args, ctx) => {
1480
- const useCase = args?.useCase;
1481
- if (!useCase) {
1482
- throw new Error("useCase is required");
1483
- }
1484
- const verbosity = args?.verbosity ?? "standard";
1485
- const snapshotComponents = listComponents(ctx.data.snapshot);
1486
- const componentsByName = new Map(
1487
- snapshotComponents.map((component) => [
1488
- component.name.toLowerCase(),
1489
- component
1490
- ])
1491
- );
1492
- const { allBlocks, localData } = buildLocalSearchData(
1493
- {
1494
- components: ctx.data.components,
1495
- blocks: ctx.data.blocks,
1496
- tokens: ctx.data.tokens,
1497
- graph: ctx.data.graph
1498
- },
1499
- {
1500
- componentIndex: ctx.indexes.componentIndex,
1501
- blockIndex: ctx.indexes.blockIndex,
1502
- tokenIndex: ctx.indexes.tokenIndex
1503
- }
1504
- );
1505
- const tokenData = ctx.data.tokens;
1506
- const implLimit = typeof args?.limit === "number" ? Math.min(Math.max(args.limit, 1), 15) : 5;
1507
- const [componentResults, blockResults, tokenResults] = await Promise.all([
1508
- hybridSearch(
1509
- useCase,
1510
- localData,
1511
- implLimit * 3,
1512
- "component",
1513
- ctx.config.searchApiKey
1514
- ),
1515
- hybridSearch(
1516
- useCase,
1517
- localData,
1518
- implLimit,
1519
- "block",
1520
- ctx.config.searchApiKey
1521
- ),
1522
- hybridSearch(
1523
- useCase,
1524
- localData,
1525
- implLimit,
1526
- "token",
1527
- ctx.config.searchApiKey
1528
- )
1529
- ]);
1530
- const topBlockScore = blockResults.length > 0 ? blockResults[0].score : 0;
1531
- const filteredBlockResults = blockResults.filter(
1532
- (result) => result.score >= topBlockScore * 0.3
1533
- );
1534
- if (filteredBlockResults.length > 0) {
1535
- const matchedBlocks = filteredBlockResults.map(
1536
- (result) => allBlocks.find((block) => block.name.toLowerCase() === result.name.toLowerCase())
1537
- ).filter(Boolean);
1538
- const blockFreq = buildBlockComponentFrequency(matchedBlocks);
1539
- boostByBlockFrequency(componentResults, blockFreq);
1540
- }
1541
- const topComponentResults = componentResults.slice(0, implLimit);
1542
- const maxCompScore = topComponentResults.length > 0 ? topComponentResults[0].score : 0;
1543
- const topMatches = topComponentResults.map((result) => {
1544
- const component = componentsByName.get(result.name.toLowerCase());
1545
- return component ? { component, score: result.score } : null;
1546
- }).filter(Boolean);
1547
- const components = await Promise.all(
1548
- topMatches.map(async ({ component, score }) => {
1549
- const pkgName = ctx.resolvePackageName(component.name);
1550
- if (verbosity === "compact") {
1551
- return {
1552
- name: component.name,
1553
- description: component.description,
1554
- confidence: assignConfidence(score, maxCompScore),
1555
- import: `import { ${component.name} } from '${pkgName}';`
1556
- };
1557
- }
1558
- const exampleLimit = verbosity === "full" ? component.examples.length : 2;
1559
- const propsLimit = verbosity === "full" ? Object.keys(component.props ?? {}).length : 5;
1560
- const examples = component.examples.slice(0, exampleLimit).map((example) => ({
1561
- variant: example.name,
1562
- code: example.code ?? `<${component.name} />`
1563
- }));
1564
- const propsSummary = Object.entries(component.props ?? {}).slice(0, propsLimit).map(
1565
- ([propName, prop]) => `${propName}${prop.required ? " (required)" : ""}: ${prop.type}${prop.values ? ` = ${prop.values.join("|")}` : ""}`
1566
- );
1567
- return {
1568
- name: component.name,
1569
- category: component.category,
1570
- description: component.description,
1571
- confidence: assignConfidence(score, maxCompScore),
1572
- import: `import { ${component.name} } from '${pkgName}';`,
1573
- props: propsSummary,
1574
- examples,
1575
- guidelines: getGuidanceWhen(component).slice(0, 3),
1576
- accessibility: component.guidance.accessibility.slice(0, 2)
1577
- };
1578
- })
1579
- );
1580
- const matchingBlocks = (await Promise.all(
1581
- filteredBlockResults.slice(0, 5).map(async (result) => {
1582
- const block = allBlocks.find(
1583
- (entry) => entry.name.toLowerCase() === result.name.toLowerCase()
1584
- );
1585
- if (!block) return null;
1586
- const imports = await buildImportStatements(
1587
- block.components,
1588
- async (componentName) => ctx.resolvePackageName(componentName)
1589
- );
1590
- const codeLines = block.code.split("\n");
1591
- const code = codeLines.length > 30 ? `${codeLines.slice(0, 20).join("\n")}
1592
- // ... truncated (${codeLines.length} lines total)` : block.code;
1593
- return {
1594
- name: block.name,
1595
- description: block.description,
1596
- components: block.components,
1597
- code,
1598
- import: imports.join("\n"),
1599
- imports
1600
- };
1601
- })
1602
- )).filter(Boolean);
1603
- let relevantTokens;
1604
- if (tokenResults.length > 0 && tokenData) {
1605
- relevantTokens = {};
1606
- for (const result of tokenResults) {
1607
- for (const [category, tokens] of Object.entries(tokenData.categories)) {
1608
- if (tokens.some((token) => token.name === result.name)) {
1609
- if (!relevantTokens[category]) relevantTokens[category] = [];
1610
- relevantTokens[category].push(result.name);
1611
- break;
1612
- }
1613
- }
1614
- }
1615
- if (Object.keys(relevantTokens).length === 0) {
1616
- relevantTokens = void 0;
1617
- }
1618
- }
1619
- if (!relevantTokens && tokenData) {
1620
- const categories = extractTokenCategories(useCase);
1621
- relevantTokens = {};
1622
- for (const category of categories) {
1623
- const tokens = tokenData.categories[category];
1624
- if (tokens && tokens.length > 0) {
1625
- relevantTokens[category] = tokens.slice(0, 5).map((token) => token.name);
1626
- }
1627
- }
1628
- if (Object.keys(relevantTokens).length === 0) {
1629
- relevantTokens = void 0;
1630
- }
1631
- }
1632
- return {
1633
- content: [
1634
- {
1635
- type: "text",
1636
- text: JSON.stringify({
1637
- useCase,
1638
- components,
1639
- blocks: verbosity !== "compact" && matchingBlocks.length > 0 ? matchingBlocks : void 0,
1640
- tokens: verbosity !== "compact" ? relevantTokens : void 0,
1641
- noMatch: components.length === 0,
1642
- summary: components.length > 0 ? `Found ${components.length} component(s) for "${useCase}". ${matchingBlocks.length > 0 ? `Plus ${matchingBlocks.length} ready-to-use block(s).` : ""}` : `No components match "${useCase}". Try ${ctx.toolNames.discover} with different terms${tokenData ? ` or ${ctx.toolNames.tokens} for tokens` : ""}.`
1643
- })
1644
- }
1645
- ]
1646
- };
1647
- };
1648
-
1649
1735
  // src/service.ts
1650
1736
  var DEFAULT_ENDPOINTS = {
1651
1737
  render: "/fragments/render",
@@ -2327,8 +2413,18 @@ var governHandler = async (args, ctx) => {
2327
2413
  }
2328
2414
  const policyOverrides = args?.policy;
2329
2415
  const format = args?.format ?? "json";
2416
+ const allowedComponents = Object.values(ctx.data.components).map(
2417
+ (component) => component.name
2418
+ );
2330
2419
  const tokenPrefix = ctx.data.tokens?.prefix;
2331
2420
  const basePolicy = tokenPrefix === "fui-" ? { rules: fragmentsPreset().rules } : { rules: universal().rules };
2421
+ basePolicy.rules["components/allow"] = {
2422
+ enabled: true,
2423
+ severity: "serious",
2424
+ options: {
2425
+ components: allowedComponents
2426
+ }
2427
+ };
2332
2428
  const engineOptions = ctx.data.tokens ? { tokenData: ctx.data.tokens } : void 0;
2333
2429
  const input = {
2334
2430
  spec,
@@ -2401,7 +2497,6 @@ var CORE_TOOLS = {
2401
2497
  inspect: inspectHandler,
2402
2498
  blocks: blocksHandler,
2403
2499
  tokens: tokensHandler,
2404
- implement: implementHandler,
2405
2500
  graph: graphHandler,
2406
2501
  perf: perfHandler,
2407
2502
  govern: governHandler
@@ -2424,7 +2519,6 @@ var TOOL_CAPABILITIES = {
2424
2519
  inspect: ["components"],
2425
2520
  blocks: ["blocks"],
2426
2521
  tokens: ["tokens"],
2427
- implement: ["components"],
2428
2522
  graph: ["graph"],
2429
2523
  perf: ["performance"],
2430
2524
  render: ["components"],
@@ -2576,7 +2670,7 @@ import { join as join7 } from "path";
2576
2670
  import { readFile } from "fs/promises";
2577
2671
 
2578
2672
  // src/discovery.ts
2579
- import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync } from "fs";
2673
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync } from "fs";
2580
2674
  import { join as join3, dirname, resolve } from "path";
2581
2675
  import { createRequire } from "module";
2582
2676
  function resolveWorkspaceGlob(baseDir, pattern) {
@@ -2609,7 +2703,7 @@ function getWorkspaceDirs(rootDir) {
2609
2703
  const rootPkgPath = join3(rootDir, "package.json");
2610
2704
  if (existsSync3(rootPkgPath)) {
2611
2705
  try {
2612
- const rootPkg = JSON.parse(readFileSync4(rootPkgPath, "utf-8"));
2706
+ const rootPkg = JSON.parse(readFileSync3(rootPkgPath, "utf-8"));
2613
2707
  const workspaces = Array.isArray(rootPkg.workspaces) ? rootPkg.workspaces : rootPkg.workspaces?.packages;
2614
2708
  if (Array.isArray(workspaces)) {
2615
2709
  for (const pattern of workspaces) {
@@ -2623,7 +2717,7 @@ function getWorkspaceDirs(rootDir) {
2623
2717
  const pnpmWsPath = join3(rootDir, "pnpm-workspace.yaml");
2624
2718
  if (existsSync3(pnpmWsPath)) {
2625
2719
  try {
2626
- const content = readFileSync4(pnpmWsPath, "utf-8");
2720
+ const content = readFileSync3(pnpmWsPath, "utf-8");
2627
2721
  const lines = content.split("\n");
2628
2722
  let inPackages = false;
2629
2723
  for (const line of lines) {
@@ -2656,7 +2750,7 @@ function resolveDepPackageJson(localRequire, depName) {
2656
2750
  while (true) {
2657
2751
  const candidate = join3(dir, "package.json");
2658
2752
  if (existsSync3(candidate)) {
2659
- const pkg = JSON.parse(readFileSync4(candidate, "utf-8"));
2753
+ const pkg = JSON.parse(readFileSync3(candidate, "utf-8"));
2660
2754
  if (pkg.name === depName) return candidate;
2661
2755
  }
2662
2756
  const parent = dirname(dir);
@@ -2671,7 +2765,7 @@ function findFragmentsInDeps(dir, found, depField) {
2671
2765
  const pkgJsonPath = join3(dir, "package.json");
2672
2766
  if (!existsSync3(pkgJsonPath)) return;
2673
2767
  try {
2674
- const pkgJson = JSON.parse(readFileSync4(pkgJsonPath, "utf-8"));
2768
+ const pkgJson = JSON.parse(readFileSync3(pkgJsonPath, "utf-8"));
2675
2769
  const allDeps = {
2676
2770
  ...pkgJson.dependencies,
2677
2771
  ...pkgJson.devDependencies
@@ -2681,7 +2775,7 @@ function findFragmentsInDeps(dir, found, depField) {
2681
2775
  try {
2682
2776
  const depPkgPath = resolveDepPackageJson(localRequire, depName);
2683
2777
  if (!depPkgPath) continue;
2684
- const depPkg = JSON.parse(readFileSync4(depPkgPath, "utf-8"));
2778
+ const depPkg = JSON.parse(readFileSync3(depPkgPath, "utf-8"));
2685
2779
  if (depPkg[depField]) {
2686
2780
  const fragmentsPath = join3(dirname(depPkgPath), depPkg[depField]);
2687
2781
  if (existsSync3(fragmentsPath) && !found.includes(fragmentsPath)) {
@@ -2757,6 +2851,26 @@ function valueToString(value) {
2757
2851
  return void 0;
2758
2852
  }
2759
2853
  }
2854
+ function buildCompoundChildren(contract, ai) {
2855
+ const contractChildren = Object.entries(contract?.compoundChildren ?? {});
2856
+ if (contractChildren.length > 0) {
2857
+ return contractChildren.map(([childName, child]) => ({
2858
+ name: childName,
2859
+ description: child.description,
2860
+ required: child.required,
2861
+ accepts: child.accepts,
2862
+ visibility: "public"
2863
+ }));
2864
+ }
2865
+ const subs = ai?.subComponents;
2866
+ if (!subs || subs.length === 0) return [];
2867
+ const requiredSet = new Set(ai?.requiredChildren ?? []);
2868
+ return subs.map((name) => ({
2869
+ name,
2870
+ required: requiredSet.has(name) || void 0,
2871
+ visibility: "public"
2872
+ }));
2873
+ }
2760
2874
  function componentFromCompiledFragment(args) {
2761
2875
  const { fragment, sourceType } = args;
2762
2876
  const name = fragment.meta.name;
@@ -2806,15 +2920,7 @@ function componentFromCompiledFragment(args) {
2806
2920
  relationship: relation.relationship,
2807
2921
  note: relation.note
2808
2922
  })),
2809
- compoundChildren: Object.entries(contract?.compoundChildren ?? {}).map(
2810
- ([childName, child]) => ({
2811
- name: childName,
2812
- description: child.description,
2813
- required: child.required,
2814
- accepts: child.accepts,
2815
- visibility: "public"
2816
- })
2817
- ),
2923
+ compoundChildren: buildCompoundChildren(contract, ai),
2818
2924
  guidance: {
2819
2925
  when: usage.when ?? [],
2820
2926
  whenNot: usage.whenNot ?? [],
@@ -3010,7 +3116,7 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
3010
3116
  };
3011
3117
 
3012
3118
  // src/adapters/auto-extract.ts
3013
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
3119
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
3014
3120
  import { join as join6, relative, sep } from "path";
3015
3121
 
3016
3122
  // src/adapters/discover-components.ts
@@ -3098,7 +3204,7 @@ function inferComponentName(fileName, dirPath) {
3098
3204
  }
3099
3205
 
3100
3206
  // src/adapters/scan-tokens.ts
3101
- import { readdirSync as readdirSync3, readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
3207
+ import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
3102
3208
  import { join as join5, extname as extname2 } from "path";
3103
3209
  function scanTokens(projectRoot) {
3104
3210
  const cssFiles = discoverCssFiles(projectRoot);
@@ -3107,7 +3213,7 @@ function scanTokens(projectRoot) {
3107
3213
  let prefix = "";
3108
3214
  for (const filePath of cssFiles) {
3109
3215
  try {
3110
- const content = readFileSync5(filePath, "utf-8");
3216
+ const content = readFileSync4(filePath, "utf-8");
3111
3217
  const tokens = extractCustomProperties(content);
3112
3218
  allTokens.push(...tokens);
3113
3219
  } catch {
@@ -3274,7 +3380,7 @@ var AutoExtractionAdapter = class {
3274
3380
  async load(projectRoot) {
3275
3381
  let extractMod;
3276
3382
  try {
3277
- extractMod = await import("./dist-V7D67NXS.js");
3383
+ extractMod = await loadExtractorModule();
3278
3384
  } catch (e) {
3279
3385
  throw new Error(
3280
3386
  "Auto-extraction requires @fragments-sdk/extract and TypeScript.\n\nIf you see this error, the MCP server may not be installed correctly.\nAlternative: pre-build your design system with `npx fragments build`"
@@ -3390,6 +3496,13 @@ Check that your tsconfig.json includes the component directories.`
3390
3496
  }
3391
3497
  }
3392
3498
  };
3499
+ var extractorModulePromise = null;
3500
+ async function loadExtractorModule() {
3501
+ if (!extractorModulePromise) {
3502
+ extractorModulePromise = import("./dist-V7D67NXS.js");
3503
+ }
3504
+ return extractorModulePromise;
3505
+ }
3393
3506
  var UNIVERSAL_INHERITED = /* @__PURE__ */ new Set(["children", "className", "id", "disabled"]);
3394
3507
  var FORM_INPUT_INHERITED = /* @__PURE__ */ new Set([
3395
3508
  "placeholder",
@@ -3661,7 +3774,7 @@ function readPackageName(projectRoot) {
3661
3774
  try {
3662
3775
  const pkgPath = join6(projectRoot, "package.json");
3663
3776
  if (!existsSync6(pkgPath)) return void 0;
3664
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
3777
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
3665
3778
  return pkg.name;
3666
3779
  } catch {
3667
3780
  return void 0;
@@ -3670,15 +3783,48 @@ function readPackageName(projectRoot) {
3670
3783
 
3671
3784
  // src/adapters/cloud-catalog.ts
3672
3785
  var DEFAULT_CLOUD_URL = "https://app.usefragments.com/api/catalog";
3786
+ var TOKEN_CATEGORY_ALIASES2 = {
3787
+ color: ["color", "colors", "accent", "background", "foreground", "danger", "brand"],
3788
+ spacing: ["spacing", "space", "padding", "margin", "gap", "inset"],
3789
+ typography: ["typography", "font", "text", "copy", "line-height", "letter"],
3790
+ border: ["border", "borders", "stroke", "outline"],
3791
+ radius: ["radius", "radii", "corner", "corners", "rounded"],
3792
+ shadow: ["shadow", "shadows", "elevation"],
3793
+ layout: ["layout", "grid", "container", "breakpoint"],
3794
+ focus: ["focus", "ring", "focus-ring"],
3795
+ surface: ["surface", "surfaces", "canvas", "card", "background"]
3796
+ };
3673
3797
  function normalizeCatalogUrl(url) {
3674
3798
  if (!url) return DEFAULT_CLOUD_URL;
3675
3799
  if (url.endsWith("/api/catalog")) return url;
3676
3800
  return `${url.replace(/\/+$/, "")}/api/catalog`;
3677
3801
  }
3802
+ function normalizeValue(value) {
3803
+ return value?.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim() ?? "";
3804
+ }
3805
+ function canonicalizeTokenCategory(token) {
3806
+ const candidates = [
3807
+ token.category,
3808
+ token.path?.[0],
3809
+ token.name.split(/[.:/-]/)[0]
3810
+ ].map(normalizeValue).filter(Boolean);
3811
+ for (const candidate of candidates) {
3812
+ for (const [canonical, aliases] of Object.entries(
3813
+ TOKEN_CATEGORY_ALIASES2
3814
+ )) {
3815
+ if (candidate === canonical || aliases.some(
3816
+ (alias) => candidate === alias || candidate.includes(alias) || alias.includes(candidate)
3817
+ )) {
3818
+ return canonical;
3819
+ }
3820
+ }
3821
+ }
3822
+ return candidates[0] || "other";
3823
+ }
3678
3824
  function groupTokens(flat) {
3679
3825
  const categories = {};
3680
3826
  const normalizedFlat = (flat ?? []).map((token) => {
3681
- const category = token.category?.trim() || "other";
3827
+ const category = canonicalizeTokenCategory(token);
3682
3828
  const normalized = {
3683
3829
  name: token.name,
3684
3830
  category,
@@ -3761,6 +3907,7 @@ function mapComponent(component, designSystem) {
3761
3907
  importPath: designSystem?.importPath ?? designSystem?.packageName ?? void 0,
3762
3908
  publicRef: component.publicRef,
3763
3909
  publicSlug: component.publicSlug ?? null,
3910
+ isCanonical: component.isCanonical ?? false,
3764
3911
  tier: component.tier,
3765
3912
  parentComponentId: component.parentComponentKey,
3766
3913
  parentComponentName: component.parentComponentName,
@@ -3778,7 +3925,7 @@ var CloudCatalogAdapter = class {
3778
3925
  async load(_projectRoot) {
3779
3926
  const response = await fetch(normalizeCatalogUrl(this.options.url), {
3780
3927
  headers: {
3781
- Authorization: `Bearer ${this.options.apiKey}`
3928
+ "X-API-Key": this.options.apiKey
3782
3929
  }
3783
3930
  });
3784
3931
  if (!response.ok) {
@@ -3822,9 +3969,21 @@ var CloudCatalogAdapter = class {
3822
3969
  packageMap,
3823
3970
  defaultPackageName: packageName
3824
3971
  });
3972
+ const hydratedComponents = Object.fromEntries(
3973
+ Object.entries(snapshot.components).map(([componentId, component]) => [
3974
+ componentId,
3975
+ {
3976
+ ...component,
3977
+ isCanonical: components[componentId]?.isCanonical ?? false
3978
+ }
3979
+ ])
3980
+ );
3825
3981
  return {
3826
- snapshot,
3827
- components: snapshot.components,
3982
+ snapshot: {
3983
+ ...snapshot,
3984
+ components: hydratedComponents
3985
+ },
3986
+ components: hydratedComponents,
3828
3987
  blocks: snapshot.blocks,
3829
3988
  tokens: snapshot.tokens,
3830
3989
  graph: void 0,
@@ -3908,6 +4067,7 @@ function normalizeRelations(relations, fallback) {
3908
4067
  function buildComponent(manifest, entry, shard) {
3909
4068
  const sourcePackage = manifest.designSystem.importPath ?? manifest.designSystem.packageName ?? void 0;
3910
4069
  const component = shard.component;
4070
+ const componentRecord = component;
3911
4071
  const props = normalizeProps(component.props);
3912
4072
  return {
3913
4073
  id: shard.componentId,
@@ -3944,6 +4104,7 @@ function buildComponent(manifest, entry, shard) {
3944
4104
  importPath: sourcePackage,
3945
4105
  publicRef: component.publicRef,
3946
4106
  publicSlug: component.publicSlug,
4107
+ isCanonical: componentRecord.isCanonical ?? component.tier === "core",
3947
4108
  tier: component.tier,
3948
4109
  parentComponentName: component.parentComponentName,
3949
4110
  metadata: {
@@ -4036,9 +4197,21 @@ var BundleAdapter = class {
4036
4197
  packageMap,
4037
4198
  defaultPackageName: packageName
4038
4199
  });
4200
+ const hydratedComponents = Object.fromEntries(
4201
+ Object.entries(snapshot.components).map(([componentId, component]) => [
4202
+ componentId,
4203
+ {
4204
+ ...component,
4205
+ isCanonical: components[componentId]?.isCanonical ?? component.tier === "core"
4206
+ }
4207
+ ])
4208
+ );
4039
4209
  return {
4040
- snapshot,
4041
- components: snapshot.components,
4210
+ snapshot: {
4211
+ ...snapshot,
4212
+ components: hydratedComponents
4213
+ },
4214
+ components: hydratedComponents,
4042
4215
  blocks: snapshot.blocks,
4043
4216
  tokens: snapshot.tokens,
4044
4217
  graph: snapshot.graph,
@@ -4122,6 +4295,9 @@ function resolveSearchApiKey(config, fileConfig) {
4122
4295
  // src/server.ts
4123
4296
  var TOOL_NAMES = buildToolNames();
4124
4297
  var TOOLS = buildMcpTools();
4298
+ var TOOL_DEFINITION_BY_KEY = new Map(
4299
+ MCP_TOOL_DEFINITIONS.map((definition) => [definition.key, definition])
4300
+ );
4125
4301
  function createMcpServer(config) {
4126
4302
  const server = new Server(
4127
4303
  {
@@ -4157,38 +4333,53 @@ function createMcpServer(config) {
4157
4333
  }
4158
4334
  }
4159
4335
  let cachedData = null;
4336
+ let loadDataPromise = null;
4160
4337
  let resolvedRoot = null;
4338
+ let resolveProjectRootPromise = null;
4161
4339
  let componentIndex = null;
4162
4340
  let blockIndex = null;
4163
4341
  let tokenIndex = null;
4164
4342
  async function resolveProjectRoot() {
4165
4343
  if (resolvedRoot) return resolvedRoot;
4166
- try {
4167
- const result = await server.listRoots();
4168
- if (result.roots?.length > 0) {
4169
- const rootUri = result.roots[0].uri;
4170
- resolvedRoot = fileURLToPath(rootUri);
4171
- return resolvedRoot;
4344
+ if (resolveProjectRootPromise) return resolveProjectRootPromise;
4345
+ resolveProjectRootPromise = (async () => {
4346
+ try {
4347
+ const result = await server.listRoots();
4348
+ if (result.roots?.length > 0) {
4349
+ const rootUri = result.roots[0].uri;
4350
+ resolvedRoot = fileURLToPath(rootUri);
4351
+ return resolvedRoot;
4352
+ }
4353
+ } catch {
4172
4354
  }
4173
- } catch {
4355
+ resolvedRoot = config.projectRoot;
4356
+ return resolvedRoot;
4357
+ })();
4358
+ try {
4359
+ return await resolveProjectRootPromise;
4360
+ } finally {
4361
+ resolveProjectRootPromise = null;
4174
4362
  }
4175
- resolvedRoot = config.projectRoot;
4176
- return resolvedRoot;
4177
4363
  }
4178
4364
  async function loadData() {
4179
4365
  if (cachedData) return cachedData;
4180
- const projectRoot = await resolveProjectRoot();
4181
- cachedData = await adapter.load(projectRoot);
4182
- const allFragments = Object.values(cachedData.components);
4183
- const allBlocks = Object.values(cachedData.blocks ?? {});
4184
- componentIndex = buildComponentIndex(allFragments);
4185
- if (allBlocks.length > 0) {
4186
- blockIndex = buildBlockIndex(allBlocks);
4187
- }
4188
- if (cachedData.tokens && cachedData.tokens.total > 0) {
4189
- tokenIndex = buildTokenIndex(cachedData.tokens);
4366
+ if (loadDataPromise) return loadDataPromise;
4367
+ loadDataPromise = (async () => {
4368
+ const projectRoot = await resolveProjectRoot();
4369
+ const loaded = await adapter.load(projectRoot);
4370
+ const allFragments = Object.values(loaded.components);
4371
+ const allBlocks = Object.values(loaded.blocks ?? {});
4372
+ componentIndex = buildComponentIndex(allFragments);
4373
+ blockIndex = allBlocks.length > 0 ? buildBlockIndex(allBlocks) : null;
4374
+ tokenIndex = loaded.tokens && loaded.tokens.total > 0 ? buildTokenIndex(loaded.tokens) : null;
4375
+ cachedData = loaded;
4376
+ return loaded;
4377
+ })();
4378
+ try {
4379
+ return await loadDataPromise;
4380
+ } finally {
4381
+ loadDataPromise = null;
4190
4382
  }
4191
- return cachedData;
4192
4383
  }
4193
4384
  server.setRequestHandler(ListToolsRequestSchema, async () => {
4194
4385
  const data = await loadData();
@@ -4220,7 +4411,7 @@ function createMcpServer(config) {
4220
4411
  const packageJsonPath = join8(root, "package.json");
4221
4412
  if (existsSync9(packageJsonPath)) {
4222
4413
  try {
4223
- const content = readFileSync7(packageJsonPath, "utf-8");
4414
+ const content = readFileSync6(packageJsonPath, "utf-8");
4224
4415
  const pkg = JSON.parse(content);
4225
4416
  if (pkg.name) {
4226
4417
  return pkg.name;
@@ -4234,6 +4425,23 @@ function createMcpServer(config) {
4234
4425
  };
4235
4426
  try {
4236
4427
  const toolKey = registry.resolveKey(name);
4428
+ const definition = TOOL_DEFINITION_BY_KEY.get(toolKey);
4429
+ const argumentKeys = Object.keys(args ?? {});
4430
+ const allowedKeys = new Set(Object.keys(definition?.params ?? {}));
4431
+ const unknownKeys = definition ? argumentKeys.filter((key) => !allowedKeys.has(key)) : [];
4432
+ if (unknownKeys.length > 0) {
4433
+ return {
4434
+ content: [
4435
+ {
4436
+ type: "text",
4437
+ text: JSON.stringify({
4438
+ error: `Unknown argument(s) for ${toolKey}: ${unknownKeys.join(", ")}`
4439
+ })
4440
+ }
4441
+ ],
4442
+ isError: true
4443
+ };
4444
+ }
4237
4445
  const mCtx = {
4238
4446
  toolName: name,
4239
4447
  toolKey,
@@ -4290,4 +4498,4 @@ export {
4290
4498
  startMcpServer,
4291
4499
  createSandboxServer
4292
4500
  };
4293
- //# sourceMappingURL=chunk-LZW5KUXV.js.map
4501
+ //# sourceMappingURL=chunk-HGGAXLRO.js.map