@deepgram/styles 0.2.12 → 0.2.14

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.
@@ -0,0 +1,477 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server for @deepgram/styles
4
+ *
5
+ * Exposes the design system (tokens, components, examples) as MCP tools
6
+ * so AI agents can query BEM classes, rendered HTML examples, and tokens.
7
+ *
8
+ * Usage:
9
+ * npx deepgram-styles-mcp # uses bundled design-system.yaml
10
+ * node dist/mcp/server.js --yaml /path/to/design-system.yaml
11
+ */
12
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
+ import { readFileSync } from "node:fs";
15
+ import { resolve, dirname } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+ import { parseDocument } from "yaml";
18
+ import { z } from "zod";
19
+ // ---------------------------------------------------------------------------
20
+ // Type guard
21
+ // ---------------------------------------------------------------------------
22
+ function isUseRefNode(node) {
23
+ return (typeof node === "object" &&
24
+ node !== null &&
25
+ "$ref" in node &&
26
+ typeof node.$ref === "string" &&
27
+ !("tag" in node));
28
+ }
29
+ // ---------------------------------------------------------------------------
30
+ // Inline AST → HTML renderer (from generators/html.ts)
31
+ // ---------------------------------------------------------------------------
32
+ const SELF_CLOSING = new Set(["img", "br", "hr", "input", "meta", "link"]);
33
+ function formatHTMLProps(props) {
34
+ return Object.entries(props)
35
+ .map(([key, value]) => {
36
+ if (typeof value === "boolean")
37
+ return value ? key : "";
38
+ const attrName = key === "className" ? "class" : key;
39
+ return `${attrName}="${value}"`;
40
+ })
41
+ .filter(Boolean)
42
+ .join(" ");
43
+ }
44
+ function indentStr(code, level = 1) {
45
+ const spaces = " ".repeat(level);
46
+ return code
47
+ .split("\n")
48
+ .map((line) => (line ? spaces + line : line))
49
+ .join("\n");
50
+ }
51
+ function astToHTML(node, depth = 0) {
52
+ const { tag, props = {}, children = [], text } = node;
53
+ const propsStr = Object.keys(props).length > 0 ? " " + formatHTMLProps(props) : "";
54
+ if (SELF_CLOSING.has(tag) && !text && children.length === 0) {
55
+ return `<${tag}${propsStr} />`;
56
+ }
57
+ if (text) {
58
+ return `<${tag}${propsStr}>${text}</${tag}>`;
59
+ }
60
+ if (children.length > 0) {
61
+ const childrenStr = children
62
+ .map((child) => {
63
+ if (typeof child === "string")
64
+ return child;
65
+ if ("tag" in child)
66
+ return astToHTML(child, depth + 1);
67
+ return "";
68
+ })
69
+ .filter(Boolean)
70
+ .join("\n");
71
+ if (children.length > 1 ||
72
+ (children[0] && typeof children[0] !== "string")) {
73
+ return `<${tag}${propsStr}>\n${indentStr(childrenStr, 1)}\n</${tag}>`;
74
+ }
75
+ return `<${tag}${propsStr}>${childrenStr}</${tag}>`;
76
+ }
77
+ return `<${tag}${propsStr}></${tag}>`;
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Inline $ref resolver (from parser/resolve-refs.ts)
81
+ // ---------------------------------------------------------------------------
82
+ function resolveComponentRefs(ds) {
83
+ const resolved = { ...ds, components: { ...ds.components } };
84
+ for (const [key, component] of Object.entries(resolved.components)) {
85
+ resolved.components[key] = resolveComponentExamples(ds, component);
86
+ }
87
+ return resolved;
88
+ }
89
+ function resolveComponentExamples(ds, component) {
90
+ const result = { ...component };
91
+ if (result.examples) {
92
+ result.examples = result.examples.map((ex) => ({
93
+ ...ex,
94
+ ast: resolveNode(ds, ex.ast),
95
+ }));
96
+ }
97
+ if (result.variants) {
98
+ result.variants = { ...result.variants };
99
+ for (const [vKey, variant] of Object.entries(result.variants)) {
100
+ if (variant.examples) {
101
+ result.variants[vKey] = {
102
+ ...variant,
103
+ examples: variant.examples.map((ex) => ({
104
+ ...ex,
105
+ ast: resolveNode(ds, ex.ast),
106
+ })),
107
+ };
108
+ }
109
+ }
110
+ }
111
+ if (result.components) {
112
+ result.components = { ...result.components };
113
+ for (const [cKey, child] of Object.entries(result.components)) {
114
+ if (child.examples) {
115
+ result.components[cKey] = {
116
+ ...child,
117
+ examples: child.examples.map((ex) => ({
118
+ ...ex,
119
+ ast: resolveNode(ds, ex.ast),
120
+ })),
121
+ };
122
+ }
123
+ }
124
+ }
125
+ return result;
126
+ }
127
+ function resolveNode(ds, node) {
128
+ if (isUseRefNode(node)) {
129
+ return resolveUseRef(ds, node);
130
+ }
131
+ if (!node.children)
132
+ return node;
133
+ const resolvedChildren = node.children.map((child) => {
134
+ if (typeof child === "string")
135
+ return child;
136
+ return resolveNode(ds, child);
137
+ });
138
+ return { ...node, children: resolvedChildren };
139
+ }
140
+ function resolveUseRef(ds, ref) {
141
+ const segments = ref.$ref.split("/");
142
+ const componentKey = segments[0];
143
+ const component = ds.components[componentKey];
144
+ if (!component) {
145
+ return { tag: "div", props: { class: `dg-${componentKey}` }, text: `[unresolved: ${ref.$ref}]` };
146
+ }
147
+ const blockClass = `dg-${componentKey}`;
148
+ let tag = component.tag ?? "div";
149
+ const classes = [blockClass];
150
+ if (segments.length > 1) {
151
+ const pathSegment = segments.slice(1).join("/");
152
+ const resolved = resolvePathSegment(ds, component, componentKey, blockClass, pathSegment);
153
+ tag = resolved.tag;
154
+ classes.length = 0;
155
+ classes.push(...resolved.classes);
156
+ }
157
+ const mergedProps = {};
158
+ if (classes.length > 0) {
159
+ mergedProps.class = classes.join(" ");
160
+ }
161
+ if (ref.props) {
162
+ for (const [key, value] of Object.entries(ref.props)) {
163
+ if (key === "class" && typeof value === "string") {
164
+ mergedProps.class = mergedProps.class
165
+ ? `${mergedProps.class} ${value}`
166
+ : value;
167
+ }
168
+ else {
169
+ mergedProps[key] = value;
170
+ }
171
+ }
172
+ }
173
+ let resolvedChildren;
174
+ if (ref.children) {
175
+ resolvedChildren = ref.children.map((child) => {
176
+ if (typeof child === "string")
177
+ return child;
178
+ return resolveNode(ds, child);
179
+ });
180
+ }
181
+ const result = { tag, props: mergedProps };
182
+ if (resolvedChildren)
183
+ result.children = resolvedChildren;
184
+ if (ref.text)
185
+ result.text = ref.text;
186
+ return result;
187
+ }
188
+ function resolvePathSegment(ds, component, componentKey, blockClass, pathSegment) {
189
+ const parts = pathSegment.split("+");
190
+ if (parts.length > 1) {
191
+ for (const part of parts) {
192
+ if (!component.variants?.[part]) {
193
+ return {
194
+ tag: component.tag ?? "div",
195
+ classes: [blockClass, ...parts.map((p) => `${blockClass}--${p}`)],
196
+ };
197
+ }
198
+ }
199
+ return {
200
+ tag: component.tag ?? "div",
201
+ classes: [blockClass, ...parts.map((p) => `${blockClass}--${p}`)],
202
+ };
203
+ }
204
+ const name = parts[0];
205
+ if (component.variants?.[name]) {
206
+ return {
207
+ tag: component.tag ?? "div",
208
+ classes: [blockClass, `${blockClass}--${name}`],
209
+ };
210
+ }
211
+ if (component.components?.[name]) {
212
+ const child = component.components[name];
213
+ const elementClass = `dg-${componentKey}__${name}`;
214
+ if (child.$ref) {
215
+ const refComponent = ds.components[child.$ref];
216
+ return {
217
+ tag: child.tag ?? refComponent?.tag ?? "div",
218
+ classes: [`dg-${child.$ref}`, elementClass],
219
+ };
220
+ }
221
+ return { tag: child.tag ?? "div", classes: [elementClass] };
222
+ }
223
+ // Graceful fallback for unknown segments
224
+ return {
225
+ tag: component.tag ?? "div",
226
+ classes: [blockClass, `${blockClass}--${name}`],
227
+ };
228
+ }
229
+ function collectAllExamples(component) {
230
+ const results = [];
231
+ for (const example of component.examples) {
232
+ results.push({ example });
233
+ }
234
+ if (component.variants) {
235
+ for (const [vKey, variant] of Object.entries(component.variants)) {
236
+ if (variant.examples) {
237
+ for (const example of variant.examples) {
238
+ results.push({ example, variantKey: vKey });
239
+ }
240
+ }
241
+ }
242
+ }
243
+ return results;
244
+ }
245
+ // ---------------------------------------------------------------------------
246
+ // CSS class extractor
247
+ // ---------------------------------------------------------------------------
248
+ function extractBEMClasses(component, name) {
249
+ const classes = new Set();
250
+ const blockClass = `dg-${name}`;
251
+ classes.add(blockClass);
252
+ // From css rules
253
+ if (component.css) {
254
+ for (const selector of Object.keys(component.css)) {
255
+ const matches = selector.match(/\.dg-[\w-]+(?:__[\w-]+)?(?:--[\w-]+)?/g);
256
+ if (matches)
257
+ matches.forEach((m) => classes.add(m.replace(/^\./, "")));
258
+ }
259
+ }
260
+ // From variants
261
+ if (component.variants) {
262
+ for (const vKey of Object.keys(component.variants)) {
263
+ classes.add(`${blockClass}--${vKey}`);
264
+ const variant = component.variants[vKey];
265
+ if (variant.css) {
266
+ for (const selector of Object.keys(variant.css)) {
267
+ const matches = selector.match(/\.dg-[\w-]+(?:__[\w-]+)?(?:--[\w-]+)?/g);
268
+ if (matches)
269
+ matches.forEach((m) => classes.add(m.replace(/^\./, "")));
270
+ }
271
+ }
272
+ }
273
+ }
274
+ // From elements/components
275
+ if (component.components) {
276
+ for (const cKey of Object.keys(component.components)) {
277
+ classes.add(`${blockClass}__${cKey}`);
278
+ }
279
+ }
280
+ // From elements (legacy css-based)
281
+ if (component.elements) {
282
+ for (const eKey of Object.keys(component.elements)) {
283
+ classes.add(`${blockClass}__${eKey}`);
284
+ }
285
+ }
286
+ return [...classes].sort();
287
+ }
288
+ // ---------------------------------------------------------------------------
289
+ // Load YAML
290
+ // ---------------------------------------------------------------------------
291
+ function loadDesignSystem(yamlPath) {
292
+ const __dirname = dirname(fileURLToPath(import.meta.url));
293
+ const resolvedPath = yamlPath
294
+ ? resolve(yamlPath)
295
+ : resolve(__dirname, "..", "..", "design-system.yaml");
296
+ const raw = readFileSync(resolvedPath, "utf-8");
297
+ const doc = parseDocument(raw);
298
+ const ds = doc.toJSON();
299
+ return resolveComponentRefs(ds);
300
+ }
301
+ // ---------------------------------------------------------------------------
302
+ // MCP Server
303
+ // ---------------------------------------------------------------------------
304
+ function createServer(ds) {
305
+ const server = new McpServer({
306
+ name: "deepgram-styles",
307
+ version: ds.version ?? "0.0.0",
308
+ });
309
+ // ---- list_components ----
310
+ server.tool("list_components", "List all components in the Deepgram design system. Returns component names, titles, categories, tags, and counts of variants and examples. Use the optional category filter to narrow results.", { category: z.string().optional().describe("Filter by category (e.g. 'application-ui', 'marketing', 'documentation')") }, async ({ category }) => {
311
+ const entries = Object.entries(ds.components)
312
+ .filter(([, comp]) => !category || comp.metadata.category === category)
313
+ .map(([name, comp]) => ({
314
+ name,
315
+ title: comp.metadata.title,
316
+ category: comp.metadata.category,
317
+ section: comp.metadata.section,
318
+ subsection: comp.metadata.subsection,
319
+ tags: comp.metadata.tags ?? [],
320
+ variantCount: comp.variants ? Object.keys(comp.variants).length : 0,
321
+ exampleCount: collectAllExamples(comp).length,
322
+ }));
323
+ return {
324
+ content: [
325
+ {
326
+ type: "text",
327
+ text: JSON.stringify(entries, null, 2),
328
+ },
329
+ ],
330
+ };
331
+ });
332
+ // ---- get_component ----
333
+ server.tool("get_component", "Get full details for a specific Deepgram design system component. Returns BEM CSS classes, variant names, and rendered HTML examples you can copy-paste. Use list_components first to discover available component names.", { name: z.string().describe("Component key (e.g. 'btn', 'card', 'alert')") }, async ({ name }) => {
334
+ const component = ds.components[name];
335
+ if (!component) {
336
+ return {
337
+ content: [
338
+ {
339
+ type: "text",
340
+ text: `Component "${name}" not found. Use list_components to see available components.`,
341
+ },
342
+ ],
343
+ };
344
+ }
345
+ const bemClasses = extractBEMClasses(component, name);
346
+ const variants = component.variants
347
+ ? Object.entries(component.variants).map(([vKey, v]) => ({
348
+ name: vKey,
349
+ class: `dg-${name}--${vKey}`,
350
+ title: v.title,
351
+ description: v.description,
352
+ }))
353
+ : [];
354
+ const examples = collectAllExamples(component).map(({ example, variantKey }) => ({
355
+ title: example.title,
356
+ description: example.description,
357
+ variant: variantKey,
358
+ html: astToHTML(example.ast),
359
+ }));
360
+ const result = {
361
+ name,
362
+ title: component.metadata.title,
363
+ description: component.metadata.description,
364
+ category: component.metadata.category,
365
+ section: component.metadata.section,
366
+ subsection: component.metadata.subsection,
367
+ tags: component.metadata.tags ?? [],
368
+ tag: component.tag ?? "div",
369
+ bemClasses,
370
+ variants,
371
+ examples,
372
+ };
373
+ return {
374
+ content: [
375
+ {
376
+ type: "text",
377
+ text: JSON.stringify(result, null, 2),
378
+ },
379
+ ],
380
+ };
381
+ });
382
+ // ---- get_design_tokens ----
383
+ server.tool("get_design_tokens", "Get design tokens (colors, spacing, fonts, shadows, border-radius, CSS variables) from the Deepgram design system. Tokens define the visual language — use these values instead of hardcoded colors or sizes.", { group: z.string().optional().describe("Token group to return: 'colors', 'spacing', 'fonts', 'shadows', 'border-radius', 'variables'. Omit for all.") }, async ({ group }) => {
384
+ const tokens = ds.tokens;
385
+ if (group) {
386
+ const key = group;
387
+ if (!(key in tokens)) {
388
+ return {
389
+ content: [
390
+ {
391
+ type: "text",
392
+ text: `Token group "${group}" not found. Available groups: ${Object.keys(tokens).join(", ")}`,
393
+ },
394
+ ],
395
+ };
396
+ }
397
+ return {
398
+ content: [
399
+ {
400
+ type: "text",
401
+ text: JSON.stringify({ [key]: tokens[key] }, null, 2),
402
+ },
403
+ ],
404
+ };
405
+ }
406
+ return {
407
+ content: [
408
+ {
409
+ type: "text",
410
+ text: JSON.stringify(tokens, null, 2),
411
+ },
412
+ ],
413
+ };
414
+ });
415
+ // ---- search_components ----
416
+ server.tool("search_components", "Search for Deepgram design system components by keyword. Matches against component name, title, tags, description, category, section, and subsection. Returns matching components with metadata.", { query: z.string().describe("Search keyword (e.g. 'button', 'navigation', 'form', 'card')") }, async ({ query }) => {
417
+ const q = query.toLowerCase();
418
+ const matches = Object.entries(ds.components)
419
+ .filter(([name, comp]) => {
420
+ const searchable = [
421
+ name,
422
+ comp.metadata.title,
423
+ comp.metadata.description ?? "",
424
+ comp.metadata.category,
425
+ comp.metadata.section,
426
+ comp.metadata.subsection,
427
+ ...(comp.metadata.tags ?? []),
428
+ ]
429
+ .join(" ")
430
+ .toLowerCase();
431
+ return searchable.includes(q);
432
+ })
433
+ .map(([name, comp]) => ({
434
+ name,
435
+ title: comp.metadata.title,
436
+ category: comp.metadata.category,
437
+ section: comp.metadata.section,
438
+ tags: comp.metadata.tags ?? [],
439
+ description: comp.metadata.description,
440
+ variantCount: comp.variants ? Object.keys(comp.variants).length : 0,
441
+ exampleCount: collectAllExamples(comp).length,
442
+ }));
443
+ return {
444
+ content: [
445
+ {
446
+ type: "text",
447
+ text: matches.length > 0
448
+ ? JSON.stringify(matches, null, 2)
449
+ : `No components found matching "${query}". Try list_components to see all available components.`,
450
+ },
451
+ ],
452
+ };
453
+ });
454
+ return server;
455
+ }
456
+ // ---------------------------------------------------------------------------
457
+ // CLI entry point
458
+ // ---------------------------------------------------------------------------
459
+ async function main() {
460
+ // Parse --yaml flag
461
+ let yamlPath;
462
+ const args = process.argv.slice(2);
463
+ for (let i = 0; i < args.length; i++) {
464
+ if (args[i] === "--yaml" && args[i + 1]) {
465
+ yamlPath = args[i + 1];
466
+ i++;
467
+ }
468
+ }
469
+ const ds = loadDesignSystem(yamlPath);
470
+ const server = createServer(ds);
471
+ const transport = new StdioServerTransport();
472
+ await server.connect(transport);
473
+ }
474
+ main().catch((err) => {
475
+ console.error("Fatal:", err);
476
+ process.exit(1);
477
+ });
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@deepgram/styles",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "Tailwind-based design system and styles library for Deepgram design system and demos",
5
5
  "author": "Deepgram",
6
6
  "license": "ISC",
7
+ "type": "module",
7
8
  "repository": {
8
9
  "type": "git",
9
10
  "url": "https://github.com/deepgram/dx-design.git",
@@ -19,6 +20,9 @@
19
20
  "ui-components",
20
21
  "frontend"
21
22
  ],
23
+ "bin": {
24
+ "deepgram-styles-mcp": "dist/mcp/server.js"
25
+ },
22
26
  "main": "dist/deepgram.css",
23
27
  "style": "dist/deepgram.css",
24
28
  "files": [
@@ -96,14 +100,19 @@
96
100
  "types": "./dist/react/index.d.ts",
97
101
  "import": "./dist/react/index.js",
98
102
  "default": "./dist/react/index.js"
103
+ },
104
+ "./mcp": {
105
+ "types": "./dist/mcp/server.d.ts",
106
+ "import": "./dist/mcp/server.js",
107
+ "default": "./dist/mcp/server.js"
99
108
  }
100
109
  },
101
110
  "publishConfig": {
102
111
  "access": "public"
103
112
  },
104
113
  "peerDependencies": {
105
- "tailwindcss": "^4.0.0",
106
- "react": "^18.0.0 || ^19.0.0"
114
+ "react": "^18.0.0 || ^19.0.0",
115
+ "tailwindcss": "^4.0.0"
107
116
  },
108
117
  "peerDependenciesMeta": {
109
118
  "tailwindcss": {
@@ -112,5 +121,10 @@
112
121
  "react": {
113
122
  "optional": true
114
123
  }
124
+ },
125
+ "dependencies": {
126
+ "@modelcontextprotocol/sdk": "^1.26.0",
127
+ "yaml": "^2.8.1",
128
+ "zod": "^4.3.6"
115
129
  }
116
130
  }