@stevegreco/design-system 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -219,20 +219,20 @@
219
219
  --component-alert-gap: 12px;
220
220
  --component-alert-font-size: 14px;
221
221
  --component-alert-title-font-weight: 600;
222
- --component-alert-success-bg: #e3ecd9;
223
- --component-alert-success-fg: #4a6b3d;
222
+ --component-alert-success-background: #e3ecd9;
223
+ --component-alert-success-foreground: #4a6b3d;
224
224
  --component-alert-success-icon-color: #6b8e5a;
225
225
  --component-alert-success-border: #e3ecd9;
226
- --component-alert-danger-bg: #f5dbd0;
227
- --component-alert-danger-fg: #84301c;
226
+ --component-alert-danger-background: #f5dbd0;
227
+ --component-alert-danger-foreground: #84301c;
228
228
  --component-alert-danger-icon-color: #b8472a;
229
229
  --component-alert-danger-border: #f5dbd0;
230
- --component-alert-warning-bg: #f7edce;
231
- --component-alert-warning-fg: #823f1e;
230
+ --component-alert-warning-background: #f7edce;
231
+ --component-alert-warning-foreground: #823f1e;
232
232
  --component-alert-warning-icon-color: #c06d1f;
233
233
  --component-alert-warning-border: #f7edce;
234
- --component-alert-info-bg: #d6e1ea;
235
- --component-alert-info-fg: #2f5973;
234
+ --component-alert-info-background: #d6e1ea;
235
+ --component-alert-info-foreground: #2f5973;
236
236
  --component-alert-info-icon-color: #4a7896;
237
237
  --component-alert-info-border: #d6e1ea;
238
238
  --component-badge-radius: 9999px;
@@ -240,23 +240,23 @@
240
240
  --component-badge-padding-y: 1px;
241
241
  --component-badge-font-size: 12px;
242
242
  --component-badge-font-weight: 500;
243
- --component-badge-default-bg: #e0e7e3;
244
- --component-badge-default-fg: #3b4a43;
243
+ --component-badge-default-background: #e0e7e3;
244
+ --component-badge-default-foreground: #3b4a43;
245
245
  --component-badge-default-border: #c1cec7;
246
- --component-badge-accent-bg: #f7edce;
247
- --component-badge-accent-fg: #a0501d;
246
+ --component-badge-accent-background: #f7edce;
247
+ --component-badge-accent-foreground: #a0501d;
248
248
  --component-badge-accent-border: #efda98;
249
- --component-badge-success-bg: #e3ecd9;
250
- --component-badge-success-fg: #4a6b3d;
249
+ --component-badge-success-background: #e3ecd9;
250
+ --component-badge-success-foreground: #4a6b3d;
251
251
  --component-badge-success-border: #e3ecd9;
252
- --component-badge-danger-bg: #f5dbd0;
253
- --component-badge-danger-fg: #84301c;
252
+ --component-badge-danger-background: #f5dbd0;
253
+ --component-badge-danger-foreground: #84301c;
254
254
  --component-badge-danger-border: #f5dbd0;
255
- --component-badge-warning-bg: #f7edce;
256
- --component-badge-warning-fg: #823f1e;
255
+ --component-badge-warning-background: #f7edce;
256
+ --component-badge-warning-foreground: #823f1e;
257
257
  --component-badge-warning-border: #f7edce;
258
- --component-badge-info-bg: #d6e1ea;
259
- --component-badge-info-fg: #2f5973;
258
+ --component-badge-info-background: #d6e1ea;
259
+ --component-badge-info-foreground: #2f5973;
260
260
  --component-badge-info-border: #d6e1ea;
261
261
  --component-button-radius-md: 10px;
262
262
  --component-button-radius-full: 9999px;
@@ -268,45 +268,45 @@
268
268
  --component-button-font-weight: 500;
269
269
  --component-button-font-size: 14px;
270
270
  --component-button-transition: 140ms;
271
- --component-button-primary-bg: #a0501d;
272
- --component-button-primary-bg-hover: #823f1e;
273
- --component-button-primary-bg-press: #6b341c;
274
- --component-button-primary-fg: #faf6f0;
271
+ --component-button-primary-background: #a0501d;
272
+ --component-button-primary-background-hover: #823f1e;
273
+ --component-button-primary-background-press: #6b341c;
274
+ --component-button-primary-foreground: #faf6f0;
275
275
  --component-button-primary-border: #a0501d;
276
- --component-button-secondary-bg: rgba(0, 0, 0, 0);
277
- --component-button-secondary-bg-hover: #e0e7e3;
278
- --component-button-secondary-bg-press: #e0e7e3;
279
- --component-button-secondary-fg: #3b4a43;
276
+ --component-button-secondary-background: rgba(0, 0, 0, 0);
277
+ --component-button-secondary-background-hover: #e0e7e3;
278
+ --component-button-secondary-background-press: #e0e7e3;
279
+ --component-button-secondary-foreground: #3b4a43;
280
280
  --component-button-secondary-border: #c1cec7;
281
- --component-button-ghost-bg: rgba(0, 0, 0, 0);
282
- --component-button-ghost-bg-hover: #e0e7e3;
283
- --component-button-ghost-fg: #171c1a;
281
+ --component-button-ghost-background: rgba(0, 0, 0, 0);
282
+ --component-button-ghost-background-hover: #e0e7e3;
283
+ --component-button-ghost-foreground: #171c1a;
284
284
  --component-button-ghost-border: rgba(0, 0, 0, 0);
285
- --component-button-danger-bg: #b8472a;
286
- --component-button-danger-bg-hover: #84301c;
287
- --component-button-danger-fg: #ffffff;
285
+ --component-button-danger-background: #b8472a;
286
+ --component-button-danger-background-hover: #84301c;
287
+ --component-button-danger-foreground: #ffffff;
288
288
  --component-button-danger-border: #b8472a;
289
- --component-card-bg: #ffffff;
289
+ --component-card-background: #ffffff;
290
290
  --component-card-border: #c1cec7;
291
291
  --component-card-radius: 14px;
292
292
  --component-card-shadow: 0 1px 2px rgba(61, 26, 11, 0.05), 0 2px 4px rgba(61, 26, 11, 0.04);
293
293
  --component-card-shadow-hover: 0 2px 4px rgba(61, 26, 11, 0.05), 0 6px 14px rgba(61, 26, 11, 0.07);
294
294
  --component-card-padding: 24px;
295
295
  --component-card-gap: 16px;
296
- --component-card-warm-bg: #f7edce;
296
+ --component-card-warm-background: #f7edce;
297
297
  --component-card-warm-border: #efda98;
298
- --component-input-bg: #ffffff;
299
- --component-input-bg-disabled: #e0e7e3;
298
+ --component-input-background: #ffffff;
299
+ --component-input-background-disabled: #e0e7e3;
300
300
  --component-input-border: #c1cec7;
301
301
  --component-input-border-hover: #9aaea4;
302
302
  --component-input-border-focus: #a0501d;
303
303
  --component-input-border-error: #b8472a;
304
- --component-input-fg: #171c1a;
305
- --component-input-fg-placeholder: #7a9086;
306
- --component-input-fg-disabled: #5b7168;
307
- --component-input-label-fg: #3b4a43;
308
- --component-input-hint-fg: #5b7168;
309
- --component-input-error-fg: #84301c;
304
+ --component-input-foreground: #171c1a;
305
+ --component-input-foreground-placeholder: #7a9086;
306
+ --component-input-foreground-disabled: #5b7168;
307
+ --component-input-label-foreground: #3b4a43;
308
+ --component-input-hint-foreground: #5b7168;
309
+ --component-input-error-foreground: #84301c;
310
310
  --component-input-radius: 6px;
311
311
  --component-input-padding-x: 12px;
312
312
  --component-input-padding-y: 8px;
@@ -0,0 +1,144 @@
1
+ {
2
+ "component": {
3
+ "alert": {
4
+ "radius": "10px",
5
+ "padding": "16px",
6
+ "gap": "12px",
7
+ "fontSize": "14px",
8
+ "titleFontWeight": 600,
9
+ "success": {
10
+ "background": "#e3ecd9",
11
+ "foreground": "#4a6b3d",
12
+ "iconColor": "#6b8e5a",
13
+ "border": "#e3ecd9"
14
+ },
15
+ "danger": {
16
+ "background": "#f5dbd0",
17
+ "foreground": "#84301c",
18
+ "iconColor": "#b8472a",
19
+ "border": "#f5dbd0"
20
+ },
21
+ "warning": {
22
+ "background": "#f7edce",
23
+ "foreground": "#823f1e",
24
+ "iconColor": "#c06d1f",
25
+ "border": "#f7edce"
26
+ },
27
+ "info": {
28
+ "background": "#d6e1ea",
29
+ "foreground": "#2f5973",
30
+ "iconColor": "#4a7896",
31
+ "border": "#d6e1ea"
32
+ }
33
+ },
34
+ "badge": {
35
+ "radius": "9999px",
36
+ "paddingX": "8px",
37
+ "paddingY": "1px",
38
+ "fontSize": "12px",
39
+ "fontWeight": 500,
40
+ "default": {
41
+ "background": "#e0e7e3",
42
+ "foreground": "#3b4a43",
43
+ "border": "#c1cec7"
44
+ },
45
+ "accent": {
46
+ "background": "#f7edce",
47
+ "foreground": "#a0501d",
48
+ "border": "#efda98"
49
+ },
50
+ "success": {
51
+ "background": "#e3ecd9",
52
+ "foreground": "#4a6b3d",
53
+ "border": "#e3ecd9"
54
+ },
55
+ "danger": {
56
+ "background": "#f5dbd0",
57
+ "foreground": "#84301c",
58
+ "border": "#f5dbd0"
59
+ },
60
+ "warning": {
61
+ "background": "#f7edce",
62
+ "foreground": "#823f1e",
63
+ "border": "#f7edce"
64
+ },
65
+ "info": {
66
+ "background": "#d6e1ea",
67
+ "foreground": "#2f5973",
68
+ "border": "#d6e1ea"
69
+ }
70
+ },
71
+ "button": {
72
+ "radiusMd": "10px",
73
+ "radiusFull": "9999px",
74
+ "paddingX": "16px",
75
+ "paddingY": "12px",
76
+ "paddingXSm": "12px",
77
+ "paddingYSm": "8px",
78
+ "fontFamily": "'Source Sans 3', system-ui, -apple-system, sans-serif",
79
+ "fontWeight": 500,
80
+ "fontSize": "14px",
81
+ "transition": "140ms",
82
+ "primary": {
83
+ "background": "#a0501d",
84
+ "backgroundHover": "#823f1e",
85
+ "backgroundPress": "#6b341c",
86
+ "foreground": "#faf6f0",
87
+ "border": "#a0501d"
88
+ },
89
+ "secondary": {
90
+ "background": "#00000000",
91
+ "backgroundHover": "#e0e7e3",
92
+ "backgroundPress": "#e0e7e3",
93
+ "foreground": "#3b4a43",
94
+ "border": "#c1cec7"
95
+ },
96
+ "ghost": {
97
+ "background": "#00000000",
98
+ "backgroundHover": "#e0e7e3",
99
+ "foreground": "#171c1a",
100
+ "border": "#00000000"
101
+ },
102
+ "danger": {
103
+ "background": "#b8472a",
104
+ "backgroundHover": "#84301c",
105
+ "foreground": "#ffffff",
106
+ "border": "#b8472a"
107
+ }
108
+ },
109
+ "card": {
110
+ "background": "#ffffff",
111
+ "border": "#c1cec7",
112
+ "radius": "14px",
113
+ "shadow": "0 1px 2px rgba(61, 26, 11, 0.05), 0 2px 4px rgba(61, 26, 11, 0.04)",
114
+ "shadowHover": "0 2px 4px rgba(61, 26, 11, 0.05), 0 6px 14px rgba(61, 26, 11, 0.07)",
115
+ "padding": "24px",
116
+ "gap": "16px",
117
+ "warm": {
118
+ "background": "#f7edce",
119
+ "border": "#efda98"
120
+ }
121
+ },
122
+ "input": {
123
+ "background": "#ffffff",
124
+ "backgroundDisabled": "#e0e7e3",
125
+ "border": "#c1cec7",
126
+ "borderHover": "#9aaea4",
127
+ "borderFocus": "#a0501d",
128
+ "borderError": "#b8472a",
129
+ "foreground": "#171c1a",
130
+ "foregroundPlaceholder": "#7a9086",
131
+ "foregroundDisabled": "#5b7168",
132
+ "labelForeground": "#3b4a43",
133
+ "hintForeground": "#5b7168",
134
+ "errorForeground": "#84301c",
135
+ "radius": "6px",
136
+ "paddingX": "12px",
137
+ "paddingY": "8px",
138
+ "shadow": "inset 0 1px 2px rgba(61, 26, 11, 0.08)",
139
+ "shadowFocus": "0 1px 2px rgba(61, 26, 11, 0.06)",
140
+ "fontFamily": "'Source Sans 3', system-ui, -apple-system, sans-serif",
141
+ "fontSize": "14px"
142
+ }
143
+ }
144
+ }
@@ -100,7 +100,7 @@
100
100
  "caps": "0.08em"
101
101
  },
102
102
  "space": {
103
- "0": "0",
103
+ "0": 0,
104
104
  "1": "4px",
105
105
  "2": "8px",
106
106
  "3": "12px",
@@ -117,7 +117,7 @@
117
117
  "px": "1px"
118
118
  },
119
119
  "radius": {
120
- "none": "0",
120
+ "none": 0,
121
121
  "xs": "3px",
122
122
  "sm": "6px",
123
123
  "md": "10px",
@@ -0,0 +1,60 @@
1
+ {
2
+ "semantic": {
3
+ "color": {
4
+ "background": {
5
+ "default": "#171c1a",
6
+ "raised": "#2c3531",
7
+ "sunken": "#0e1211",
8
+ "muted": "#323d38"
9
+ },
10
+ "foreground": {
11
+ "default": "#faf6f0",
12
+ "muted": "#c1cec7",
13
+ "subtle": "#9aaea4",
14
+ "faint": "#5b7168",
15
+ "onAccent": "#171c1a",
16
+ "inverse": "#171c1a"
17
+ },
18
+ "border": {
19
+ "default": "#323d38",
20
+ "strong": "#3b4a43",
21
+ "subtle": "#2c3531",
22
+ "warm": "#823f1e"
23
+ },
24
+ "accent": {
25
+ "default": "#e1ac3e",
26
+ "hover": "#e8c468",
27
+ "press": "#d98f27",
28
+ "soft": "#3a2e1e",
29
+ "foreground": "#171c1a"
30
+ },
31
+ "secondary": {
32
+ "default": "#c1cec7",
33
+ "hover": "#faf6f0",
34
+ "soft": "#323d38"
35
+ },
36
+ "status": {
37
+ "success": {
38
+ "default": "#6b8e5a",
39
+ "foreground": "#e3ecd9",
40
+ "background": "#394739"
41
+ },
42
+ "danger": {
43
+ "default": "#b8472a",
44
+ "foreground": "#f5dbd0",
45
+ "background": "#483930"
46
+ },
47
+ "warning": {
48
+ "default": "#d98f27",
49
+ "foreground": "#f7edce",
50
+ "background": "#51432d"
51
+ },
52
+ "info": {
53
+ "default": "#4a7896",
54
+ "foreground": "#d6e1ea",
55
+ "background": "#34464a"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "semantic": {
3
+ "color": {
4
+ "background": {
5
+ "default": "#faf6f0",
6
+ "raised": "#ffffff",
7
+ "sunken": "#f2ebdf",
8
+ "muted": "#e0e7e3"
9
+ },
10
+ "foreground": {
11
+ "default": "#171c1a",
12
+ "muted": "#3b4a43",
13
+ "subtle": "#5b7168",
14
+ "faint": "#7a9086",
15
+ "onAccent": "#faf6f0",
16
+ "inverse": "#faf6f0"
17
+ },
18
+ "border": {
19
+ "default": "#c1cec7",
20
+ "strong": "#9aaea4",
21
+ "subtle": "#e6d8c3",
22
+ "warm": "#efda98"
23
+ },
24
+ "accent": {
25
+ "default": "#a0501d",
26
+ "hover": "#823f1e",
27
+ "press": "#6b341c",
28
+ "soft": "#f7edce",
29
+ "foreground": "#faf6f0"
30
+ },
31
+ "secondary": {
32
+ "default": "#3b4a43",
33
+ "hover": "#323d38",
34
+ "soft": "#e0e7e3"
35
+ },
36
+ "status": {
37
+ "success": {
38
+ "default": "#6b8e5a",
39
+ "foreground": "#4a6b3d",
40
+ "background": "#e3ecd9"
41
+ },
42
+ "danger": {
43
+ "default": "#b8472a",
44
+ "foreground": "#84301c",
45
+ "background": "#f5dbd0"
46
+ },
47
+ "warning": {
48
+ "default": "#c06d1f",
49
+ "foreground": "#823f1e",
50
+ "background": "#f7edce"
51
+ },
52
+ "info": {
53
+ "default": "#4a7896",
54
+ "foreground": "#2f5973",
55
+ "background": "#d6e1ea"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
package/mcp/index.js ADDED
@@ -0,0 +1,376 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { readFileSync, statSync, readdirSync } from "fs";
6
+ import { join, dirname } from "path";
7
+ import { fileURLToPath } from "url";
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const ROOT = join(__dirname, "..");
11
+
12
+ // Maps CSS properties to the token type and path hints used for scoring.
13
+ // hints are substrings matched against token paths (case-insensitive).
14
+ const CSS_PROPERTY_MAP = {
15
+ "background-color": { type: "color", hints: ["bg", "background"] },
16
+ "background": { type: "color", hints: ["bg", "background"] },
17
+ "color": { type: "color", hints: ["fg", "foreground"] },
18
+ "border-color": { type: "color", hints: ["border"] },
19
+ "outline-color": { type: "color", hints: ["border", "focus"] },
20
+ "fill": { type: "color", hints: ["fg", "foreground"] },
21
+ "stroke": { type: "color", hints: ["border", "fg"] },
22
+ "caret-color": { type: "color", hints: ["fg", "accent"] },
23
+ "border-radius": { type: "dimension", hints: ["radius"] },
24
+ "border-width": { type: "dimension", hints: ["border"] },
25
+ "padding": { type: "dimension", hints: ["padding"] },
26
+ "padding-top": { type: "dimension", hints: ["paddingY", "padding"] },
27
+ "padding-bottom": { type: "dimension", hints: ["paddingY", "padding"] },
28
+ "padding-left": { type: "dimension", hints: ["paddingX", "padding"] },
29
+ "padding-right": { type: "dimension", hints: ["paddingX", "padding"] },
30
+ "gap": { type: "dimension", hints: ["gap", "space"] },
31
+ "margin": { type: "dimension", hints: ["space"] },
32
+ "font-size": { type: "dimension", hints: ["fontSize", "text"] },
33
+ "letter-spacing": { type: "dimension", hints: ["tracking"] },
34
+ "line-height": { type: "number", hints: ["leading"] },
35
+ "font-family": { type: "fontFamily", hints: ["fontFamily", "font"] },
36
+ "font-weight": { type: "fontWeight", hints: ["fontWeight", "weight"] },
37
+ "box-shadow": { type: "shadow", hints: ["shadow"] },
38
+ "transition-duration": { type: "duration", hints: ["duration", "transition"] },
39
+ "animation-duration": { type: "duration", hints: ["duration"] },
40
+ "transition-timing-function":{ type: "cubicBezier", hints: ["ease"] },
41
+ "width": { type: "dimension", hints: ["layout", "space"] },
42
+ "max-width": { type: "dimension", hints: ["layout"] },
43
+ };
44
+
45
+ const TIER_BONUS = { component: 0.3, semantic: 0.2, primitive: 0.1 };
46
+
47
+ const DIST = {
48
+ primitives: join(ROOT, "dist/tokens/primitives.json"),
49
+ semanticLight: join(ROOT, "dist/tokens/semantic.light.json"),
50
+ semanticDark: join(ROOT, "dist/tokens/semantic.dark.json"),
51
+ components: join(ROOT, "dist/tokens/components.json"),
52
+ };
53
+
54
+ function sourceFiles() {
55
+ const base = [
56
+ join(ROOT, "tokens/primitives.tokens.json"),
57
+ join(ROOT, "tokens/semantic/light.tokens.json"),
58
+ join(ROOT, "tokens/semantic/dark.tokens.json"),
59
+ ];
60
+ try {
61
+ const components = readdirSync(join(ROOT, "tokens/components"))
62
+ .filter((f) => f.endsWith(".tokens.json"))
63
+ .map((f) => join(ROOT, "tokens/components", f));
64
+ return [...base, ...components];
65
+ } catch {
66
+ return base;
67
+ }
68
+ }
69
+
70
+ function isStale() {
71
+ try {
72
+ const srcMax = Math.max(
73
+ ...sourceFiles().map((f) => {
74
+ try { return statSync(f).mtimeMs; } catch { return 0; }
75
+ })
76
+ );
77
+ const distMin = Math.min(
78
+ ...Object.values(DIST).map((f) => {
79
+ try { return statSync(f).mtimeMs; } catch { return 0; }
80
+ })
81
+ );
82
+ return srcMax > distMin;
83
+ } catch {
84
+ return false;
85
+ }
86
+ }
87
+
88
+ function inferType(value) {
89
+ if (typeof value === "number") return "fontWeight";
90
+ const v = String(value);
91
+ if (/^#[0-9a-f]{3,8}$/i.test(v) || /^(rgb|hsl)a?\(/.test(v)) return "color";
92
+ if (/^-?\d+(\.\d+)?(px|rem|em|%)$/.test(v)) return "dimension";
93
+ if (/^\d+ms$/.test(v)) return "duration";
94
+ if (/^cubic-bezier/.test(v)) return "cubicBezier";
95
+ // shadows: multi-part strings containing px and spaces, or inset keyword
96
+ if (/^inset /.test(v) || (v.includes(" ") && v.includes("px"))) return "shadow";
97
+ // font families: quoted strings or comma-separated lists (not function calls)
98
+ if (v.includes("'") || v.includes('"') || (v.includes(",") && !v.includes("("))) return "fontFamily";
99
+ if (/^-?\d+(\.\d+)?$/.test(v)) return "number";
100
+ return "string";
101
+ }
102
+
103
+ function flatten(obj, parts = []) {
104
+ const tokens = [];
105
+ for (const [key, val] of Object.entries(obj)) {
106
+ const path = [...parts, key];
107
+ if (val !== null && typeof val === "object" && !Array.isArray(val)) {
108
+ tokens.push(...flatten(val, path));
109
+ } else {
110
+ const root = path[0];
111
+ tokens.push({
112
+ path: path.join("."),
113
+ cssVariable: "--" + path.join("-"),
114
+ value: String(val),
115
+ type: inferType(val),
116
+ tier:
117
+ root === "primitives" ? "primitive"
118
+ : root === "semantic" ? "semantic"
119
+ : "component",
120
+ });
121
+ }
122
+ }
123
+ return tokens;
124
+ }
125
+
126
+ function loadTokens(theme = "light") {
127
+ const semanticFile = theme === "dark" ? DIST.semanticDark : DIST.semanticLight;
128
+ const tokens = [];
129
+ for (const file of [DIST.primitives, semanticFile, DIST.components]) {
130
+ try {
131
+ tokens.push(...flatten(JSON.parse(readFileSync(file, "utf-8"))));
132
+ } catch {
133
+ // dist not yet built
134
+ }
135
+ }
136
+ return tokens;
137
+ }
138
+
139
+ const server = new McpServer({
140
+ name: "design-system-tokens",
141
+ version: "1.0.0",
142
+ });
143
+
144
+ server.registerTool(
145
+ "search_tokens",
146
+ {
147
+ description:
148
+ "Search design tokens by keyword. Matches against token paths and resolved values. " +
149
+ "Returns path, CSS variable name, resolved value, type, and tier for each result.",
150
+ inputSchema: z.object({
151
+ query: z.string().describe(
152
+ "Substring to search for in token paths and resolved values (e.g. 'danger', 'border', '#b8472a')"
153
+ ),
154
+ tier: z
155
+ .enum(["primitive", "semantic", "component"])
156
+ .optional()
157
+ .describe("Restrict results to a single tier"),
158
+ type: z
159
+ .string()
160
+ .optional()
161
+ .describe("Restrict results to a token type (e.g. color, dimension, fontFamily, duration)"),
162
+ theme: z
163
+ .enum(["light", "dark"])
164
+ .optional()
165
+ .describe("Theme variant for semantic tokens. Defaults to light."),
166
+ }),
167
+ },
168
+ async ({ query, tier, type, theme = "light" }) => {
169
+ const tokens = loadTokens(theme);
170
+ const q = query.toLowerCase();
171
+
172
+ let results = tokens.filter(
173
+ (t) => t.path.toLowerCase().includes(q) || t.value.toLowerCase().includes(q)
174
+ );
175
+ if (tier) results = results.filter((t) => t.tier === tier);
176
+ if (type) results = results.filter((t) => t.type === type);
177
+
178
+ const stale = isStale() ? "\n\n⚠ Token files may be out of date — run pnpm build" : "";
179
+
180
+ if (results.length === 0) {
181
+ return {
182
+ content: [{ type: "text", text: `No tokens found matching "${query}".${stale}` }],
183
+ };
184
+ }
185
+ return {
186
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) + stale }],
187
+ };
188
+ }
189
+ );
190
+
191
+ server.registerTool(
192
+ "get_token",
193
+ {
194
+ description:
195
+ "Get a single design token by its exact dot-separated path. " +
196
+ "Returns path, CSS variable name, resolved value, type, and tier.",
197
+ inputSchema: z.object({
198
+ path: z
199
+ .string()
200
+ .describe("Exact token path (e.g. component.button.primary.bg)"),
201
+ theme: z
202
+ .enum(["light", "dark"])
203
+ .optional()
204
+ .describe("Theme variant for semantic tokens. Defaults to light."),
205
+ }),
206
+ },
207
+ async ({ path, theme = "light" }) => {
208
+ const tokens = loadTokens(theme);
209
+ const token = tokens.find((t) => t.path === path);
210
+ const stale = isStale() ? "\n\n⚠ Token files may be out of date — run pnpm build" : "";
211
+
212
+ if (!token) {
213
+ return {
214
+ content: [{ type: "text", text: `Token not found: "${path}".${stale}` }],
215
+ isError: true,
216
+ };
217
+ }
218
+ return {
219
+ content: [{ type: "text", text: JSON.stringify(token, null, 2) + stale }],
220
+ };
221
+ }
222
+ );
223
+
224
+ server.registerTool(
225
+ "suggest_token",
226
+ {
227
+ description:
228
+ "Suggest the best design token for a given CSS property and usage context. " +
229
+ "Returns up to 5 ranked suggestions, preferring component tokens over semantic and semantic over primitive.",
230
+ inputSchema: z.object({
231
+ property: z.string().describe(
232
+ "CSS property name (e.g. 'background-color', 'border-radius', 'font-size', 'box-shadow')"
233
+ ),
234
+ context: z.string().optional().describe(
235
+ "Plain-English usage context to narrow results (e.g. 'danger button', 'input focus state', 'card background')"
236
+ ),
237
+ theme: z.enum(["light", "dark"]).optional().describe(
238
+ "Theme variant for semantic tokens. Defaults to light."
239
+ ),
240
+ }),
241
+ },
242
+ async ({ property, context = "", theme = "light" }) => {
243
+ const tokens = loadTokens(theme);
244
+ const stale = isStale() ? "\n\n⚠ Token files may be out of date — run pnpm build" : "";
245
+ const hint = CSS_PROPERTY_MAP[property.toLowerCase()];
246
+ const contextKeywords = context.toLowerCase().split(/\W+/).filter(Boolean);
247
+
248
+ const scored = tokens
249
+ .map((token) => {
250
+ const pathLower = token.path.toLowerCase();
251
+ let score = 0;
252
+
253
+ // Hard filter by token type when the CSS property has a known mapping
254
+ if (hint?.type && token.type !== hint.type) return null;
255
+
256
+ // Score by how well the path matches the CSS property's expected path hints
257
+ for (const h of hint?.hints ?? []) {
258
+ if (pathLower.includes(h.toLowerCase())) score += 2;
259
+ }
260
+
261
+ // Score by context keyword overlap
262
+ for (const kw of contextKeywords) {
263
+ if (pathLower.includes(kw)) score += 1;
264
+ }
265
+
266
+ // If unknown CSS property, fall back to matching the property name itself
267
+ if (!hint) {
268
+ const propKeywords = property.toLowerCase().split(/[-\s]+/);
269
+ for (const kw of propKeywords) {
270
+ if (pathLower.includes(kw)) score += 1;
271
+ }
272
+ }
273
+
274
+ if (score === 0) return null;
275
+
276
+ return { score: score + TIER_BONUS[token.tier], token };
277
+ })
278
+ .filter(Boolean)
279
+ .sort((a, b) => b.score - a.score)
280
+ .slice(0, 5)
281
+ .map(({ token }) => token);
282
+
283
+ if (scored.length === 0) {
284
+ const msg = hint
285
+ ? `No tokens found for "${property}"${context ? ` in context "${context}"` : ""}.`
286
+ : `Unknown CSS property "${property}". Try a standard property like "background-color", "border-radius", or "font-size".`;
287
+ return { content: [{ type: "text", text: msg + stale }] };
288
+ }
289
+
290
+ return {
291
+ content: [{ type: "text", text: JSON.stringify(scored, null, 2) + stale }],
292
+ };
293
+ }
294
+ );
295
+
296
+ // --- Resources ---
297
+
298
+ server.registerResource(
299
+ "primitives",
300
+ "tokens://primitives",
301
+ {
302
+ title: "Primitive Tokens",
303
+ description: "Raw scale-based values: colors, spacing, typography, radius, shadow, motion.",
304
+ mimeType: "application/json",
305
+ },
306
+ async (uri) => ({
307
+ contents: [{ uri: uri.href, text: readFileSync(DIST.primitives, "utf-8") }],
308
+ })
309
+ );
310
+
311
+ server.registerResource(
312
+ "components",
313
+ "tokens://components",
314
+ {
315
+ title: "Component Tokens",
316
+ description: "Component-scoped tokens for button, input, card, badge, and alert.",
317
+ mimeType: "application/json",
318
+ },
319
+ async (uri) => ({
320
+ contents: [{ uri: uri.href, text: readFileSync(DIST.components, "utf-8") }],
321
+ })
322
+ );
323
+
324
+ server.registerResource(
325
+ "semantic",
326
+ new ResourceTemplate("tokens://semantic/{theme}", {
327
+ list: async () => ({
328
+ resources: [
329
+ { uri: "tokens://semantic/light", name: "Semantic Tokens — Light" },
330
+ { uri: "tokens://semantic/dark", name: "Semantic Tokens — Dark" },
331
+ ],
332
+ }),
333
+ }),
334
+ {
335
+ title: "Semantic Tokens",
336
+ description: "Intent-named tokens for background, foreground, border, accent, and status. Pass 'light' or 'dark'.",
337
+ mimeType: "application/json",
338
+ },
339
+ async (uri, { theme }) => {
340
+ const file = theme === "dark" ? DIST.semanticDark : DIST.semanticLight;
341
+ return { contents: [{ uri: uri.href, text: readFileSync(file, "utf-8") }] };
342
+ }
343
+ );
344
+
345
+ // --- Prompts ---
346
+
347
+ server.registerPrompt(
348
+ "find-token",
349
+ {
350
+ title: "Find a Token",
351
+ description: "Describe what you need in plain English and get a suggestion for the right design token to use.",
352
+ argsSchema: {
353
+ description: z.string().describe(
354
+ "Plain-English description of what you need (e.g. 'a background color for a danger alert')"
355
+ ),
356
+ },
357
+ },
358
+ ({ description }) => ({
359
+ messages: [
360
+ {
361
+ role: "user",
362
+ content: {
363
+ type: "text",
364
+ text:
365
+ `I need a design token for: "${description}"\n\n` +
366
+ `Use the search_tokens tool to find the best match. ` +
367
+ `Prefer semantic or component tokens over primitives. ` +
368
+ `Reply with the token path, its CSS variable name, and the resolved value.`,
369
+ },
370
+ },
371
+ ],
372
+ })
373
+ );
374
+
375
+ const transport = new StdioServerTransport();
376
+ await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stevegreco/design-system",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Design tokens distributed as CSS and JSON artifacts.",
5
5
  "type": "module",
6
6
  "main": "./dist/tokens/primitives.json",
@@ -8,10 +8,17 @@
8
8
  "exports": {
9
9
  ".": "./dist/tokens/primitives.json",
10
10
  "./css": "./dist/css/tokens.css",
11
- "./tokens": "./dist/tokens/primitives.json"
11
+ "./tokens/primitives": "./dist/tokens/primitives.json",
12
+ "./tokens/semantic-light": "./dist/tokens/semantic.light.json",
13
+ "./tokens/semantic-dark": "./dist/tokens/semantic.dark.json",
14
+ "./tokens/components": "./dist/tokens/components.json"
15
+ },
16
+ "bin": {
17
+ "design-system-mcp": "mcp/index.js"
12
18
  },
13
19
  "files": [
14
- "dist"
20
+ "dist",
21
+ "mcp"
15
22
  ],
16
23
  "keywords": [
17
24
  "design-system",
@@ -26,11 +33,16 @@
26
33
  "devDependencies": {
27
34
  "style-dictionary": "latest"
28
35
  },
36
+ "dependencies": {
37
+ "@modelcontextprotocol/sdk": "^1.29.0",
38
+ "zod": "^4.4.3"
39
+ },
29
40
  "scripts": {
30
41
  "clean": "rm -rf dist",
31
42
  "build": "pnpm run clean && node build-tokens.js && node build-css.js",
32
43
  "showcase": "node generate-showcase.js",
33
44
  "dashboard": "open examples/dashboard.html",
34
- "food": "open examples/food-order.html"
45
+ "food": "open examples/food-order.html",
46
+ "mcp": "node mcp/index.js"
35
47
  }
36
48
  }
package/README.md DELETED
@@ -1,64 +0,0 @@
1
- # design-system
2
-
3
- Design token package with DTCG-style source tokens and generated distribution artifacts.
4
-
5
- ## Source of truth
6
-
7
- - DTCG-style tokens: `tokens/primitives.tokens.json`
8
- - Semantic token examples:
9
- - `tokens/semantic/light.tokens.json`
10
- - `tokens/semantic/dark.tokens.json`
11
-
12
- ## Build outputs
13
-
14
- - CSS variables (single file): `dist/css/tokens.css`
15
- - JSON tokens: `dist/tokens/primitives.json`
16
- - The CSS file contains:
17
- - primitives in `:root` with `--primitives-*` names
18
- - semantic light tokens in `:root`
19
- - semantic dark overrides in `:root[data-theme="dark"]`
20
-
21
- ## Install
22
-
23
- ```bash
24
- pnpm install
25
- ```
26
-
27
- ## Build
28
-
29
- ```bash
30
- pnpm run build
31
- ```
32
-
33
- ## Consume in CSS projects
34
-
35
- ```css
36
- @import "design-system/css";
37
- ```
38
-
39
- Or via JS:
40
-
41
- ```js
42
- import "design-system/css";
43
- ```
44
-
45
- Semantic layers:
46
- The single CSS entry already includes both light and dark semantic layers.
47
- The sample semantic token is `--semantic-color-background-brand`.
48
-
49
- ## Consume JSON tokens
50
-
51
- ```js
52
- import tokens from "design-system/tokens";
53
- ```
54
-
55
- ## Publish checklist
56
-
57
- 1. Run `pnpm run build`
58
- 2. Bump version in `package.json`
59
- 3. Publish with `pnpm publish`
60
-
61
- ## Notes
62
-
63
- - The package uses Style Dictionary to generate platform outputs.
64
- - Keep editing tokens in `tokens/primitives.tokens.json` rather than editing `dist/*` files directly.