@primer/mcp 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { s as server } from './server-Cwz0naYT.js';
1
+ export { s as server } from './server-owUZMSeG.js';
2
2
  import '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import 'cheerio';
4
4
  import 'zod';
@@ -7,6 +7,7 @@ import '@primer/react/generated/components.json' with { type: 'json' };
7
7
  import '@primer/octicons/build/data.json' with { type: 'json' };
8
8
  import 'node:fs';
9
9
  import 'node:module';
10
+ import 'child_process';
10
11
  import '@primer/primitives/dist/docs/base/motion/motion.json' with { type: 'json' };
11
12
  import '@primer/primitives/dist/docs/base/size/size.json' with { type: 'json' };
12
13
  import '@primer/primitives/dist/docs/base/typography/typography.json' with { type: 'json' };
@@ -3,7 +3,43 @@ declare const categories: {
3
3
  readonly motion: {
4
4
  name: string;
5
5
  type: string;
6
- value: string | number[];
6
+ value: number[] | {
7
+ value: number;
8
+ unit: string;
9
+ } | {
10
+ value: number;
11
+ unit: string;
12
+ } | {
13
+ value: number;
14
+ unit: string;
15
+ } | {
16
+ value: number;
17
+ unit: string;
18
+ } | {
19
+ value: number;
20
+ unit: string;
21
+ } | {
22
+ value: number;
23
+ unit: string;
24
+ } | {
25
+ value: number;
26
+ unit: string;
27
+ } | {
28
+ value: number;
29
+ unit: string;
30
+ } | {
31
+ value: number;
32
+ unit: string;
33
+ } | {
34
+ value: number;
35
+ unit: string;
36
+ } | {
37
+ value: number;
38
+ unit: string;
39
+ } | {
40
+ value: number;
41
+ unit: string;
42
+ };
7
43
  }[];
8
44
  readonly size: {
9
45
  name: string;
@@ -13,7 +49,7 @@ declare const categories: {
13
49
  readonly typography: {
14
50
  name: string;
15
51
  type: string;
16
- value: number;
52
+ value: string | number;
17
53
  }[];
18
54
  };
19
55
  readonly functional: {
@@ -59,7 +95,43 @@ declare const categories: {
59
95
  declare const tokens: ({
60
96
  name: string;
61
97
  type: string;
62
- value: string | number[];
98
+ value: number[] | {
99
+ value: number;
100
+ unit: string;
101
+ } | {
102
+ value: number;
103
+ unit: string;
104
+ } | {
105
+ value: number;
106
+ unit: string;
107
+ } | {
108
+ value: number;
109
+ unit: string;
110
+ } | {
111
+ value: number;
112
+ unit: string;
113
+ } | {
114
+ value: number;
115
+ unit: string;
116
+ } | {
117
+ value: number;
118
+ unit: string;
119
+ } | {
120
+ value: number;
121
+ unit: string;
122
+ } | {
123
+ value: number;
124
+ unit: string;
125
+ } | {
126
+ value: number;
127
+ unit: string;
128
+ } | {
129
+ value: number;
130
+ unit: string;
131
+ } | {
132
+ value: number;
133
+ unit: string;
134
+ };
63
135
  } | {
64
136
  name: string;
65
137
  type: string;
@@ -104,5 +176,9 @@ declare function formatBundle(bundleTokens: TokenWithGuidelines[]): string;
104
176
  */
105
177
  declare function getValidGroupsList(validTokens: TokenWithGuidelines[]): string;
106
178
  declare const groupHints: Record<string, string>;
107
- export { parseDesignTokensSpec, findTokens, buildAllTokens, expandTokenPattern, loadAllTokensWithGuidelines, loadDesignTokensGuide, loadDesignTokensSpec, getDesignTokenSpecsText, getTokenUsagePatternsText, getValidGroupsList, searchTokens, formatBundle, groupHints, GROUP_ALIASES, GROUP_LABELS, tokenMatchesGroup, type TokenWithGuidelines, };
179
+ declare function runStylelint(css: string): Promise<{
180
+ stdout: string;
181
+ stderr: string;
182
+ }>;
183
+ export { parseDesignTokensSpec, findTokens, buildAllTokens, expandTokenPattern, loadAllTokensWithGuidelines, loadDesignTokensGuide, loadDesignTokensSpec, getDesignTokenSpecsText, getTokenUsagePatternsText, getValidGroupsList, searchTokens, formatBundle, groupHints, GROUP_ALIASES, GROUP_LABELS, tokenMatchesGroup, runStylelint, type TokenWithGuidelines, };
108
184
  //# sourceMappingURL=primitives.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../src/primitives.ts"],"names":[],"mappings":"AAsBA,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6EN,CAAA;AAEV,QAAA,MAAM,MAAM;;;;;;;;IAWX,CAAA;AAED,iBAAS,SAAS,CAAC,KAAK,EAAE,OAAO,MAAM,GAAG,MAAM,CAM/C;AAKD,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CACrB,CAAA;AAED,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,UAAU,EAAE,CAAA;IACtB,SAAS,EAAE,UAAU,EAAE,CAAA;CACxB,CAAA;AAED,iBAAS,eAAe,IAAI,WAAW,CAoDtC;AAED,OAAO,EAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,KAAK,WAAW,EAAC,CAAA;AAGzE,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAGD,iBAAS,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAiFtE;AASD,QAAA,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAmBxC,CAAA;AAQD,iBAAS,cAAc,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,GAAG,mBAAmB,EAAE,CAuCtF;AAGD,iBAAS,UAAU,CAAC,SAAS,EAAE,mBAAmB,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAc1G;AAGD,iBAAS,kBAAkB,CAAC,KAAK,EAAE,mBAAmB,GAAG,mBAAmB,EAAE,CAW7E;AAGD,iBAAS,2BAA2B,IAAI,mBAAmB,EAAE,CAS5D;AAGD,iBAAS,qBAAqB,IAAI,MAAM,CAIvC;AAGD,iBAAS,oBAAoB,IAAI,MAAM,CAItC;AAGD,iBAAS,uBAAuB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAmD5D;AAGD,iBAAS,yBAAyB,IAAI,MAAM,CA6F3C;AAID,iBAAS,YAAY,CAAC,SAAS,EAAE,mBAAmB,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAsB5G;AAGD,QAAA,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAqDzC,CAAA;AAGD,iBAAS,iBAAiB,CAAC,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAKrF;AAGD,iBAAS,YAAY,CAAC,YAAY,EAAE,mBAAmB,EAAE,GAAG,MAAM,CAoBjE;AAED;;;GAGG;AACH,iBAAS,kBAAkB,CAAC,WAAW,EAAE,mBAAmB,EAAE,GAAG,MAAM,CAatE;AAGD,QAAA,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAOtC,CAAA;AAED,OAAO,EACL,qBAAqB,EACrB,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,2BAA2B,EAC3B,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,yBAAyB,EACzB,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,KAAK,mBAAmB,GACzB,CAAA"}
1
+ {"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../src/primitives.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,7 @@ import componentsMetadata from '@primer/react/generated/components.json' with {
6
6
  import octicons from '@primer/octicons/build/data.json' with { type: 'json' };
7
7
  import { readFileSync } from 'node:fs';
8
8
  import { createRequire } from 'node:module';
9
+ import { spawn } from 'child_process';
9
10
  import baseMotion from '@primer/primitives/dist/docs/base/motion/motion.json' with { type: 'json' };
10
11
  import baseSize from '@primer/primitives/dist/docs/base/size/size.json' with { type: 'json' };
11
12
  import baseTypography from '@primer/primitives/dist/docs/base/typography/typography.json' with { type: 'json' };
@@ -174,7 +175,7 @@ const categories = {
174
175
  const tokens = [...categories.base.motion, ...categories.base.size, ...categories.base.typography, ...categories.functional.border, ...categories.functional.radius, ...categories.functional.sizeCoarse, ...categories.functional.sizeFine, ...categories.functional.size, ...categories.functional.themes.light, ...categories.functional.typography];
175
176
 
176
177
  // Semantic group prefixes that apply to any element
177
- const SEMANTIC_PREFIXES = ['bgColor', 'fgColor', 'border', 'borderColor', 'shadow', 'focus', 'color'];
178
+ const SEMANTIC_PREFIXES = ['bgColor', 'fgColor', 'border', 'borderColor', 'shadow', 'focus', 'color', 'animation', 'duration'];
178
179
  function listTokenGroups() {
179
180
  // Use the full token set so non-theme groups (stack, text, borderRadius, etc.) are included
180
181
  const allTokens = tokens;
@@ -414,51 +415,42 @@ function getDesignTokenSpecsText(groups) {
414
415
  # Design Token Specifications
415
416
 
416
417
  ## 1. Core Rule & Enforcement
417
- - **Expert Mode**: You are a CSS expert. NEVER use raw values (hex, px, etc.). Only use tokens.
418
- - **Shorthand**: MUST use shorthand tokens (e.g., \`font: var(...)\`). NEVER split font-size/weight.
419
- - **States**: MUST define 5 states: Rest, Hover, Focus-visible, Active, Disabled.
420
- - **Safety**: If unsure of a token name, suffix with \`/* check-token */\`.
421
- - **Focus States**: When implementing :focus-visible, you MUST use both:
422
- - outline: var(--focus-outline)
423
- - outline-offset: var(--outline-focus-offset)
418
+ * **Expert Mode**: CSS expert. NEVER use raw values (hex, px, etc.). Tokens only.
419
+ * **Motion & Transitions:** Every interactive state change (Hover, Active) MUST include a transition. NEVER use raw values like 200ms or ease-in. Use var(--base-duration-...) and var(--base-easing-...).
420
+ * **Shorthand**: MUST use \`font: var(...)\`. NEVER split size/weight.
421
+ * **Shorthand Fallback**: If no shorthand exists (e.g. Monospace), use individual tokens for font-size, family, and line-height. NEVER raw 1.5.
422
+ * **States**: Define 5: Rest, Hover, Focus-visible, Active, Disabled.
423
+ * **Focus**: \`:focus-visible\` MUST use \`outline: var(--focus-outline)\` AND \`outline-offset: var(--outline-focus-offset)\`.
424
+ * **Validation**: CALL \`lint_css\` after any CSS change. Task is incomplete without a success message.
425
+ * **Self-Correction**: Adopt autofixes immediately. Report unfixable errors to the user.
424
426
 
425
427
  ## 2. Typography Constraints (STRICT)
426
- - **Body Only**: Only the \`body\` group supports size suffixes (e.g., \`body-small\`, \`body-medium\`).
427
- - **Static Shorthands**: The following groups do NOT support size suffixes. Use the base shorthand only:
428
- - **caption**: \`var(--text-caption-shorthand)\` (NEVER add -medium or -small)
429
- - **display**: \`var(--text-display-shorthand)\`
430
- - **codeBlock**: \`var(--text-codeBlock-shorthand)\`
431
- - **codeInline**: \`var(--text-codeInline-shorthand)\`
432
-
433
- ## 3. Logic Matrix: Color Pairings (CRITICAL)
434
- | Background Token | Foreground Token | Requirement |
435
- | :--- | :--- | :--- |
436
- | --bgColor-*-emphasis | --fgColor-onEmphasis | MUST pair |
437
- | --bgColor-*-muted | --fgColor-{semantic} | MUST match semantic |
438
- | --bgColor-default | --fgColor-default | Standard pairing |
439
- | --bgColor-muted | --fgColor-default | NEVER use fgColor-muted |
440
-
441
- ## 4. Semantic Intent Key
442
- Use these for \`find_tokens\` or to map natural language to groups:
443
- - **Search Aliases**: Map "background" -> \`bgColor\`, "foreground" -> \`fgColor\`, "typography/font" -> \`text\`, "padding/margin" -> \`stack\`, "radius" -> \`borderRadius\`, "shadow/elevation" -> \`overlay\`.
444
- - **danger**: Errors/Destructive | **success**: Positive/Done
445
- - **attention**: Warning/Pending | **accent**: Interactive/Selected
446
-
447
- ## 5. Available Token Groups
448
- Use these names in \`find_tokens(group: "...")\`:
449
- - **Semantic**: ${groups.semantic.map(g => g.name).join(', ')}
450
- - **Components**: ${groups.component.map(g => g.name).join(', ')}
451
-
452
- ## 6. Optimization Strategy (MANDATORY)
453
- - **STOP**: Do not call \`find_tokens\` repeatedly for individual properties.
454
- - **GO**: Use \`get_token_group_bundle\` to fetch relevant groups at once.
455
- - *Example for Button*: \`get_token_group_bundle(groups: ["control", "button"])\`
456
- - *Note*: \`control\` is for form inputs; \`button\` is for triggers.
457
-
458
- ## 7. Token Bundle Recipes (Recommended)
459
- - **Forms/Inputs**: \`["control", "focus", "outline", "text", "borderRadius"]\`
460
- - **Modals/Dialogs**: \`["overlay", "shadow", "outline", "borderRadius", "bgColor"]\`
461
- - **Data Tables**: \`["stack", "borderColor", "text", "bgColor"]\`
428
+ - **Body Only**: Only \`body\` group supports size suffixes (e.g., \`body-small\`).
429
+ - **Static Shorthands**: NEVER add suffixes to \`caption\`, \`display\`, \`codeBlock\`, or \`codeInline\`.
430
+
431
+ ## 3. Logic Matrix: Color & Semantic Mapping
432
+ | Input Color/Intent | Semantic Role | Background Suffix | Foreground Requirement |
433
+ | :--- | :--- | :--- | :--- |
434
+ | Blue / Interactive | \`accent\` | \`-emphasis\` (Solid) | \`fgColor-onEmphasis\` |
435
+ | Green / Positive | \`success\` | \`-muted\` (Light) | \`fgColor-{semantic}\` |
436
+ | Red / Danger | \`danger\` | \`-emphasis\` | \`fgColor-onEmphasis\` |
437
+ | Yellow / Warning | \`attention\` | \`-muted\` | \`fgColor-attention\` |
438
+ | Orange / Critical | \`severe\` | \`-emphasis\` | \`fgColor-onEmphasis\` |
439
+ | Purple / Done | \`done\` | Any | Match intent |
440
+ | Pink / Sponsors | \`sponsors\` | Any | Match intent |
441
+ | Grey / Neutral | \`default\` | \`bgColor-muted\` | \`fgColor-default\` (Not muted) |
442
+
443
+ ## 4. Optimization & Recipes (MANDATORY)
444
+ **Strategy**: STOP property-by-property searching. Use \`get_token_group_bundle\` for these common patterns:
445
+ - **Forms**: \`["control", "focus", "outline", "text", "borderRadius", "stack", "animation"]\`
446
+ - **Modals/Cards**: \`["overlay", "shadow", "outline", "borderRadius", "bgColor", "stack", "animation"]\`
447
+ - **Tables/Lists**: \`["stack", "borderColor", "text", "bgColor", "control"]\`
448
+ - **Nav/Sidebars**: \`["control", "text", "accent", "stack", "focus", "animation"]\`
449
+ - **Status/Badges**: \`["text", "success", "danger", "attention", "severe", "stack"]\`
450
+
451
+ ## 5. Available Groups
452
+ - **Semantic**: ${groups.semantic.map(g => `${g.name}\``).join(', ')}
453
+ - **Components**: ${groups.component.map(g => `\`${g.name}\``).join(', ')}
462
454
  `.trim();
463
455
  }
464
456
 
@@ -612,6 +604,9 @@ const GROUP_ALIASES = {
612
604
  typography: 'text',
613
605
  font: 'text',
614
606
  text: 'text',
607
+ 'line-height': 'text',
608
+ lineheight: 'text',
609
+ leading: 'text',
615
610
  // Layout & Spacing
616
611
  stack: 'stack',
617
612
  controlstack: 'controlStack',
@@ -628,7 +623,26 @@ const GROUP_ALIASES = {
628
623
  borderwidth: 'borderWidth',
629
624
  line: 'borderColor',
630
625
  stroke: 'borderColor',
631
- separator: 'borderColor'
626
+ separator: 'borderColor',
627
+ // Color-to-Semantic Intent Mapping
628
+ red: 'danger',
629
+ green: 'success',
630
+ yellow: 'attention',
631
+ orange: 'severe',
632
+ blue: 'accent',
633
+ purple: 'done',
634
+ pink: 'sponsors',
635
+ grey: 'neutral',
636
+ gray: 'neutral',
637
+ // Descriptive Aliases
638
+ light: 'muted',
639
+ subtle: 'muted',
640
+ dark: 'emphasis',
641
+ strong: 'emphasis',
642
+ intense: 'emphasis',
643
+ bold: 'emphasis',
644
+ vivid: 'emphasis',
645
+ highlight: 'emphasis'
632
646
  };
633
647
 
634
648
  // Match a token against a resolved group by checking both the token name prefix and the group label
@@ -680,12 +694,49 @@ function getValidGroupsList(validTokens) {
680
694
  const groupHints = {
681
695
  control: '`control` tokens are for form inputs/checkboxes. For buttons, use the `button` group.',
682
696
  button: '`button` tokens are for standard triggers. For form-fields, see the `control` group.',
683
- text: 'STRICT: The following typography groups do NOT support size suffixes (-small, -medium, -large): `caption`, `display`, `codeBlock`, and `codeInline`. Use the base shorthand name only (e.g., --text-codeBlock-shorthand).',
697
+ text: 'STRICT: The following typography groups do NOT support size suffixes (-small, -medium, -large): `caption`, `display`, `codeBlock`, and `codeInline`. STRICT: Use shorthand tokens where possible. If splitting, you MUST fetch line-height tokens (e.g., --text-body-lineHeight-small) instead of using raw numbers.',
684
698
  fgColor: 'Use `fgColor` for text. For borders, use `borderColor`.',
685
- borderWidth: '`borderWidth` only has sizing values (thin, thick, thicker). For border *colors*, use the `borderColor` or `border` group.'
699
+ borderWidth: '`borderWidth` only has sizing values (thin, thick, thicker). For border *colors*, use the `borderColor` or `border` group.',
700
+ animation: 'TRANSITION RULE: Apply duration and easing to the base class, not the :hover state. Standard pairing: `transition: background-color var(--base-duration-200) var(--base-easing-easeInOut);`'
686
701
  };
687
702
 
688
- var version = "0.3.0";
703
+ // -----------------------------------------------------------------------------
704
+ // Stylelint runner
705
+ // -----------------------------------------------------------------------------
706
+ function runStylelint(css) {
707
+ return new Promise((resolve, reject) => {
708
+ const proc = spawn('npx', ['stylelint', '--stdin', '--fix'], {
709
+ stdio: ['pipe', 'pipe', 'pipe'],
710
+ shell: true
711
+ });
712
+ let stdout = '';
713
+ let stderr = '';
714
+ proc.stdout.on('data', data => {
715
+ stdout += data.toString();
716
+ });
717
+ proc.stderr.on('data', data => {
718
+ stderr += data.toString();
719
+ });
720
+ proc.on('close', code => {
721
+ if (code === 0) {
722
+ resolve({
723
+ stdout,
724
+ stderr
725
+ });
726
+ } else {
727
+ const error = new Error(`Stylelint exited with code ${code}`);
728
+ error.stdout = stdout;
729
+ error.stderr = stderr;
730
+ reject(error);
731
+ }
732
+ });
733
+ proc.on('error', reject);
734
+ proc.stdin.write(css);
735
+ proc.stdin.end();
736
+ });
737
+ }
738
+
739
+ var version = "0.3.1";
689
740
  var packageJson = {
690
741
  version: version};
691
742
 
@@ -1056,7 +1107,7 @@ ${text}`
1056
1107
  // Design Tokens
1057
1108
  // -----------------------------------------------------------------------------
1058
1109
  server.registerTool('find_tokens', {
1059
- description: "Search for specific tokens. Tip: If you only provide a 'group' and leave 'query' empty, it returns all tokens in that category. Avoid property-by-property searching.",
1110
+ description: 'Search for specific tokens. Tip: If you only provide a \'group\' and leave \'query\' empty, it returns all tokens in that category. Avoid property-by-property searching. COLOR RESOLUTION: If a user asks for "pink" or "blue", do not search for the color name. Use the semantic intent: blue->accent, red->danger, green->success. Always check both "emphasis" and "muted" variants for background colors. After identifying tokens and writing CSS, you MUST validate the result using lint_css.',
1060
1111
  inputSchema: {
1061
1112
  query: z.string().optional().default('').describe('Search keywords (e.g., "danger border", "success background")'),
1062
1113
  group: z.string().optional().describe('Filter by group (e.g., "fgColor", "border")'),
@@ -1217,6 +1268,36 @@ server.registerTool('get_token_usage_patterns', {
1217
1268
  }]
1218
1269
  };
1219
1270
  });
1271
+ server.registerTool('lint_css', {
1272
+ description: 'REQUIRED FINAL STEP. Use this to validate your CSS. You cannot complete a task involving CSS without a successful run of this tool.',
1273
+ inputSchema: {
1274
+ css: z.string()
1275
+ }
1276
+ }, async ({
1277
+ css
1278
+ }) => {
1279
+ try {
1280
+ // --fix flag tells Stylelint to repair what it can
1281
+ const {
1282
+ stdout
1283
+ } = await runStylelint(css);
1284
+ return {
1285
+ content: [{
1286
+ type: 'text',
1287
+ text: stdout || '✅ Stylelint passed (or was successfully autofixed).'
1288
+ }]
1289
+ };
1290
+ } catch (error) {
1291
+ // If Stylelint still has errors it CANNOT fix, it will land here
1292
+ const errorOutput = error instanceof Error && 'stdout' in error ? error.stdout : String(error);
1293
+ return {
1294
+ content: [{
1295
+ type: 'text',
1296
+ text: `❌ Errors without autofix remaining:\n${errorOutput}`
1297
+ }]
1298
+ };
1299
+ }
1300
+ });
1220
1301
 
1221
1302
  // -----------------------------------------------------------------------------
1222
1303
  // Foundations
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAA;AAuBjE,QAAA,MAAM,MAAM,WAGV,CAAA;AAu5BF,OAAO,EAAC,MAAM,EAAC,CAAA"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAA;AAwBjE,QAAA,MAAM,MAAM,WAGV,CAAA;AA27BF,OAAO,EAAC,MAAM,EAAC,CAAA"}
package/dist/stdio.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
2
- import { s as server } from './server-Cwz0naYT.js';
2
+ import { s as server } from './server-owUZMSeG.js';
3
3
  import '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  import 'cheerio';
5
5
  import 'zod';
@@ -8,6 +8,7 @@ import '@primer/react/generated/components.json' with { type: 'json' };
8
8
  import '@primer/octicons/build/data.json' with { type: 'json' };
9
9
  import 'node:fs';
10
10
  import 'node:module';
11
+ import 'child_process';
11
12
  import '@primer/primitives/dist/docs/base/motion/motion.json' with { type: 'json' };
12
13
  import '@primer/primitives/dist/docs/base/size/size.json' with { type: 'json' };
13
14
  import '@primer/primitives/dist/docs/base/typography/typography.json' with { type: 'json' };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@primer/mcp",
3
3
  "description": "An MCP server that connects AI tools to the Primer Design System",
4
- "version": "0.3.0",
4
+ "version": "0.3.1",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "mcp": "./bin/mcp.js"
@@ -37,7 +37,7 @@
37
37
  "@modelcontextprotocol/sdk": "^1.24.0",
38
38
  "@primer/octicons": "^19.15.5",
39
39
  "@primer/primitives": "10.x || 11.x",
40
- "@primer/react": "^38.12.0",
40
+ "@primer/react": "^38.15.0",
41
41
  "cheerio": "^1.0.0",
42
42
  "turndown": "^7.2.0",
43
43
  "zod": "^4.3.5"
package/src/primitives.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {readFileSync} from 'node:fs'
2
2
  import {createRequire} from 'node:module'
3
+ import {spawn} from 'child_process'
3
4
  import baseMotion from '@primer/primitives/dist/docs/base/motion/motion.json' with {type: 'json'}
4
5
  import baseSize from '@primer/primitives/dist/docs/base/size/size.json' with {type: 'json'}
5
6
  import baseTypography from '@primer/primitives/dist/docs/base/typography/typography.json' with {type: 'json'}
@@ -121,7 +122,17 @@ function serialize(value: typeof tokens): string {
121
122
  }
122
123
 
123
124
  // Semantic group prefixes that apply to any element
124
- const SEMANTIC_PREFIXES = ['bgColor', 'fgColor', 'border', 'borderColor', 'shadow', 'focus', 'color']
125
+ const SEMANTIC_PREFIXES = [
126
+ 'bgColor',
127
+ 'fgColor',
128
+ 'border',
129
+ 'borderColor',
130
+ 'shadow',
131
+ 'focus',
132
+ 'color',
133
+ 'animation',
134
+ 'duration',
135
+ ]
125
136
 
126
137
  type TokenGroup = {
127
138
  name: string
@@ -421,51 +432,42 @@ function getDesignTokenSpecsText(groups: TokenGroups): string {
421
432
  # Design Token Specifications
422
433
 
423
434
  ## 1. Core Rule & Enforcement
424
- - **Expert Mode**: You are a CSS expert. NEVER use raw values (hex, px, etc.). Only use tokens.
425
- - **Shorthand**: MUST use shorthand tokens (e.g., \`font: var(...)\`). NEVER split font-size/weight.
426
- - **States**: MUST define 5 states: Rest, Hover, Focus-visible, Active, Disabled.
427
- - **Safety**: If unsure of a token name, suffix with \`/* check-token */\`.
428
- - **Focus States**: When implementing :focus-visible, you MUST use both:
429
- - outline: var(--focus-outline)
430
- - outline-offset: var(--outline-focus-offset)
435
+ * **Expert Mode**: CSS expert. NEVER use raw values (hex, px, etc.). Tokens only.
436
+ * **Motion & Transitions:** Every interactive state change (Hover, Active) MUST include a transition. NEVER use raw values like 200ms or ease-in. Use var(--base-duration-...) and var(--base-easing-...).
437
+ * **Shorthand**: MUST use \`font: var(...)\`. NEVER split size/weight.
438
+ * **Shorthand Fallback**: If no shorthand exists (e.g. Monospace), use individual tokens for font-size, family, and line-height. NEVER raw 1.5.
439
+ * **States**: Define 5: Rest, Hover, Focus-visible, Active, Disabled.
440
+ * **Focus**: \`:focus-visible\` MUST use \`outline: var(--focus-outline)\` AND \`outline-offset: var(--outline-focus-offset)\`.
441
+ * **Validation**: CALL \`lint_css\` after any CSS change. Task is incomplete without a success message.
442
+ * **Self-Correction**: Adopt autofixes immediately. Report unfixable errors to the user.
431
443
 
432
444
  ## 2. Typography Constraints (STRICT)
433
- - **Body Only**: Only the \`body\` group supports size suffixes (e.g., \`body-small\`, \`body-medium\`).
434
- - **Static Shorthands**: The following groups do NOT support size suffixes. Use the base shorthand only:
435
- - **caption**: \`var(--text-caption-shorthand)\` (NEVER add -medium or -small)
436
- - **display**: \`var(--text-display-shorthand)\`
437
- - **codeBlock**: \`var(--text-codeBlock-shorthand)\`
438
- - **codeInline**: \`var(--text-codeInline-shorthand)\`
439
-
440
- ## 3. Logic Matrix: Color Pairings (CRITICAL)
441
- | Background Token | Foreground Token | Requirement |
442
- | :--- | :--- | :--- |
443
- | --bgColor-*-emphasis | --fgColor-onEmphasis | MUST pair |
444
- | --bgColor-*-muted | --fgColor-{semantic} | MUST match semantic |
445
- | --bgColor-default | --fgColor-default | Standard pairing |
446
- | --bgColor-muted | --fgColor-default | NEVER use fgColor-muted |
447
-
448
- ## 4. Semantic Intent Key
449
- Use these for \`find_tokens\` or to map natural language to groups:
450
- - **Search Aliases**: Map "background" -> \`bgColor\`, "foreground" -> \`fgColor\`, "typography/font" -> \`text\`, "padding/margin" -> \`stack\`, "radius" -> \`borderRadius\`, "shadow/elevation" -> \`overlay\`.
451
- - **danger**: Errors/Destructive | **success**: Positive/Done
452
- - **attention**: Warning/Pending | **accent**: Interactive/Selected
453
-
454
- ## 5. Available Token Groups
455
- Use these names in \`find_tokens(group: "...")\`:
456
- - **Semantic**: ${groups.semantic.map(g => g.name).join(', ')}
457
- - **Components**: ${groups.component.map(g => g.name).join(', ')}
458
-
459
- ## 6. Optimization Strategy (MANDATORY)
460
- - **STOP**: Do not call \`find_tokens\` repeatedly for individual properties.
461
- - **GO**: Use \`get_token_group_bundle\` to fetch relevant groups at once.
462
- - *Example for Button*: \`get_token_group_bundle(groups: ["control", "button"])\`
463
- - *Note*: \`control\` is for form inputs; \`button\` is for triggers.
464
-
465
- ## 7. Token Bundle Recipes (Recommended)
466
- - **Forms/Inputs**: \`["control", "focus", "outline", "text", "borderRadius"]\`
467
- - **Modals/Dialogs**: \`["overlay", "shadow", "outline", "borderRadius", "bgColor"]\`
468
- - **Data Tables**: \`["stack", "borderColor", "text", "bgColor"]\`
445
+ - **Body Only**: Only \`body\` group supports size suffixes (e.g., \`body-small\`).
446
+ - **Static Shorthands**: NEVER add suffixes to \`caption\`, \`display\`, \`codeBlock\`, or \`codeInline\`.
447
+
448
+ ## 3. Logic Matrix: Color & Semantic Mapping
449
+ | Input Color/Intent | Semantic Role | Background Suffix | Foreground Requirement |
450
+ | :--- | :--- | :--- | :--- |
451
+ | Blue / Interactive | \`accent\` | \`-emphasis\` (Solid) | \`fgColor-onEmphasis\` |
452
+ | Green / Positive | \`success\` | \`-muted\` (Light) | \`fgColor-{semantic}\` |
453
+ | Red / Danger | \`danger\` | \`-emphasis\` | \`fgColor-onEmphasis\` |
454
+ | Yellow / Warning | \`attention\` | \`-muted\` | \`fgColor-attention\` |
455
+ | Orange / Critical | \`severe\` | \`-emphasis\` | \`fgColor-onEmphasis\` |
456
+ | Purple / Done | \`done\` | Any | Match intent |
457
+ | Pink / Sponsors | \`sponsors\` | Any | Match intent |
458
+ | Grey / Neutral | \`default\` | \`bgColor-muted\` | \`fgColor-default\` (Not muted) |
459
+
460
+ ## 4. Optimization & Recipes (MANDATORY)
461
+ **Strategy**: STOP property-by-property searching. Use \`get_token_group_bundle\` for these common patterns:
462
+ - **Forms**: \`["control", "focus", "outline", "text", "borderRadius", "stack", "animation"]\`
463
+ - **Modals/Cards**: \`["overlay", "shadow", "outline", "borderRadius", "bgColor", "stack", "animation"]\`
464
+ - **Tables/Lists**: \`["stack", "borderColor", "text", "bgColor", "control"]\`
465
+ - **Nav/Sidebars**: \`["control", "text", "accent", "stack", "focus", "animation"]\`
466
+ - **Status/Badges**: \`["text", "success", "danger", "attention", "severe", "stack"]\`
467
+
468
+ ## 5. Available Groups
469
+ - **Semantic**: ${groups.semantic.map(g => `${g.name}\``).join(', ')}
470
+ - **Components**: ${groups.component.map(g => `\`${g.name}\``).join(', ')}
469
471
  `.trim()
470
472
  }
471
473
 
@@ -625,6 +627,9 @@ const GROUP_ALIASES: Record<string, string> = {
625
627
  typography: 'text',
626
628
  font: 'text',
627
629
  text: 'text',
630
+ 'line-height': 'text',
631
+ lineheight: 'text',
632
+ leading: 'text',
628
633
 
629
634
  // Layout & Spacing
630
635
  stack: 'stack',
@@ -645,6 +650,27 @@ const GROUP_ALIASES: Record<string, string> = {
645
650
  line: 'borderColor',
646
651
  stroke: 'borderColor',
647
652
  separator: 'borderColor',
653
+
654
+ // Color-to-Semantic Intent Mapping
655
+ red: 'danger',
656
+ green: 'success',
657
+ yellow: 'attention',
658
+ orange: 'severe',
659
+ blue: 'accent',
660
+ purple: 'done',
661
+ pink: 'sponsors',
662
+ grey: 'neutral',
663
+ gray: 'neutral',
664
+
665
+ // Descriptive Aliases
666
+ light: 'muted',
667
+ subtle: 'muted',
668
+ dark: 'emphasis',
669
+ strong: 'emphasis',
670
+ intense: 'emphasis',
671
+ bold: 'emphasis',
672
+ vivid: 'emphasis',
673
+ highlight: 'emphasis',
648
674
  }
649
675
 
650
676
  // Match a token against a resolved group by checking both the token name prefix and the group label
@@ -701,10 +727,51 @@ function getValidGroupsList(validTokens: TokenWithGuidelines[]): string {
701
727
  const groupHints: Record<string, string> = {
702
728
  control: '`control` tokens are for form inputs/checkboxes. For buttons, use the `button` group.',
703
729
  button: '`button` tokens are for standard triggers. For form-fields, see the `control` group.',
704
- text: 'STRICT: The following typography groups do NOT support size suffixes (-small, -medium, -large): `caption`, `display`, `codeBlock`, and `codeInline`. Use the base shorthand name only (e.g., --text-codeBlock-shorthand).',
730
+ text: 'STRICT: The following typography groups do NOT support size suffixes (-small, -medium, -large): `caption`, `display`, `codeBlock`, and `codeInline`. STRICT: Use shorthand tokens where possible. If splitting, you MUST fetch line-height tokens (e.g., --text-body-lineHeight-small) instead of using raw numbers.',
705
731
  fgColor: 'Use `fgColor` for text. For borders, use `borderColor`.',
706
732
  borderWidth:
707
733
  '`borderWidth` only has sizing values (thin, thick, thicker). For border *colors*, use the `borderColor` or `border` group.',
734
+ animation:
735
+ 'TRANSITION RULE: Apply duration and easing to the base class, not the :hover state. Standard pairing: `transition: background-color var(--base-duration-200) var(--base-easing-easeInOut);`',
736
+ }
737
+
738
+ // -----------------------------------------------------------------------------
739
+ // Stylelint runner
740
+ // -----------------------------------------------------------------------------
741
+ function runStylelint(css: string): Promise<{stdout: string; stderr: string}> {
742
+ return new Promise((resolve, reject) => {
743
+ const proc = spawn('npx', ['stylelint', '--stdin', '--fix'], {
744
+ stdio: ['pipe', 'pipe', 'pipe'],
745
+ shell: true,
746
+ })
747
+
748
+ let stdout = ''
749
+ let stderr = ''
750
+
751
+ proc.stdout.on('data', (data: Buffer) => {
752
+ stdout += data.toString()
753
+ })
754
+
755
+ proc.stderr.on('data', (data: Buffer) => {
756
+ stderr += data.toString()
757
+ })
758
+
759
+ proc.on('close', code => {
760
+ if (code === 0) {
761
+ resolve({stdout, stderr})
762
+ } else {
763
+ const error = new Error(`Stylelint exited with code ${code}`) as Error & {stdout: string; stderr: string}
764
+ error.stdout = stdout
765
+ error.stderr = stderr
766
+ reject(error)
767
+ }
768
+ })
769
+
770
+ proc.on('error', reject)
771
+
772
+ proc.stdin.write(css)
773
+ proc.stdin.end()
774
+ })
708
775
  }
709
776
 
710
777
  export {
@@ -724,5 +791,6 @@ export {
724
791
  GROUP_ALIASES,
725
792
  GROUP_LABELS,
726
793
  tokenMatchesGroup,
794
+ runStylelint,
727
795
  type TokenWithGuidelines,
728
796
  }