@primer/mcp 0.0.5 → 0.3.0

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/src/server.ts CHANGED
@@ -1,10 +1,24 @@
1
1
  import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
2
2
  // eslint-disable-next-line import/no-namespace
3
3
  import * as cheerio from 'cheerio'
4
- import {z} from 'zod'
4
+ // eslint-disable-next-line import/no-namespace
5
+ import * as z from 'zod'
5
6
  import TurndownService from 'turndown'
6
7
  import {listComponents, listPatterns, listIcons} from './primer'
7
- import {tokens, serialize} from './primitives'
8
+ import {
9
+ listTokenGroups,
10
+ loadAllTokensWithGuidelines,
11
+ loadDesignTokensGuide,
12
+ getDesignTokenSpecsText,
13
+ getTokenUsagePatternsText,
14
+ searchTokens,
15
+ formatBundle,
16
+ GROUP_ALIASES,
17
+ tokenMatchesGroup,
18
+ type TokenWithGuidelines,
19
+ getValidGroupsList,
20
+ groupHints,
21
+ } from './primitives'
8
22
  import packageJson from '../package.json' with {type: 'json'}
9
23
 
10
24
  const server = new McpServer({
@@ -14,38 +28,46 @@ const server = new McpServer({
14
28
 
15
29
  const turndownService = new TurndownService()
16
30
 
31
+ // Load all tokens with guidelines from primitives
32
+ const allTokensWithGuidelines: TokenWithGuidelines[] = loadAllTokensWithGuidelines()
33
+
17
34
  // -----------------------------------------------------------------------------
18
35
  // Project setup
19
36
  // -----------------------------------------------------------------------------
20
- server.tool('init', 'Setup or create a project that includes Primer React', async () => {
21
- const url = new URL(`/product/getting-started/react`, 'https://primer.style')
22
- const response = await fetch(url)
23
- if (!response.ok) {
24
- throw new Error(`Failed to fetch ${url}: ${response.statusText}`)
25
- }
26
-
27
- const html = await response.text()
28
- if (!html) {
29
- return {
30
- content: [],
37
+ server.registerTool(
38
+ 'init',
39
+ {
40
+ description: 'Setup or create a project that includes Primer React',
41
+ },
42
+ async () => {
43
+ const url = new URL(`/product/getting-started/react`, 'https://primer.style')
44
+ const response = await fetch(url)
45
+ if (!response.ok) {
46
+ throw new Error(`Failed to fetch ${url}: ${response.statusText}`)
31
47
  }
32
- }
33
48
 
34
- const $ = cheerio.load(html)
35
- const source = $('main').html()
36
- if (!source) {
37
- return {
38
- content: [],
49
+ const html = await response.text()
50
+ if (!html) {
51
+ return {
52
+ content: [],
53
+ }
39
54
  }
40
- }
41
55
 
42
- const text = turndownService.turndown(source)
56
+ const $ = cheerio.load(html)
57
+ const source = $('main').html()
58
+ if (!source) {
59
+ return {
60
+ content: [],
61
+ }
62
+ }
43
63
 
44
- return {
45
- content: [
46
- {
47
- type: 'text',
48
- text: `The getting started documentation for Primer React is included below. It's important that the project:
64
+ const text = turndownService.turndown(source)
65
+
66
+ return {
67
+ content: [
68
+ {
69
+ type: 'text',
70
+ text: `The getting started documentation for Primer React is included below. It's important that the project:
49
71
 
50
72
  - Is using a tool like Vite, Next.js, etc that supports TypeScript and React. If the project does not have support for that, generate an appropriate project scaffold
51
73
  - Installs the latest version of \`@primer/react\` from \`npm\`
@@ -58,37 +80,45 @@ server.tool('init', 'Setup or create a project that includes Primer React', asyn
58
80
 
59
81
  ${text}
60
82
  `,
61
- },
62
- ],
63
- }
64
- })
83
+ },
84
+ ],
85
+ }
86
+ },
87
+ )
65
88
 
66
89
  // -----------------------------------------------------------------------------
67
90
  // Components
68
91
  // -----------------------------------------------------------------------------
69
- server.tool('list_components', 'List all of the components available from Primer React', async () => {
70
- const components = listComponents().map(component => {
71
- return `- ${component.name}`
72
- })
73
- return {
74
- content: [
75
- {
76
- type: 'text',
77
- text: `The following components are available in the @primer/react in TypeScript projects:
92
+ server.registerTool(
93
+ 'list_components',
94
+ {description: 'List all of the components available from Primer React'},
95
+ async () => {
96
+ const components = listComponents().map(component => {
97
+ return `- ${component.name}`
98
+ })
99
+ return {
100
+ content: [
101
+ {
102
+ type: 'text',
103
+ text: `The following components are available in the @primer/react in TypeScript projects:
78
104
 
79
105
  ${components.join('\n')}
80
106
 
81
107
  You can use the \`get_component\` tool to get more information about a specific component. You can use these components from the @primer/react package.`,
82
- },
83
- ],
84
- }
85
- })
108
+ },
109
+ ],
110
+ }
111
+ },
112
+ )
86
113
 
87
- server.tool(
114
+ server.registerTool(
88
115
  'get_component',
89
- 'Retrieve documentation and usage details for a specific React component from the @primer/react package by its name. This tool provides the official Primer documentation for any listed component, making it easy to inspect, reuse, or integrate components in your project.',
90
116
  {
91
- name: z.string().describe('The name of the component to retrieve'),
117
+ description:
118
+ 'Retrieve documentation and usage details for a specific React component from the @primer/react package by its name. This tool provides the official Primer documentation for any listed component, making it easy to inspect, reuse, or integrate components in your project.',
119
+ inputSchema: {
120
+ name: z.string().describe('The name of the component to retrieve'),
121
+ },
92
122
  },
93
123
  async ({name}) => {
94
124
  const components = listComponents()
@@ -97,12 +127,27 @@ server.tool(
97
127
  })
98
128
  if (!match) {
99
129
  return {
100
- content: [
101
- {
102
- type: 'text',
103
- text: `There is no component named \`${name}\` in the @primer/react package. For a full list of components, use the \`list_components\` tool.`,
104
- },
105
- ],
130
+ isError: true,
131
+ errorMessage: `There is no component named \`${name}\` in the @primer/react package. For a full list of components, use the \`list_components\` tool.`,
132
+ content: [],
133
+ }
134
+ }
135
+
136
+ const llmsUrl = new URL(`/product/components/${match.slug}/llms.txt`, 'https://primer.style')
137
+ const llmsResponse = await fetch(llmsUrl)
138
+ if (llmsResponse.ok) {
139
+ try {
140
+ const llmsText = await llmsResponse.text()
141
+ return {
142
+ content: [
143
+ {
144
+ type: 'text',
145
+ text: llmsText,
146
+ },
147
+ ],
148
+ }
149
+ } catch (_: unknown) {
150
+ // If there's an error fetching or processing the llms.txt, we fall back to the regular documentation
106
151
  }
107
152
  }
108
153
 
@@ -141,11 +186,13 @@ ${text}`,
141
186
  },
142
187
  )
143
188
 
144
- server.tool(
189
+ server.registerTool(
145
190
  'get_component_examples',
146
- 'Get examples for how to use a component from Primer React',
147
191
  {
148
- name: z.string().describe('The name of the component to retrieve'),
192
+ description: 'Get examples for how to use a component from Primer React',
193
+ inputSchema: {
194
+ name: z.string().describe('The name of the component to retrieve'),
195
+ },
149
196
  },
150
197
  async ({name}) => {
151
198
  const components = listComponents()
@@ -199,11 +246,13 @@ ${text}`,
199
246
  },
200
247
  )
201
248
 
202
- server.tool(
249
+ server.registerTool(
203
250
  'get_component_usage_guidelines',
204
- 'Get usage information for how to use a component from Primer',
205
251
  {
206
- name: z.string().describe('The name of the component to retrieve'),
252
+ description: 'Get usage information for how to use a component from Primer',
253
+ inputSchema: {
254
+ name: z.string().describe('The name of the component to retrieve'),
255
+ },
207
256
  },
208
257
  async ({name}) => {
209
258
  const components = listComponents()
@@ -268,11 +317,14 @@ ${text}`,
268
317
  },
269
318
  )
270
319
 
271
- server.tool(
320
+ server.registerTool(
272
321
  'get_component_accessibility_guidelines',
273
- 'Retrieve accessibility guidelines and best practices for a specific component from the @primer/react package by its name. Use this tool to get official accessibility recommendations, usage tips, and requirements to ensure your UI components are inclusive and meet accessibility standards.',
274
322
  {
275
- name: z.string().describe('The name of the component to retrieve'),
323
+ description:
324
+ 'Retrieve accessibility guidelines and best practices for a specific component from the @primer/react package by its name. Use this tool to get official accessibility recommendations, usage tips, and requirements to ensure your UI components are inclusive and meet accessibility standards.',
325
+ inputSchema: {
326
+ name: z.string().describe('The name of the component to retrieve'),
327
+ },
276
328
  },
277
329
  async ({name}) => {
278
330
  const components = listComponents()
@@ -340,27 +392,33 @@ ${text}`,
340
392
  // -----------------------------------------------------------------------------
341
393
  // Patterns
342
394
  // -----------------------------------------------------------------------------
343
- server.tool('list_patterns', 'List all of the patterns available from Primer React', async () => {
344
- const patterns = listPatterns().map(pattern => {
345
- return `- ${pattern.name}`
346
- })
347
- return {
348
- content: [
349
- {
350
- type: 'text',
351
- text: `The following patterns are available in the @primer/react in TypeScript projects:
395
+ server.registerTool(
396
+ 'list_patterns',
397
+ {description: 'List all of the patterns available from Primer React'},
398
+ async () => {
399
+ const patterns = listPatterns().map(pattern => {
400
+ return `- ${pattern.name}`
401
+ })
402
+ return {
403
+ content: [
404
+ {
405
+ type: 'text',
406
+ text: `The following patterns are available in the @primer/react in TypeScript projects:
352
407
 
353
408
  ${patterns.join('\n')}`,
354
- },
355
- ],
356
- }
357
- })
409
+ },
410
+ ],
411
+ }
412
+ },
413
+ )
358
414
 
359
- server.tool(
415
+ server.registerTool(
360
416
  'get_pattern',
361
- 'Get a specific pattern by name',
362
417
  {
363
- name: z.string().describe('The name of the pattern to retrieve'),
418
+ description: 'Get a specific pattern by name',
419
+ inputSchema: {
420
+ name: z.string().describe('The name of the pattern to retrieve'),
421
+ },
364
422
  },
365
423
  async ({name}) => {
366
424
  const patterns = listPatterns()
@@ -417,127 +475,330 @@ ${text}`,
417
475
  // -----------------------------------------------------------------------------
418
476
  // Design Tokens
419
477
  // -----------------------------------------------------------------------------
420
- server.tool('list_tokens', 'List all of the design tokens available from Primer', async () => {
421
- let text =
422
- 'Below is a list of all design tokens available from Primer. Tokens are used in CSS and CSS Modules. To refer to the CSS Custom Property for a design token, wrap it in var(--{name-of-token}). To learn how to use a specific token, use a corresponding usage tool for the category of the token. For example, if a token is a color token look for the get_color_usage tool. \n\n'
423
-
424
- text += serialize(tokens)
425
-
426
- return {
427
- content: [
428
- {
429
- type: 'text',
430
- text,
431
- },
432
- ],
433
- }
434
- })
478
+ server.registerTool(
479
+ 'find_tokens',
480
+ {
481
+ description:
482
+ "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.",
483
+ inputSchema: {
484
+ query: z
485
+ .string()
486
+ .optional()
487
+ .default('')
488
+ .describe('Search keywords (e.g., "danger border", "success background")'),
489
+ group: z.string().optional().describe('Filter by group (e.g., "fgColor", "border")'),
490
+ limit: z
491
+ .number()
492
+ .int()
493
+ .min(1)
494
+ .max(100)
495
+ .optional()
496
+ .default(15)
497
+ .describe('Maximum results to return to stay within context limits'),
498
+ },
499
+ },
500
+ async ({query, group, limit}) => {
501
+ // Resolve group via aliases
502
+ const resolvedGroup = group ? GROUP_ALIASES[group.toLowerCase().replace(/\s+/g, '')] || group : undefined
503
+
504
+ // Split query into keywords and extract any that match a known group
505
+ const rawKeywords = query
506
+ .toLowerCase()
507
+ .split(/\s+/)
508
+ .filter(k => k.length > 0)
509
+
510
+ let effectiveGroup = resolvedGroup
511
+ const filteredKeywords: string[] = []
512
+
513
+ for (const kw of rawKeywords) {
514
+ const normalized = kw.replace(/\s+/g, '')
515
+ const aliasMatch = GROUP_ALIASES[normalized]
516
+ if (aliasMatch && !effectiveGroup) {
517
+ effectiveGroup = aliasMatch
518
+ } else {
519
+ filteredKeywords.push(kw)
520
+ }
521
+ }
522
+
523
+ // Guard: no query and no group → ask user to provide at least one
524
+ if (filteredKeywords.length === 0 && !effectiveGroup) {
525
+ return {
526
+ content: [
527
+ {
528
+ type: 'text',
529
+ text: 'Please provide a query, a group, or both. Call `get_design_token_specs` to see available token groups.',
530
+ },
531
+ ],
532
+ }
533
+ }
534
+
535
+ // Group-only search: return all tokens in the group
536
+ const isGroupOnly = filteredKeywords.length === 0 && effectiveGroup
537
+ let results: TokenWithGuidelines[]
538
+
539
+ if (isGroupOnly) {
540
+ results = allTokensWithGuidelines.filter(token => tokenMatchesGroup(token, effectiveGroup!))
541
+ } else {
542
+ results = searchTokens(allTokensWithGuidelines, filteredKeywords.join(' '), effectiveGroup)
543
+ }
544
+
545
+ if (results.length === 0) {
546
+ const validGroups = getValidGroupsList(allTokensWithGuidelines)
547
+
548
+ return {
549
+ content: [
550
+ {
551
+ type: 'text',
552
+ text: `No tokens found matching "${query}"${effectiveGroup ? ` in group "${effectiveGroup}"` : ''}.
553
+
554
+ ### 💡 Available Groups:
555
+ ${validGroups}
556
+
557
+ ### Troubleshooting for AI:
558
+ 1. **Multi-word Queries**: Search keywords use 'AND' logic. If searching "text shorthand typography" fails, try a single keyword like "shorthand" within the "text" group.
559
+ 2. **Property Mismatch**: Do not search for CSS properties like "offset", "padding", or "font-size". Use semantic intent keywords: "danger", "muted", "emphasis".
560
+ 3. **Typography**: Remember that \`caption\`, \`display\`, and \`code\` groups do NOT support size suffixes. Use the base shorthand only.
561
+ 4. **Group Intent**: Use the \`group\` parameter instead of putting group names in the \`query\` string (e.g., use group: "stack" instead of query: "stack padding").`,
562
+ },
563
+ ],
564
+ }
565
+ }
566
+
567
+ const limitedResults = results.slice(0, limit)
568
+
569
+ let output: string
570
+
571
+ if (!query) {
572
+ output = `Found ${results.length} token(s). Showing top ${limitedResults.length}:\n\n`
573
+ } else {
574
+ output = `Found ${results.length} token(s) matching "${query}". Showing top ${limitedResults.length}:\n\n`
575
+ }
576
+ output += formatBundle(limitedResults)
577
+
578
+ if (results.length > limit) {
579
+ output += `\n\n*...and ${results.length - limit} more matches. Use more specific keywords to narrow the search.*`
580
+ }
435
581
 
436
- // -----------------------------------------------------------------------------
437
- // Foundations
438
- // -----------------------------------------------------------------------------
439
- server.tool('get_color_usage', 'Get the guidelines for how to apply color to a user interface', async () => {
440
- const url = new URL(`/product/getting-started/foundations/color-usage`, 'https://primer.style')
441
- const response = await fetch(url)
442
- if (!response.ok) {
443
- throw new Error(`Failed to fetch ${url} - ${response.statusText}`)
444
- }
445
-
446
- const html = await response.text()
447
- if (!html) {
448
582
  return {
449
- content: [],
583
+ content: [{type: 'text', text: output}],
584
+ }
585
+ },
586
+ )
587
+
588
+ server.registerTool(
589
+ 'get_token_group_bundle',
590
+ {
591
+ description:
592
+ "PREFERRED FOR COMPONENTS. Fetch all tokens for complex UI (e.g., Dialogs, Cards) in one call by providing an array of groups like ['overlay', 'shadow']. Use this instead of multiple find_tokens calls to save context.",
593
+ inputSchema: {
594
+ groups: z.array(z.string()).describe('Array of group names (e.g., ["overlay", "shadow", "focus"])'),
595
+ },
596
+ },
597
+ async ({groups}) => {
598
+ // Normalize and resolve aliases
599
+ const resolvedGroups = groups.map(g => {
600
+ const normalized = g.toLowerCase().replace(/\s+/g, '')
601
+ return GROUP_ALIASES[normalized] || g
602
+ })
603
+
604
+ // Filter tokens matching any of the resolved groups
605
+ const matched = allTokensWithGuidelines.filter(token => resolvedGroups.some(rg => tokenMatchesGroup(token, rg)))
606
+
607
+ if (matched.length === 0) {
608
+ const validGroups = getValidGroupsList(allTokensWithGuidelines)
609
+ return {
610
+ content: [
611
+ {
612
+ type: 'text',
613
+ text: `No tokens found for groups: ${groups.join(', ')}.\n\n### Valid Groups:\n${validGroups}`,
614
+ },
615
+ ],
616
+ }
617
+ }
618
+
619
+ let text = `Found ${matched.length} token(s) across ${resolvedGroups.length} group(s):\n\n${formatBundle(matched)}`
620
+
621
+ const activeHints = resolvedGroups.map(g => groupHints[g]).filter(Boolean)
622
+
623
+ if (activeHints.length > 0) {
624
+ text += `\n\n### ⚠️ Usage Guidance:\n${activeHints.map(h => `- ${h}`).join('\n')}`
450
625
  }
451
- }
452
626
 
453
- const $ = cheerio.load(html)
454
- const source = $('main').html()
455
- if (!source) {
456
627
  return {
457
- content: [],
628
+ content: [{type: 'text', text}],
458
629
  }
459
- }
630
+ },
631
+ )
460
632
 
461
- const text = turndownService.turndown(source)
633
+ server.registerTool(
634
+ 'get_design_token_specs',
635
+ {
636
+ description:
637
+ 'CRITICAL: CALL THIS FIRST. Provides the logic matrix and the list of valid group names. You cannot search accurately without this map.',
638
+ },
639
+ async () => {
640
+ const groups = listTokenGroups()
641
+ const customRules = getDesignTokenSpecsText(groups)
642
+ let text: string
643
+ try {
644
+ const upstreamGuide = loadDesignTokensGuide()
645
+ text = `${customRules}\n\n---\n\n${upstreamGuide}`
646
+ } catch {
647
+ text = customRules
648
+ }
462
649
 
463
- return {
464
- content: [
465
- {
466
- type: 'text',
467
- text: `Here is the documentation for color usage in Primer:\n\n${text}`,
468
- },
469
- ],
470
- }
471
- })
650
+ return {
651
+ content: [{type: 'text', text}],
652
+ }
653
+ },
654
+ )
472
655
 
473
- server.tool('get_typography_usage', 'Get the guidelines for how to apply typography to a user interface', async () => {
474
- const url = new URL(`/product/getting-started/foundations/typography`, 'https://primer.style')
475
- const response = await fetch(url)
476
- if (!response.ok) {
477
- throw new Error(`Failed to fetch ${url} - ${response.statusText}`)
478
- }
656
+ server.registerTool(
657
+ 'get_token_usage_patterns',
658
+ {
659
+ description:
660
+ 'Provides "Golden Example" CSS for core patterns: Button (Interactions) and Stack (Layout). Use this to understand how to apply the Logic Matrix, Motion, and Spacing scales.',
661
+ },
662
+ async () => {
663
+ const customPatterns = getTokenUsagePatternsText()
664
+ let text: string
665
+ try {
666
+ const guide = loadDesignTokensGuide()
667
+ const goldenExampleMatch = guide.match(/## Golden Example[\s\S]*?(?=\n## |$)/)
668
+ if (goldenExampleMatch) {
669
+ text = `${customPatterns}\n\n---\n\n${goldenExampleMatch[0].trim()}`
670
+ } else {
671
+ text = customPatterns
672
+ }
673
+ } catch {
674
+ text = customPatterns
675
+ }
479
676
 
480
- const html = await response.text()
481
- if (!html) {
482
677
  return {
483
- content: [],
678
+ content: [{type: 'text', text}],
679
+ }
680
+ },
681
+ )
682
+
683
+ // -----------------------------------------------------------------------------
684
+ // Foundations
685
+ // -----------------------------------------------------------------------------
686
+ server.registerTool(
687
+ 'get_color_usage',
688
+ {description: 'Get the guidelines for how to apply color to a user interface'},
689
+ async () => {
690
+ const url = new URL(`/product/getting-started/foundations/color-usage`, 'https://primer.style')
691
+ const response = await fetch(url)
692
+ if (!response.ok) {
693
+ throw new Error(`Failed to fetch ${url} - ${response.statusText}`)
694
+ }
695
+
696
+ const html = await response.text()
697
+ if (!html) {
698
+ return {
699
+ content: [],
700
+ }
701
+ }
702
+
703
+ const $ = cheerio.load(html)
704
+ const source = $('main').html()
705
+ if (!source) {
706
+ return {
707
+ content: [],
708
+ }
484
709
  }
485
- }
486
710
 
487
- const $ = cheerio.load(html)
488
- const source = $('main').html()
489
- if (!source) {
711
+ const text = turndownService.turndown(source)
712
+
490
713
  return {
491
- content: [],
714
+ content: [
715
+ {
716
+ type: 'text',
717
+ text: `Here is the documentation for color usage in Primer:\n\n${text}`,
718
+ },
719
+ ],
492
720
  }
493
- }
721
+ },
722
+ )
494
723
 
495
- const text = turndownService.turndown(source)
724
+ server.registerTool(
725
+ 'get_typography_usage',
726
+ {description: 'Get the guidelines for how to apply typography to a user interface'},
727
+ async () => {
728
+ const url = new URL(`/product/getting-started/foundations/typography`, 'https://primer.style')
729
+ const response = await fetch(url)
730
+ if (!response.ok) {
731
+ throw new Error(`Failed to fetch ${url} - ${response.statusText}`)
732
+ }
496
733
 
497
- return {
498
- content: [
499
- {
500
- type: 'text',
501
- text: `Here is the documentation for typography usage in Primer:\n\n${text}`,
502
- },
503
- ],
504
- }
505
- })
734
+ const html = await response.text()
735
+ if (!html) {
736
+ return {
737
+ content: [],
738
+ }
739
+ }
740
+
741
+ const $ = cheerio.load(html)
742
+ const source = $('main').html()
743
+ if (!source) {
744
+ return {
745
+ content: [],
746
+ }
747
+ }
748
+
749
+ const text = turndownService.turndown(source)
750
+
751
+ return {
752
+ content: [
753
+ {
754
+ type: 'text',
755
+ text: `Here is the documentation for typography usage in Primer:\n\n${text}`,
756
+ },
757
+ ],
758
+ }
759
+ },
760
+ )
506
761
 
507
762
  // -----------------------------------------------------------------------------
508
763
  // Icons
509
764
  // -----------------------------------------------------------------------------
510
- server.tool('list_icons', 'List all of the icons (octicons) available from Primer Octicons React', async () => {
511
- const icons = listIcons().map(icon => {
512
- const keywords = icon.keywords.map(keyword => {
513
- return `<keyword>${keyword}</keyword>`
514
- })
515
- const sizes = icon.heights.map(height => {
516
- return `<size value="${height}"></size>`
765
+ server.registerTool(
766
+ 'list_icons',
767
+ {description: 'List all of the icons (octicons) available from Primer Octicons React'},
768
+ async () => {
769
+ const icons = listIcons().map(icon => {
770
+ const keywords = icon.keywords.map(keyword => {
771
+ return `<keyword>${keyword}</keyword>`
772
+ })
773
+ const sizes = icon.heights.map(height => {
774
+ return `<size value="${height}"></size>`
775
+ })
776
+ return [`<icon name="${icon.name}">`, ...keywords, ...sizes, `</icon>`].join('\n')
517
777
  })
518
- return [`<icon name="${icon.name}">`, ...keywords, ...sizes, `</icon>`].join('\n')
519
- })
520
778
 
521
- return {
522
- content: [
523
- {
524
- type: 'text',
525
- text: `The following icons are available in the @primer/octicons-react package in TypeScript projects:
779
+ return {
780
+ content: [
781
+ {
782
+ type: 'text',
783
+ text: `The following icons are available in the @primer/octicons-react package in TypeScript projects:
526
784
 
527
785
  ${icons.join('\n')}
528
786
 
529
787
  You can use the \`get_icon\` tool to get more information about a specific icon. You can use these components from the @primer/octicons-react package.`,
530
- },
531
- ],
532
- }
533
- })
788
+ },
789
+ ],
790
+ }
791
+ },
792
+ )
534
793
 
535
- server.tool(
794
+ server.registerTool(
536
795
  'get_icon',
537
- 'Get a specific icon (octicon) by name from Primer',
538
796
  {
539
- name: z.string().describe('The name of the icon to retrieve'),
540
- size: z.string().optional().describe('The size of the icon to retrieve, e.g. "16"').default('16'),
797
+ description: 'Get a specific icon (octicon) by name from Primer',
798
+ inputSchema: {
799
+ name: z.string().describe('The name of the icon to retrieve'),
800
+ size: z.string().optional().describe('The size of the icon to retrieve, e.g. "16"').default('16'),
801
+ },
541
802
  },
542
803
  async ({name, size}) => {
543
804
  const icons = listIcons()
@@ -593,9 +854,9 @@ ${text}`,
593
854
  // -----------------------------------------------------------------------------
594
855
  // Coding guidelines
595
856
  // -----------------------------------------------------------------------------
596
- server.tool(
857
+ server.registerTool(
597
858
  'primer_coding_guidelines',
598
- 'Get the guidelines when writing code that uses Primer or for UI code that you are creating',
859
+ {description: 'Get the guidelines when writing code that uses Primer or for UI code that you are creating'},
599
860
  async () => {
600
861
  return {
601
862
  content: [
@@ -605,7 +866,7 @@ server.tool(
605
866
 
606
867
  ## Design Tokens
607
868
 
608
- - Prefer design tokens over hard-coded values. For example, use \`var(--fgColor-default)\` instead of \`#24292f\`. Use the \`list_tokens\` tool to find the design token you need.
869
+ - Prefer design tokens over hard-coded values. For example, use \`var(--fgColor-default)\` instead of \`#24292f\`. Use the \`find_tokens\` tool to search for a design token by keyword or group. Use \`get_design_token_specs\` to browse available token groups, and \`get_token_group_bundle\` to retrieve all tokens within a specific group.
609
870
  - Prefer recommending design tokens in the same group for related CSS properties. For example, when styling background and border color, use tokens from the same group/category
610
871
 
611
872
  ## Authoring & Using Components
@@ -643,19 +904,15 @@ The following list of coding guidelines must be followed:
643
904
  *
644
905
  *
645
906
  **/
646
- server.tool(
907
+ server.registerTool(
647
908
  'review_alt_text',
648
- 'Evaluates image alt text against accessibility best practices and context relevance.',
649
909
  {
650
- surroundingText: z.string().describe('Text surrounding the image, relevant to the image.'),
651
- alt: z.string().describe('The alt text of the image being evaluated'),
652
- image: z
653
- .union([
654
- z.instanceof(File).describe('The image src file being evaluated'),
655
- z.string().url().describe('The URL of the image src being evaluated'),
656
- z.string().describe('The file path of the image src being evaluated'),
657
- ])
658
- .describe('The image file, file path, or URL being evaluated'),
910
+ description: 'Evaluates image alt text against accessibility best practices and context relevance.',
911
+ inputSchema: {
912
+ surroundingText: z.string().describe('Text surrounding the image, relevant to the image.'),
913
+ alt: z.string().describe('The alt text of the image being evaluated'),
914
+ image: z.string().describe('The image URL or file path being evaluated'),
915
+ },
659
916
  },
660
917
  async ({surroundingText, alt, image}) => {
661
918
  // Call the LLM through MCP sampling