@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.
- package/LICENSE +1 -4
- package/README.md +2 -0
- package/dist/bin.js +1 -2
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-SZ63UDGU.js → chunk-XMNCAB2A.js} +122 -590
- package/dist/chunk-XMNCAB2A.js.map +1 -0
- package/dist/server.js +1 -2
- package/package.json +9 -4
- package/dist/chunk-IEJ7ZYTZ.js +0 -389
- package/dist/chunk-IEJ7ZYTZ.js.map +0 -1
- package/dist/chunk-SZ63UDGU.js.map +0 -1
- package/dist/graph-UWOAWP4T.js +0 -17
- package/dist/graph-UWOAWP4T.js.map +0 -1
|
@@ -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
|
|
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
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
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("
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
1736
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
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 =
|
|
2236
|
-
|
|
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
|
|
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:
|
|
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-
|
|
2228
|
+
//# sourceMappingURL=chunk-XMNCAB2A.js.map
|