@fragments-sdk/mcp 0.5.8 → 0.5.10

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.
@@ -1,8 +1,3 @@
1
- import {
2
- ComponentGraphEngine,
3
- deserializeGraph
4
- } from "./chunk-IEJ7ZYTZ.js";
5
-
6
1
  // src/server.ts
7
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -12,7 +7,7 @@ import {
12
7
  } from "@modelcontextprotocol/sdk/types.js";
13
8
  import { readFile } from "fs/promises";
14
9
  import { existsSync as existsSync2 } from "fs";
15
- import { readFileSync as readFileSync2 } from "fs";
10
+ import { readFileSync as readFileSync3 } from "fs";
16
11
  import { join as join2 } from "path";
17
12
  import { fileURLToPath } from "url";
18
13
 
@@ -89,546 +84,9 @@ var DEFAULTS = {
89
84
  port: 6006
90
85
  };
91
86
 
92
- // ../context/dist/chunk-2UQY4VNM.js
93
- var PLACEHOLDER_PATTERNS = [
94
- /^\w+ component is needed$/i,
95
- /^Alternative component is more appropriate$/i,
96
- /^Use \w+ when you need/i
97
- ];
98
- function filterPlaceholders(items) {
99
- if (!items) return [];
100
- return items.filter(
101
- (item) => !PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(item.trim()))
102
- );
103
- }
104
- function generateContext(fragments, options = {}, blocks) {
105
- const format = options.format ?? "markdown";
106
- const compact = options.compact ?? false;
107
- const include = {
108
- props: options.include?.props ?? true,
109
- variants: options.include?.variants ?? true,
110
- usage: options.include?.usage ?? true,
111
- relations: options.include?.relations ?? false,
112
- code: options.include?.code ?? false
113
- };
114
- const sorted = [...fragments].sort((a, b) => {
115
- const catCompare = a.meta.category.localeCompare(b.meta.category);
116
- if (catCompare !== 0) return catCompare;
117
- return a.meta.name.localeCompare(b.meta.name);
118
- });
119
- if (format === "json") {
120
- return generateJsonContext(sorted, include, compact, blocks);
121
- }
122
- return generateMarkdownContext(sorted, include, compact, blocks);
123
- }
124
- function generateMarkdownContext(fragments, include, compact, blocks) {
125
- const lines = [];
126
- lines.push("# Design System Reference");
127
- lines.push("");
128
- lines.push("## Quick Reference");
129
- lines.push("");
130
- lines.push("| Component | Category | Use For |");
131
- lines.push("|-----------|----------|---------|");
132
- for (const fragment of fragments) {
133
- const filteredWhen = filterPlaceholders(fragment.usage.when);
134
- const useFor = filteredWhen.slice(0, 2).join(", ") || fragment.meta.description;
135
- lines.push(`| ${fragment.meta.name} | ${fragment.meta.category} | ${truncate(useFor, 50)} |`);
136
- }
137
- lines.push("");
138
- if (compact) {
139
- const content2 = lines.join("\n");
140
- return { content: content2, tokenEstimate: estimateTokens(content2) };
141
- }
142
- lines.push("## Components");
143
- lines.push("");
144
- for (const fragment of fragments) {
145
- lines.push(`### ${fragment.meta.name}`);
146
- lines.push("");
147
- const statusParts = [`**Category:** ${fragment.meta.category}`];
148
- if (fragment.meta.status) {
149
- statusParts.push(`**Status:** ${fragment.meta.status}`);
150
- }
151
- lines.push(statusParts.join(" | "));
152
- lines.push("");
153
- if (fragment.meta.description) {
154
- lines.push(fragment.meta.description);
155
- lines.push("");
156
- }
157
- const whenFiltered = filterPlaceholders(fragment.usage.when);
158
- const whenNotFiltered = filterPlaceholders(fragment.usage.whenNot);
159
- if (include.usage && (whenFiltered.length > 0 || whenNotFiltered.length > 0)) {
160
- if (whenFiltered.length > 0) {
161
- lines.push("**When to use:**");
162
- for (const when of whenFiltered) {
163
- lines.push(`- ${when}`);
164
- }
165
- lines.push("");
166
- }
167
- if (whenNotFiltered.length > 0) {
168
- lines.push("**When NOT to use:**");
169
- for (const whenNot of whenNotFiltered) {
170
- lines.push(`- ${whenNot}`);
171
- }
172
- lines.push("");
173
- }
174
- }
175
- if (include.props && Object.keys(fragment.props).length > 0) {
176
- lines.push("**Props:**");
177
- for (const [name, prop] of Object.entries(fragment.props)) {
178
- lines.push(`- \`${name}\`: ${formatPropType(prop)}${prop.required ? " (required)" : ""}`);
179
- }
180
- lines.push("");
181
- }
182
- if (include.variants && fragment.variants.length > 0) {
183
- const variantNames = fragment.variants.map((v) => v.name).join(", ");
184
- lines.push(`**Variants:** ${variantNames}`);
185
- lines.push("");
186
- if (include.code) {
187
- for (const variant of fragment.variants) {
188
- if (variant.code) {
189
- lines.push(`*${variant.name}:*`);
190
- lines.push("```tsx");
191
- lines.push(variant.code);
192
- lines.push("```");
193
- lines.push("");
194
- }
195
- }
196
- }
197
- }
198
- if (include.relations && fragment.relations && fragment.relations.length > 0) {
199
- lines.push("**Related:**");
200
- for (const relation of fragment.relations) {
201
- lines.push(`- ${relation.component} (${relation.relationship}): ${relation.note}`);
202
- }
203
- lines.push("");
204
- }
205
- lines.push("---");
206
- lines.push("");
207
- }
208
- if (blocks && blocks.length > 0) {
209
- lines.push("## Blocks");
210
- lines.push("");
211
- lines.push("Composition patterns showing how components wire together.");
212
- lines.push("");
213
- for (const block of blocks) {
214
- lines.push(`### ${block.name}`);
215
- lines.push("");
216
- lines.push(block.description);
217
- lines.push("");
218
- lines.push(`**Category:** ${block.category}`);
219
- lines.push(`**Components:** ${block.components.join(", ")}`);
220
- if (block.tags && block.tags.length > 0) {
221
- lines.push(`**Tags:** ${block.tags.join(", ")}`);
222
- }
223
- lines.push("");
224
- lines.push("```tsx");
225
- lines.push(block.code);
226
- lines.push("```");
227
- lines.push("");
228
- lines.push("---");
229
- lines.push("");
230
- }
231
- }
232
- const content = lines.join("\n");
233
- return { content, tokenEstimate: estimateTokens(content) };
234
- }
235
- function generateJsonContext(fragments, include, compact, blocks) {
236
- const categories = [...new Set(fragments.map((s) => s.meta.category))].sort();
237
- const components = {};
238
- for (const fragment of fragments) {
239
- const component = {
240
- category: fragment.meta.category,
241
- description: fragment.meta.description
242
- };
243
- if (fragment.meta.status) {
244
- component.status = fragment.meta.status;
245
- }
246
- if (!compact) {
247
- if (include.usage) {
248
- const whenFiltered = filterPlaceholders(fragment.usage.when);
249
- const whenNotFiltered = filterPlaceholders(fragment.usage.whenNot);
250
- if (whenFiltered.length > 0) component.whenToUse = whenFiltered;
251
- if (whenNotFiltered.length > 0) component.whenNotToUse = whenNotFiltered;
252
- }
253
- if (include.props && Object.keys(fragment.props).length > 0) {
254
- component.props = {};
255
- for (const [name, prop] of Object.entries(fragment.props)) {
256
- component.props[name] = {
257
- type: formatPropType(prop),
258
- description: prop.description
259
- };
260
- if (prop.required) component.props[name].required = true;
261
- if (prop.default !== void 0) component.props[name].default = prop.default;
262
- }
263
- }
264
- if (include.variants && fragment.variants.length > 0) {
265
- component.variants = fragment.variants.map((v) => v.name);
266
- }
267
- if (include.relations && fragment.relations && fragment.relations.length > 0) {
268
- component.relations = fragment.relations.map((r) => ({
269
- component: r.component,
270
- relationship: r.relationship,
271
- note: r.note
272
- }));
273
- }
274
- }
275
- components[fragment.meta.name] = component;
276
- }
277
- const blocksMap = blocks && blocks.length > 0 ? Object.fromEntries(blocks.map((b) => [b.name, {
278
- description: b.description,
279
- category: b.category,
280
- components: b.components,
281
- code: b.code,
282
- tags: b.tags
283
- }])) : void 0;
284
- const output = {
285
- version: "1.0",
286
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
287
- summary: {
288
- totalComponents: fragments.length,
289
- categories,
290
- ...blocksMap && { totalBlocks: blocks.length }
291
- },
292
- components,
293
- ...blocksMap && { blocks: blocksMap }
294
- };
295
- const content = JSON.stringify(output, null, 2);
296
- return { content, tokenEstimate: estimateTokens(content) };
297
- }
298
- function formatPropType(prop) {
299
- if (prop.type === "enum" && prop.values) {
300
- return prop.values.map((v) => `"${v}"`).join(" | ");
301
- }
302
- if (prop.default !== void 0) {
303
- return `${prop.type} (default: ${JSON.stringify(prop.default)})`;
304
- }
305
- return prop.type;
306
- }
307
- function truncate(str, maxLength) {
308
- if (str.length <= maxLength) return str;
309
- return str.slice(0, maxLength - 3) + "...";
310
- }
311
- function estimateTokens(text) {
312
- return Math.ceil(text.length / 4);
313
- }
314
-
315
- // ../context/dist/chunk-NECSN43I.js
316
- var MCP_TOOL_DEFINITIONS = [
317
- {
318
- key: "discover",
319
- description: "Discover components in the design system. Use with no params to list all components. Use 'useCase' for AI-powered suggestions. Use 'component' to find alternatives. Use 'compact' for a token-efficient overview.",
320
- params: {
321
- useCase: {
322
- type: "string",
323
- description: 'Description of what you want to build \u2014 returns ranked suggestions (e.g., "form for user email input", "button to submit data")'
324
- },
325
- component: {
326
- type: "string",
327
- description: 'Component name to find alternatives for (e.g., "Button")'
328
- },
329
- category: {
330
- type: "string",
331
- description: 'Filter by category (e.g., "actions", "forms", "layout")'
332
- },
333
- search: {
334
- type: "string",
335
- description: "Search term to filter by name, description, or tags"
336
- },
337
- status: {
338
- type: "string",
339
- enum: ["stable", "beta", "deprecated", "experimental"],
340
- description: "Filter by component status"
341
- },
342
- format: {
343
- type: "string",
344
- enum: ["markdown", "json"],
345
- description: "Output format for context mode (default: markdown)"
346
- },
347
- compact: {
348
- type: "boolean",
349
- description: "If true, returns minimal output (just component names and categories)"
350
- },
351
- includeCode: {
352
- type: "boolean",
353
- description: "If true, includes code examples for each variant"
354
- },
355
- limit: {
356
- type: "number",
357
- description: "Maximum number of results to return (default: 10 for useCase mode)"
358
- },
359
- includeRelations: {
360
- type: "boolean",
361
- description: "If true, includes component relationships"
362
- },
363
- verbosity: {
364
- type: "string",
365
- enum: ["compact", "standard", "full"],
366
- description: 'Response detail level: "compact" (names only), "standard" (default), "full" (everything including code)'
367
- }
368
- }
369
- },
370
- {
371
- key: "inspect",
372
- description: "Get detailed information about a specific component: props, usage guidelines, code examples, accessibility \u2014 all in one call. Use 'fields' to request only specific data for token efficiency.",
373
- params: {
374
- component: {
375
- type: "string",
376
- description: 'Component name (e.g., "Button", "Input")'
377
- },
378
- fields: {
379
- type: "array",
380
- items: { type: "string" },
381
- description: 'Specific fields to return (e.g., ["meta", "guidelines.when", "contract.propsSummary", "props", "examples"]). If omitted, returns everything. Supports dot notation. Aliases: "usage" \u2192 "guidelines".'
382
- },
383
- variant: {
384
- type: "string",
385
- description: 'Filter examples to a specific variant name (e.g., "Default", "Primary")'
386
- },
387
- maxExamples: {
388
- type: "number",
389
- description: "Maximum number of code examples to return (default: all)"
390
- },
391
- maxLines: {
392
- type: "number",
393
- description: "Maximum lines per code example (truncates longer examples)"
394
- },
395
- verbosity: {
396
- type: "string",
397
- enum: ["compact", "standard", "full"],
398
- description: 'Response detail level: "compact" (meta + prop names), "standard" (default), "full" (everything)'
399
- }
400
- },
401
- required: ["component"]
402
- },
403
- {
404
- key: "blocks",
405
- description: 'Search and retrieve composition blocks \u2014 named patterns showing how design system components wire together for common use cases (e.g., "Login Form", "Settings Page"). Returns the block with its code pattern.',
406
- params: {
407
- name: {
408
- type: "string",
409
- description: 'Exact block name to retrieve (e.g., "Login Form")'
410
- },
411
- search: {
412
- type: "string",
413
- description: "Free-text search across block names, descriptions, tags, and components"
414
- },
415
- component: {
416
- type: "string",
417
- description: 'Filter blocks that use a specific component (e.g., "Button")'
418
- },
419
- category: {
420
- type: "string",
421
- description: 'Filter by category (e.g., "authentication", "marketing", "dashboard", "settings", "ecommerce", "ai")'
422
- },
423
- limit: {
424
- type: "number",
425
- description: "Maximum number of blocks to return (default: all matching)"
426
- },
427
- verbosity: {
428
- type: "string",
429
- enum: ["compact", "standard", "full"],
430
- description: 'Response detail level: "compact" (no code), "standard" (default, code preview for long blocks), "full" (full code)'
431
- }
432
- }
433
- },
434
- {
435
- key: "tokens",
436
- description: "List available CSS design tokens (custom properties) by category. Use this when you need to style custom elements or override defaults \u2014 no more guessing variable names. Filter by category or search by keyword.",
437
- params: {
438
- category: {
439
- type: "string",
440
- description: 'Filter by category (e.g., "colors", "spacing", "typography", "surfaces", "shadows", "radius", "borders", "text", "focus", "layout", "code", "component-sizing")'
441
- },
442
- search: {
443
- type: "string",
444
- description: 'Search token names (e.g., "accent", "hover", "padding")'
445
- },
446
- limit: {
447
- type: "number",
448
- description: "Maximum number of tokens to return per category (default: 25 for search, unlimited for category browsing)"
449
- }
450
- }
451
- },
452
- {
453
- key: "implement",
454
- description: "One-shot implementation helper. Describe what you want to build and get everything needed in a single call: best-matching component(s) with full props and code examples, relevant composition blocks, and applicable CSS tokens. Saves multiple round-trips.",
455
- params: {
456
- useCase: {
457
- type: "string",
458
- description: 'What you want to implement (e.g., "login form", "data table with sorting", "streaming chat messages")'
459
- },
460
- limit: {
461
- type: "number",
462
- description: "Maximum number of components to return (default: 5)"
463
- },
464
- verbosity: {
465
- type: "string",
466
- enum: ["compact", "standard", "full"],
467
- description: 'Response detail level: "compact" (names only), "standard" (default), "full" (all props + examples + full block code)'
468
- }
469
- },
470
- required: ["useCase"]
471
- },
472
- {
473
- key: "render",
474
- description: "Render a component and return a screenshot. Optionally compare against a Figma design ('figmaUrl'). Requires a running Fragments dev server (viewer URL). Use this to verify your implementation looks correct.",
475
- params: {
476
- component: {
477
- type: "string",
478
- description: 'Component name (e.g., "Button", "Card", "Input")'
479
- },
480
- props: {
481
- type: "object",
482
- description: 'Props to pass to the component (e.g., { "variant": "primary", "children": "Click me" })'
483
- },
484
- viewport: {
485
- type: "object",
486
- properties: {
487
- width: {
488
- type: "number",
489
- description: "Viewport width (default: 800)"
490
- },
491
- height: {
492
- type: "number",
493
- description: "Viewport height (default: 600)"
494
- }
495
- },
496
- description: "Optional viewport size for the render"
497
- },
498
- figmaUrl: {
499
- type: "string",
500
- description: "Figma frame URL \u2014 if provided, compares the render against the Figma design"
501
- },
502
- variant: {
503
- type: "string",
504
- description: "Variant name to render (uses the variant's render function from the fragment definition). Works in both render and compare modes."
505
- },
506
- threshold: {
507
- type: "number",
508
- description: "Diff threshold percentage (default: 1 for Figma)"
509
- }
510
- },
511
- required: ["component"]
512
- },
513
- {
514
- key: "fix",
515
- description: "Generate patches to fix token compliance issues in a component. Returns unified diff patches that replace hardcoded CSS values with design token references. Requires a running Fragments dev server.",
516
- params: {
517
- component: {
518
- type: "string",
519
- description: 'Component name to generate fixes for (e.g., "Button", "Card")'
520
- },
521
- variant: {
522
- type: "string",
523
- description: "Specific variant to fix (optional, fixes all variants if omitted)"
524
- },
525
- fixType: {
526
- type: "string",
527
- enum: ["token", "all"],
528
- description: 'Type of fixes to generate: "token" for hardcoded\u2192token replacements, "all" for all available fixes (default: "all")'
529
- }
530
- },
531
- required: ["component"]
532
- },
533
- {
534
- key: "graph",
535
- description: 'Query the component relationship graph. Understand dependencies, impact analysis, composition trees, alternatives, and design system health. Use "health" for an overview, "dependencies"/"dependents" for composition and declared relationships (not code-level imports), "impact" for change analysis, "composition" for compound component trees.',
536
- params: {
537
- mode: {
538
- type: "string",
539
- enum: ["dependencies", "dependents", "impact", "path", "composition", "alternatives", "islands", "health"],
540
- description: "Query mode"
541
- },
542
- component: {
543
- type: "string",
544
- description: "Component name (required for most modes)"
545
- },
546
- target: {
547
- type: "string",
548
- description: 'Target component for "path" mode'
549
- },
550
- edgeTypes: {
551
- type: "array",
552
- items: { type: "string" },
553
- description: "Filter by edge types (imports, hook-depends, renders, composes, parent-of, alternative-to, sibling-of)"
554
- },
555
- maxDepth: {
556
- type: "number",
557
- description: "Max traversal depth for impact mode (default: 3)"
558
- }
559
- },
560
- required: ["mode"]
561
- },
562
- {
563
- key: "a11y",
564
- description: "Run an accessibility audit on a component. Returns axe-core violations, a WCAG compliance score, and optional fix suggestions. Requires a running Fragments dev server.",
565
- params: {
566
- component: {
567
- type: "string",
568
- description: 'Component name to audit (e.g., "Button", "Card")'
569
- },
570
- variant: {
571
- type: "string",
572
- description: "Specific variant to audit (optional, audits all variants if omitted)"
573
- },
574
- standard: {
575
- type: "string",
576
- enum: ["AA", "AAA"],
577
- description: "WCAG compliance level to check against (default: AA)"
578
- },
579
- includeFixPatches: {
580
- type: "boolean",
581
- description: "If true, includes auto-fix suggestions for each violation"
582
- }
583
- },
584
- required: ["component"]
585
- }
586
- ];
587
- function buildToolNames(prefix) {
588
- const map = {};
589
- for (const def of MCP_TOOL_DEFINITIONS) {
590
- map[def.key] = `${prefix}_${def.key}`;
591
- }
592
- return map;
593
- }
594
- function buildMcpTools(prefix, extensions) {
595
- const extMap = /* @__PURE__ */ new Map();
596
- if (extensions) {
597
- for (const ext of extensions) {
598
- extMap.set(ext.key, ext);
599
- }
600
- }
601
- return MCP_TOOL_DEFINITIONS.map((def) => {
602
- const ext = extMap.get(def.key);
603
- const mergedParams = ext ? { ...def.params, ...ext.params } : def.params;
604
- const properties = {};
605
- for (const [name, param] of Object.entries(mergedParams)) {
606
- const prop = {
607
- type: param.type,
608
- description: param.description
609
- };
610
- if (param.enum) prop.enum = param.enum;
611
- if (param.items) prop.items = param.items;
612
- if (param.properties) {
613
- const nested = {};
614
- for (const [k, v] of Object.entries(param.properties)) {
615
- nested[k] = { type: v.type, description: v.description };
616
- }
617
- prop.properties = nested;
618
- }
619
- properties[name] = prop;
620
- }
621
- return {
622
- name: `${prefix}_${def.key}`,
623
- description: ext?.description ?? def.description,
624
- inputSchema: {
625
- type: "object",
626
- properties,
627
- ...def.required && { required: def.required }
628
- }
629
- };
630
- });
631
- }
87
+ // src/server.ts
88
+ import { generateContext, filterPlaceholders } from "@fragments-sdk/context/generate";
89
+ import { buildMcpTools, buildToolNames } from "@fragments-sdk/context/mcp-tools";
632
90
 
633
91
  // src/discovery.ts
634
92
  import { existsSync, readFileSync, readdirSync } from "fs";
@@ -1137,7 +595,7 @@ async function hybridSearch(query, data, limit = 10, kind, apiKey) {
1137
595
  const graphBoostResults = [];
1138
596
  if (data.graph) {
1139
597
  try {
1140
- const { ComponentGraphEngine: ComponentGraphEngine2, deserializeGraph: deserializeGraph2 } = await import("./graph-UWOAWP4T.js");
598
+ const { ComponentGraphEngine: ComponentGraphEngine2, deserializeGraph: deserializeGraph2 } = await import("@fragments-sdk/context/graph");
1141
599
  const graph = deserializeGraph2(data.graph);
1142
600
  const engine = new ComponentGraphEngine2(graph);
1143
601
  const topComponents = [...keywordResults, ...vectorResults].filter((r) => r.kind === "component").slice(0, 5);
@@ -1378,6 +836,10 @@ function projectFields(obj, fields) {
1378
836
  }
1379
837
 
1380
838
  // src/graph-handler.ts
839
+ import {
840
+ ComponentGraphEngine,
841
+ deserializeGraph
842
+ } from "@fragments-sdk/context/graph";
1381
843
  function handleGraphTool(args, serializedGraph, blocks, componentNames) {
1382
844
  if (!serializedGraph) {
1383
845
  return {
@@ -1556,6 +1018,78 @@ function handleGraphTool(args, serializedGraph, blocks, componentNames) {
1556
1018
  }
1557
1019
  }
1558
1020
 
1021
+ // src/server-helpers.ts
1022
+ function normalizeFilter(value) {
1023
+ const normalized = value?.trim().toLowerCase();
1024
+ return normalized && normalized.length > 0 ? normalized : void 0;
1025
+ }
1026
+ function categoryMatches(category, categoryFilter) {
1027
+ if (!categoryFilter) return true;
1028
+ return normalizeFilter(category) === categoryFilter;
1029
+ }
1030
+ function buildLocalSearchData(data, indexes) {
1031
+ const allFragments = Object.values(data.fragments);
1032
+ const allBlocks = Object.values(data.blocks ?? data.recipes ?? {});
1033
+ const localData = {
1034
+ fragments: allFragments,
1035
+ blocks: allBlocks,
1036
+ tokenData: data.tokens,
1037
+ graph: data.graph,
1038
+ componentIndex: indexes.componentIndex ?? void 0,
1039
+ blockIndex: indexes.blockIndex ?? void 0,
1040
+ tokenIndex: indexes.tokenIndex ?? void 0
1041
+ };
1042
+ return { allFragments, allBlocks, localData };
1043
+ }
1044
+ async function buildImportStatements(components, resolvePackageName) {
1045
+ const grouped = /* @__PURE__ */ new Map();
1046
+ for (const component of components) {
1047
+ if (!component) continue;
1048
+ const packageName = await resolvePackageName(component);
1049
+ const existing = grouped.get(packageName);
1050
+ if (!existing) {
1051
+ grouped.set(packageName, [component]);
1052
+ continue;
1053
+ }
1054
+ if (!existing.includes(component)) {
1055
+ existing.push(component);
1056
+ }
1057
+ }
1058
+ return Array.from(grouped.entries()).map(
1059
+ ([packageName, componentNames]) => `import { ${componentNames.join(", ")} } from '${packageName}';`
1060
+ );
1061
+ }
1062
+ function limitTokensPerCategory(categories, limit) {
1063
+ if (limit === void 0) {
1064
+ return {
1065
+ categories,
1066
+ total: Object.values(categories).reduce((sum, tokens) => sum + tokens.length, 0)
1067
+ };
1068
+ }
1069
+ const limited = {};
1070
+ let total = 0;
1071
+ for (const [category, tokens] of Object.entries(categories)) {
1072
+ const sliced = tokens.slice(0, limit);
1073
+ if (sliced.length === 0) continue;
1074
+ limited[category] = sliced;
1075
+ total += sliced.length;
1076
+ }
1077
+ return { categories: limited, total };
1078
+ }
1079
+
1080
+ // src/version.ts
1081
+ import { readFileSync as readFileSync2 } from "fs";
1082
+ function readPackageVersion() {
1083
+ try {
1084
+ const raw = readFileSync2(new URL("../package.json", import.meta.url), "utf-8");
1085
+ const pkg = JSON.parse(raw);
1086
+ return pkg.version ?? "0.0.0";
1087
+ } catch {
1088
+ return "0.0.0";
1089
+ }
1090
+ }
1091
+ var MCP_SERVER_VERSION = readPackageVersion();
1092
+
1559
1093
  // src/server.ts
1560
1094
  var TOOL_NAMES = buildToolNames(BRAND.nameLower);
1561
1095
  var NO_VIEWER_MSG = "This tool requires a running dev server. Add --viewer-url http://localhost:PORT to this server's config args. Start the viewer with 'fragments dev' in your component library directory.";
@@ -1564,7 +1098,7 @@ function createMcpServer(config) {
1564
1098
  const server = new Server(
1565
1099
  {
1566
1100
  name: `${BRAND.nameLower}-mcp`,
1567
- version: "0.3.0"
1101
+ version: MCP_SERVER_VERSION
1568
1102
  },
1569
1103
  {
1570
1104
  capabilities: {
@@ -1662,7 +1196,7 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
1662
1196
  const packageJsonPath = join2(root, "package.json");
1663
1197
  if (existsSync2(packageJsonPath)) {
1664
1198
  try {
1665
- const content = readFileSync2(packageJsonPath, "utf-8");
1199
+ const content = readFileSync3(packageJsonPath, "utf-8");
1666
1200
  const pkg = JSON.parse(content);
1667
1201
  if (pkg.name) {
1668
1202
  defaultPackageName = pkg.name;
@@ -1695,7 +1229,7 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
1695
1229
  const data = await loadFragments();
1696
1230
  const useCase = args?.useCase ?? void 0;
1697
1231
  const componentForAlts = args?.component ?? void 0;
1698
- const category = args?.category ?? void 0;
1232
+ const category = normalizeFilter(args?.category);
1699
1233
  const search2 = args?.search?.toLowerCase() ?? void 0;
1700
1234
  const status = args?.status ?? void 0;
1701
1235
  const format = args?.format ?? "markdown";
@@ -1708,7 +1242,7 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
1708
1242
  let fragments2 = Object.values(data.fragments);
1709
1243
  const allBlocks = Object.values(data.blocks ?? data.recipes ?? {});
1710
1244
  if (category) {
1711
- fragments2 = fragments2.filter((f) => f.meta.category?.toLowerCase() === category);
1245
+ fragments2 = fragments2.filter((f) => categoryMatches(f.meta.category, category));
1712
1246
  }
1713
1247
  if (search2) {
1714
1248
  fragments2 = fragments2.filter(
@@ -1732,18 +1266,16 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
1732
1266
  };
1733
1267
  }
1734
1268
  if (useCase) {
1735
- const allFragments = Object.values(data.fragments);
1736
- const allBlocks = Object.values(data.blocks ?? data.recipes ?? {});
1269
+ const { allFragments, allBlocks, localData } = buildLocalSearchData(
1270
+ data,
1271
+ {
1272
+ componentIndex,
1273
+ blockIndex,
1274
+ tokenIndex
1275
+ }
1276
+ );
1737
1277
  const context = args?.context?.toLowerCase() ?? "";
1738
1278
  const fullQuery = context ? `${useCase} ${context}` : useCase;
1739
- const localData = {
1740
- fragments: allFragments,
1741
- blocks: allBlocks,
1742
- tokenData: data.tokens,
1743
- componentIndex: componentIndex ?? void 0,
1744
- blockIndex: blockIndex ?? void 0,
1745
- tokenIndex: tokenIndex ?? void 0
1746
- };
1747
1279
  const searchResults = await hybridSearch(fullQuery, localData, limit, "component", config.apiKey);
1748
1280
  const blockMatches = keywordScoreBlocks(fullQuery, allBlocks, blockIndex ?? void 0).slice(0, 5);
1749
1281
  if (blockMatches.length > 0) {
@@ -1874,7 +1406,7 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
1874
1406
  };
1875
1407
  }
1876
1408
  const fragments = Object.values(data.fragments).filter((s) => {
1877
- if (category && s.meta.category !== category) return false;
1409
+ if (category && !categoryMatches(s.meta.category, category)) return false;
1878
1410
  if (status && (s.meta.status ?? "stable") !== status) return false;
1879
1411
  if (search2) {
1880
1412
  const nameMatch = s.meta.name.toLowerCase().includes(search2);
@@ -2107,23 +1639,27 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
2107
1639
  if (blocksLimit !== void 0) {
2108
1640
  filtered = filtered.slice(0, blocksLimit);
2109
1641
  }
2110
- const pkgName = fragmentPackageMap.values().next().value ?? "your-component-library";
2111
1642
  const verbosity = args?.verbosity ?? "standard";
2112
- const blocksWithImports = filtered.map((b) => {
1643
+ const blocksWithImports = await Promise.all(filtered.map(async (b) => {
1644
+ const imports = await buildImportStatements(
1645
+ b.components,
1646
+ async (componentName) => getPackageName(componentName)
1647
+ );
2113
1648
  const base = {
2114
1649
  name: b.name,
2115
1650
  description: b.description,
2116
1651
  category: b.category,
2117
1652
  components: b.components,
2118
1653
  tags: b.tags,
2119
- import: `import { ${b.components.join(", ")} } from '${pkgName}';`
1654
+ import: imports.join("\n"),
1655
+ imports
2120
1656
  };
2121
1657
  if (verbosity === "compact") return base;
2122
1658
  if (verbosity === "full") return { ...base, code: b.code };
2123
1659
  const codeLines = b.code.split("\n");
2124
1660
  const code = codeLines.length > 30 ? codeLines.slice(0, 20).join("\n") + "\n// ... truncated (" + codeLines.length + " lines total)" : b.code;
2125
1661
  return { ...base, code };
2126
- });
1662
+ }));
2127
1663
  return {
2128
1664
  content: [{
2129
1665
  type: "text",
@@ -2180,15 +1716,9 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
2180
1716
  }
2181
1717
  }
2182
1718
  if (tokensLimit !== void 0) {
2183
- let remaining = tokensLimit;
2184
- const limited = {};
2185
- for (const [cat, tokens] of Object.entries(filteredCategories)) {
2186
- if (remaining <= 0) break;
2187
- limited[cat] = tokens.slice(0, remaining);
2188
- remaining -= limited[cat].length;
2189
- }
2190
- filteredCategories = limited;
2191
- filteredTotal = Math.min(filteredTotal, tokensLimit);
1719
+ const limited = limitTokensPerCategory(filteredCategories, tokensLimit);
1720
+ filteredCategories = limited.categories;
1721
+ filteredTotal = limited.total;
2192
1722
  }
2193
1723
  let hint;
2194
1724
  if (filteredTotal === 0) {
@@ -2232,18 +1762,16 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
2232
1762
  throw new Error("useCase is required");
2233
1763
  }
2234
1764
  const verbosity = args?.verbosity ?? "standard";
2235
- const allFragments = Object.values(data.fragments);
2236
- const allBlocks = Object.values(data.blocks ?? data.recipes ?? {});
1765
+ const { allFragments, allBlocks, localData } = buildLocalSearchData(
1766
+ data,
1767
+ {
1768
+ componentIndex,
1769
+ blockIndex,
1770
+ tokenIndex
1771
+ }
1772
+ );
2237
1773
  const tokenData = data.tokens;
2238
1774
  const implLimit = typeof args?.limit === "number" ? Math.min(Math.max(args.limit, 1), 15) : 5;
2239
- const localData = {
2240
- fragments: allFragments,
2241
- blocks: allBlocks,
2242
- tokenData,
2243
- componentIndex: componentIndex ?? void 0,
2244
- blockIndex: blockIndex ?? void 0,
2245
- tokenIndex: tokenIndex ?? void 0
2246
- };
2247
1775
  const [componentResults, blockResults, tokenResults] = await Promise.all([
2248
1776
  hybridSearch(useCase, localData, implLimit * 3, "component", config.apiKey),
2249
1777
  hybridSearch(useCase, localData, implLimit, "block", config.apiKey),
@@ -2297,12 +1825,15 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
2297
1825
  };
2298
1826
  })
2299
1827
  );
2300
- const matchingBlocks = filteredBlockResults.slice(0, 5).map((result) => {
1828
+ const matchingBlocks = (await Promise.all(filteredBlockResults.slice(0, 5).map(async (result) => {
2301
1829
  const block = allBlocks.find(
2302
1830
  (b) => b.name.toLowerCase() === result.name.toLowerCase()
2303
1831
  );
2304
1832
  if (!block) return null;
2305
- const pkgName = fragmentPackageMap.values().next().value ?? "your-component-library";
1833
+ const imports = await buildImportStatements(
1834
+ block.components,
1835
+ async (componentName) => getPackageName(componentName)
1836
+ );
2306
1837
  const codeLines = block.code.split("\n");
2307
1838
  const code = codeLines.length > 30 ? codeLines.slice(0, 20).join("\n") + "\n// ... truncated (" + codeLines.length + " lines total)" : block.code;
2308
1839
  return {
@@ -2310,9 +1841,10 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
2310
1841
  description: block.description,
2311
1842
  components: block.components,
2312
1843
  code,
2313
- import: `import { ${block.components.join(", ")} } from '${pkgName}';`
1844
+ import: imports.join("\n"),
1845
+ imports
2314
1846
  };
2315
- }).filter(Boolean);
1847
+ }))).filter(Boolean);
2316
1848
  let relevantTokens;
2317
1849
  if (tokenResults.length > 0 && tokenData) {
2318
1850
  relevantTokens = {};
@@ -2693,4 +2225,4 @@ export {
2693
2225
  startMcpServer,
2694
2226
  createSandboxServer
2695
2227
  };
2696
- //# sourceMappingURL=chunk-SZ63UDGU.js.map
2228
+ //# sourceMappingURL=chunk-XMNCAB2A.js.map