@atlaskit/ads-mcp 0.18.0 → 0.19.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.
Files changed (96) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +2 -2
  3. package/dist/cjs/helpers/fuse-multi-term.js +130 -0
  4. package/dist/cjs/helpers/index.js +15 -1
  5. package/dist/cjs/index.js +28 -14
  6. package/dist/cjs/instructions.js +1 -1
  7. package/dist/cjs/tools/analyze-a11y/index.js +12 -12
  8. package/dist/cjs/tools/get-a11y-guidelines/index.js +3 -3
  9. package/dist/cjs/tools/{get-components → get-all-components}/index.js +6 -6
  10. package/dist/cjs/tools/get-all-components/load-all-components.js +10 -0
  11. package/dist/cjs/tools/get-all-icons/index.js +1 -1
  12. package/dist/cjs/tools/get-all-tokens/index.js +1 -1
  13. package/dist/cjs/tools/get-guidelines/index.js +4 -4
  14. package/dist/cjs/tools/get-lint-rules/index.js +5 -5
  15. package/dist/cjs/tools/i18n-conversion/index.js +3 -3
  16. package/dist/cjs/tools/migration-guides/index.js +3 -3
  17. package/dist/cjs/tools/plan/index.js +13 -17
  18. package/dist/cjs/tools/search-components/index.js +44 -65
  19. package/dist/cjs/tools/search-icons/index.js +42 -68
  20. package/dist/cjs/tools/search-tokens/index.js +56 -43
  21. package/dist/cjs/tools/suggest-a11y-fixes/index.js +7 -7
  22. package/dist/es2019/helpers/fuse-multi-term.js +98 -0
  23. package/dist/es2019/helpers/index.js +1 -0
  24. package/dist/es2019/index.js +81 -83
  25. package/dist/es2019/instructions.js +2 -1
  26. package/dist/es2019/tools/analyze-a11y/index.js +28 -12
  27. package/dist/es2019/tools/get-a11y-guidelines/index.js +10 -3
  28. package/dist/es2019/tools/{get-components → get-all-components}/index.js +9 -4
  29. package/dist/es2019/tools/get-all-components/load-all-components.js +4 -0
  30. package/dist/es2019/tools/get-all-icons/index.js +6 -1
  31. package/dist/es2019/tools/get-all-tokens/index.js +6 -1
  32. package/dist/es2019/tools/get-guidelines/index.js +20 -7
  33. package/dist/es2019/tools/get-lint-rules/index.js +12 -8
  34. package/dist/es2019/tools/i18n-conversion/index.js +10 -13
  35. package/dist/es2019/tools/migration-guides/index.js +10 -4
  36. package/dist/es2019/tools/plan/index.js +25 -25
  37. package/dist/es2019/tools/search-components/index.js +42 -56
  38. package/dist/es2019/tools/search-icons/index.js +37 -62
  39. package/dist/es2019/tools/search-tokens/index.js +54 -45
  40. package/dist/es2019/tools/suggest-a11y-fixes/index.js +16 -7
  41. package/dist/esm/helpers/fuse-multi-term.js +122 -0
  42. package/dist/esm/helpers/index.js +1 -0
  43. package/dist/esm/index.js +28 -14
  44. package/dist/esm/instructions.js +1 -1
  45. package/dist/esm/tools/analyze-a11y/index.js +12 -12
  46. package/dist/esm/tools/get-a11y-guidelines/index.js +3 -3
  47. package/dist/esm/tools/{get-components → get-all-components}/index.js +5 -5
  48. package/dist/esm/tools/get-all-components/load-all-components.js +4 -0
  49. package/dist/esm/tools/get-all-icons/index.js +1 -1
  50. package/dist/esm/tools/get-all-tokens/index.js +1 -1
  51. package/dist/esm/tools/get-guidelines/index.js +4 -4
  52. package/dist/esm/tools/get-lint-rules/index.js +5 -5
  53. package/dist/esm/tools/i18n-conversion/index.js +3 -3
  54. package/dist/esm/tools/migration-guides/index.js +3 -3
  55. package/dist/esm/tools/plan/index.js +13 -17
  56. package/dist/esm/tools/search-components/index.js +45 -66
  57. package/dist/esm/tools/search-icons/index.js +43 -69
  58. package/dist/esm/tools/search-tokens/index.js +57 -44
  59. package/dist/esm/tools/suggest-a11y-fixes/index.js +7 -7
  60. package/dist/types/helpers/fuse-multi-term.d.ts +45 -0
  61. package/dist/types/helpers/index.d.ts +1 -0
  62. package/dist/types/instructions.d.ts +1 -1
  63. package/dist/types/tools/{get-components → get-all-components}/index.d.ts +2 -2
  64. package/dist/types/tools/plan/index.d.ts +1 -4
  65. package/dist/types/tools/search-components/index.d.ts +1 -4
  66. package/dist/types/tools/search-icons/index.d.ts +1 -4
  67. package/dist/types/tools/search-tokens/index.d.ts +1 -4
  68. package/dist/types-ts4.5/helpers/fuse-multi-term.d.ts +45 -0
  69. package/dist/types-ts4.5/helpers/index.d.ts +1 -0
  70. package/dist/types-ts4.5/instructions.d.ts +1 -1
  71. package/dist/types-ts4.5/tools/{get-components → get-all-components}/index.d.ts +2 -2
  72. package/dist/types-ts4.5/tools/plan/index.d.ts +1 -4
  73. package/dist/types-ts4.5/tools/search-components/index.d.ts +1 -4
  74. package/dist/types-ts4.5/tools/search-icons/index.d.ts +1 -4
  75. package/dist/types-ts4.5/tools/search-tokens/index.d.ts +1 -4
  76. package/package.json +5 -5
  77. package/dist/cjs/tools/get-components/load-all-components.js +0 -16
  78. package/dist/es2019/tools/get-components/load-all-components.js +0 -10
  79. package/dist/esm/tools/get-components/load-all-components.js +0 -10
  80. /package/dist/cjs/tools/{get-components → get-all-components}/components.codegen.js +0 -0
  81. /package/dist/cjs/tools/{get-components → get-all-components}/components.js +0 -0
  82. /package/dist/cjs/tools/{get-components → get-all-components}/types.js +0 -0
  83. /package/dist/es2019/tools/{get-components → get-all-components}/components.codegen.js +0 -0
  84. /package/dist/es2019/tools/{get-components → get-all-components}/components.js +0 -0
  85. /package/dist/es2019/tools/{get-components → get-all-components}/types.js +0 -0
  86. /package/dist/esm/tools/{get-components → get-all-components}/components.codegen.js +0 -0
  87. /package/dist/esm/tools/{get-components → get-all-components}/components.js +0 -0
  88. /package/dist/esm/tools/{get-components → get-all-components}/types.js +0 -0
  89. /package/dist/types/tools/{get-components → get-all-components}/components.codegen.d.ts +0 -0
  90. /package/dist/types/tools/{get-components → get-all-components}/components.d.ts +0 -0
  91. /package/dist/types/tools/{get-components → get-all-components}/load-all-components.d.ts +0 -0
  92. /package/dist/types/tools/{get-components → get-all-components}/types.d.ts +0 -0
  93. /package/dist/types-ts4.5/tools/{get-components → get-all-components}/components.codegen.d.ts +0 -0
  94. /package/dist/types-ts4.5/tools/{get-components → get-all-components}/components.d.ts +0 -0
  95. /package/dist/types-ts4.5/tools/{get-components → get-all-components}/load-all-components.d.ts +0 -0
  96. /package/dist/types-ts4.5/tools/{get-components → get-all-components}/types.d.ts +0 -0
@@ -8,14 +8,20 @@ import { getAvailableMigrationIds, getAvailableMigrationsDescription, migrationR
8
8
  const migrationIds = getAvailableMigrationIds();
9
9
  const migrationDescriptions = getAvailableMigrationsDescription();
10
10
  export const migrationGuidesInputSchema = z.object({
11
- migration: z.enum(migrationIds).describe(`The specific migration to perform.\n`),
12
- description: z.enum(migrationDescriptions).describe(`Description of the migration type.\n`)
11
+ migration: z.enum(migrationIds).describe('Migration id from the registry. Must match the guide you want (see tool description list).'),
12
+ description: z.enum(migrationDescriptions).describe('Human-readable migration label that pairs with `migration` in the schema—choose the entry that matches the selected id.')
13
13
  });
14
14
  export const listMigrationGuidesTool = {
15
15
  name: 'ads_migration_guides',
16
- description: `Migration guides for Atlassian Design System components.
16
+ description: `Returns a structured Atlassian Design System (ADS) **migration guide** for a known package or API migration (before/after examples, best practices, links).
17
17
 
18
- Available migrations:\n${getAvailableMigrationsDescription()}`,
18
+ WHEN TO USE:
19
+ You are upgrading or refactoring code between ADS packages or APIs and need the official migration pattern for a specific id listed below.
20
+
21
+ Pass **both** \`migration\` and \`description\` using a **matching pair** from the enum (schema enforces valid combinations).
22
+
23
+ Available migrations:
24
+ ${getAvailableMigrationsDescription()}`,
19
25
  annotations: {
20
26
  title: 'ADS Migration Guides',
21
27
  readOnlyHint: true,
@@ -6,17 +6,25 @@ import { searchComponentsTool } from '../search-components';
6
6
  import { searchIconsTool } from '../search-icons';
7
7
  import { searchTokensTool } from '../search-tokens';
8
8
  export const planInputSchema = z.object({
9
- tokens: z.array(z.string()).describe('Array of terms to search for tokens, eg. `["spacing", "inverted text", "background primary"]`. Provide a minimum of 2 terms when known.'),
10
- icons: z.array(z.string()).describe('Array of terms to search for icons, eg. `["search", "folder", "user"]`. Provide a minimum of 2 terms when known.'),
11
- components: z.array(z.string()).describe('Array of terms to search for components, eg. `["button", "input", "select"]`. Provide a minimum of 2 terms when known.'),
12
- limit: z.number().default(1).describe('Maximum number of results per search term in the provided arrays (default: 1)').optional(),
13
- exactName: z.boolean().default(false).describe('Search tokens, icons, and components by their exact name match (use when you explicitly know the name and need more details)').optional()
9
+ tokens: z.array(z.string()).describe('Search terms for ADS design tokens (fuzzy by default). Use `[]` if you only need icons or components. Prefer **at least two** terms per non-empty list when you know what you need.'),
10
+ icons: z.array(z.string()).describe('Search terms for ADS icons. Use `[]` if you only need tokens or components. Prefer **at least two** terms per non-empty list when known.'),
11
+ components: z.array(z.string()).describe('Search terms for ADS components. Use `[]` if you only need tokens or icons. Prefer **at least two** terms per non-empty list when known.'),
12
+ limit: z.number().default(2).describe('Max matches **per term** for each non-empty list (default 2). Same limit applies to tokens, icons, and components searches.').optional()
14
13
  });
15
14
  export const listPlanTool = {
16
15
  name: 'ads_plan',
17
- description: `Search how to use the Atlassian Design System offerings and get guidance on \`tokens\`, \`icons\`, and \`components\`.
16
+ description: `Runs **ads_search_tokens**, **ads_search_icons**, and **ads_search_components** in one call and returns a single JSON payload (each section only if that list was non-empty). Use this as the default way to discover ADS **tokens**, **icons**, and **components** for a UI task.
18
17
 
19
- YOU MUST ALWAYS call this tool with known parameters and include a minimum of 2 search terms per parameter when known, eg.
18
+ WHEN TO USE:
19
+ **Implementing or iterating on a UI**—new screen, feature, or polish—and you need candidate **token** names, **icon** imports, and **component** packages/props in one pass. Also use when exploring ADS building blocks before you write code.
20
+
21
+ At least one of \`tokens\`, \`icons\`, or \`components\` must contain search terms (use \`[]\` for lists you do not need).
22
+
23
+ Prefer supplying **multiple** terms per non-empty array when you know them—broader queries improve recall. Some queries return no rows where metadata is thin; try alternate wording.
24
+
25
+ This is equivalent to calling the individual search tools; there are no extra merge semantics beyond concatenating results.
26
+
27
+ Example request:
20
28
  \`\`\`json
21
29
  {
22
30
  "tokens": ["spacing", "inverted text", "background primary", "animation"],
@@ -25,8 +33,6 @@ YOU MUST ALWAYS call this tool with known parameters and include a minimum of 2
25
33
  }
26
34
  \`\`\`
27
35
 
28
- Please note, there may not be results for everything as there are minor gaps in offerings or how we describe them.
29
-
30
36
  Example token usage:
31
37
  \`\`\`tsx
32
38
  import { token } from '@atlaskit/tokens';
@@ -39,7 +45,7 @@ import AddIcon from '@atlaskit/icon/core/add';
39
45
  <AddIcon label="Add work item" size="small" />
40
46
  \`\`\``,
41
47
  annotations: {
42
- title: 'Plan ADS resources',
48
+ title: 'Search ADS tokens, icons, and components to plan what to build',
43
49
  readOnlyHint: true,
44
50
  destructiveHint: false,
45
51
  idempotentHint: true,
@@ -47,15 +53,12 @@ import AddIcon from '@atlaskit/icon/core/add';
47
53
  },
48
54
  inputSchema: zodToJsonSchema(planInputSchema)
49
55
  };
50
- export const planTool = async params => {
51
- const {
52
- tokens: tokens_search,
53
- icons: icons_search,
54
- components: components_search,
55
- limit = 1,
56
- exactName = false
57
- } = params;
58
-
56
+ export const planTool = async ({
57
+ tokens: tokens_search,
58
+ icons: icons_search,
59
+ components: components_search,
60
+ limit
61
+ }) => {
59
62
  // Validate that at least one search type is provided
60
63
  if (!(tokens_search !== null && tokens_search !== void 0 && tokens_search.length) && !(icons_search !== null && icons_search !== void 0 && icons_search.length) && !(components_search !== null && components_search !== void 0 && components_search.length)) {
61
64
  return {
@@ -73,8 +76,7 @@ export const planTool = async params => {
73
76
  if (tokens_search !== null && tokens_search !== void 0 && tokens_search.length) {
74
77
  searchPromises.push(searchTokensTool({
75
78
  terms: tokens_search,
76
- limit,
77
- exactName
79
+ limit
78
80
  }).then(result => {
79
81
  results.tokens = result;
80
82
  }));
@@ -82,8 +84,7 @@ export const planTool = async params => {
82
84
  if (icons_search !== null && icons_search !== void 0 && icons_search.length) {
83
85
  searchPromises.push(searchIconsTool({
84
86
  terms: icons_search,
85
- limit,
86
- exactName
87
+ limit
87
88
  }).then(result => {
88
89
  results.icons = result;
89
90
  }));
@@ -91,8 +92,7 @@ export const planTool = async params => {
91
92
  if (components_search !== null && components_search !== void 0 && components_search.length) {
92
93
  searchPromises.push(searchComponentsTool({
93
94
  terms: components_search,
94
- limit,
95
- exactName
95
+ limit
96
96
  }).then(result => {
97
97
  results.components = result;
98
98
  }));
@@ -2,16 +2,18 @@
2
2
 
3
3
  import Fuse from 'fuse.js';
4
4
  import { z } from 'zod';
5
- import { cleanQuery, zodToJsonSchema } from '../../helpers';
6
- import { loadAllComponents } from '../get-components/load-all-components';
5
+ import { cleanQuery, mergeMultiTermFuseResults, zodToJsonSchema } from '../../helpers';
6
+ import { loadAllComponents } from '../get-all-components/load-all-components';
7
7
  export const searchComponentsInputSchema = z.object({
8
- terms: z.array(z.string()).describe('An array of search terms to find components by name, package name, description, or example, eg. `["button", "input", "select"]`'),
9
- limit: z.number().default(1).describe('Maximum number of results per search term in the array (default: 1)').optional(),
10
- exactName: z.boolean().default(false).describe('Enable to explicitly search components by the exact name match (when you know the name, but need more details)').optional()
8
+ terms: z.array(z.string()).describe('Required: one or more search terms (fuzzy over name, package, category, description, keywords, examples). Example: `["button", "modal", "select"]`.'),
9
+ limit: z.number().default(2).describe('Max matches **per term** (default 2).').optional()
11
10
  });
12
11
  export const listSearchComponentsTool = {
13
12
  name: 'ads_search_components',
14
- description: 'Search for Atlassian Design System components.',
13
+ description: `Searches the bundled Atlassian Design System (ADS) component catalog. Returns JSON objects with **name**, **package**, **examples**, and **props** for each match (trimmed payload).
14
+
15
+ WHEN TO USE:
16
+ **Selecting which ADS component to use**—package name, examples, and props—before implementation. Use when composing a new view or swapping a primitive. Prefer \`ads_plan\` when you also need token and icon discovery in one shot.`,
15
17
  annotations: {
16
18
  title: 'Search ADS components',
17
19
  readOnlyHint: true,
@@ -21,9 +23,7 @@ export const listSearchComponentsTool = {
21
23
  },
22
24
  inputSchema: zodToJsonSchema(searchComponentsInputSchema)
23
25
  };
24
-
25
- // Clean component result to only return name, package name, example, and props
26
- const cleanComponentResult = result => {
26
+ const buildComponentResult = result => {
27
27
  return {
28
28
  name: result.name,
29
29
  package: result.package,
@@ -31,43 +31,24 @@ const cleanComponentResult = result => {
31
31
  props: result.props
32
32
  };
33
33
  };
34
- export const searchComponentsTool = async params => {
35
- const {
36
- terms,
37
- limit = 1,
38
- exactName = false
39
- } = params;
40
- const searchTerms = terms.filter(Boolean).map(cleanQuery);
34
+ export const searchComponentsTool = async ({
35
+ terms,
36
+ limit = 2
37
+ }) => {
38
+ const searchTerms = [...new Set(terms.filter(Boolean).map(cleanQuery))];
41
39
  if (!searchTerms.length) {
42
40
  return {
43
- isError: true,
44
41
  content: [{
45
42
  type: 'text',
46
- text: `Error: Required parameter 'terms' is missing or empty`
43
+ text: '[]'
47
44
  }]
48
45
  };
49
46
  }
50
47
  const components = loadAllComponents();
51
- if (exactName) {
52
- // for each search term, search for the exact match
53
- const exactNameMatches = searchTerms.map(term => {
54
- return components.find(component => component.name.toLowerCase() === term.toLowerCase());
55
- }).filter(Boolean);
56
- if (exactNameMatches.length > 0) {
57
- return {
58
- content: [{
59
- type: 'text',
60
- text: JSON.stringify(exactNameMatches.map(cleanComponentResult))
61
- }]
62
- };
63
- }
64
- }
65
-
66
- // use Fuse.js to fuzzy-search through the components
67
48
  const fuse = new Fuse(components, {
68
49
  keys: [{
69
50
  name: 'name',
70
- weight: 3
51
+ weight: 5
71
52
  }, {
72
53
  name: 'package',
73
54
  weight: 3
@@ -77,42 +58,47 @@ export const searchComponentsTool = async params => {
77
58
  }, {
78
59
  name: 'description',
79
60
  weight: 2
61
+ }, {
62
+ name: 'keywords',
63
+ weight: 2
64
+ }, {
65
+ name: 'usageGuidelines',
66
+ weight: 2
67
+ }, {
68
+ name: 'contentGuidelines',
69
+ weight: 1
70
+ }, {
71
+ name: 'accessibilityGuidelines',
72
+ weight: 1
80
73
  }, {
81
74
  name: 'examples',
82
75
  weight: 1
83
76
  }],
84
- threshold: 0.4
77
+ threshold: 0.4,
78
+ distance: 80,
79
+ minMatchCharLength: 3,
80
+ ignoreFieldNorm: true,
81
+ includeScore: true
85
82
  });
86
-
87
- // every search term, search for the results
88
- const results = searchTerms.map(term => {
89
- // always search exact match from the components first
90
- const exactNameMatch = components.find(component => component.name.toLowerCase() === term.toLowerCase());
91
- if (exactNameMatch) {
92
- return [{
93
- item: exactNameMatch
94
- }];
95
- }
96
- return fuse.search(term).slice(0, limit);
97
- }).flat();
98
- if (!results.length) {
83
+ const matchedItems = mergeMultiTermFuseResults({
84
+ searchTerms,
85
+ limit,
86
+ search: query => fuse.search(query, {
87
+ limit: limit * searchTerms.length
88
+ })
89
+ });
90
+ if (!matchedItems.length) {
99
91
  return {
100
- isError: true,
101
92
  content: [{
102
93
  type: 'text',
103
94
  text: `Error: No components found for '${terms.join(', ')}'. Available components: ${components.map(c => c.name).join(', ')}`
104
95
  }]
105
96
  };
106
97
  }
107
-
108
- // Remove duplicates based on component name
109
- const uniqueResults = results.filter((result, index, arr) => {
110
- return arr.findIndex(r => r.item.name === result.item.name) === index;
111
- });
112
98
  return {
113
99
  content: [{
114
100
  type: 'text',
115
- text: JSON.stringify(uniqueResults.map(result => result.item).map(cleanComponentResult))
101
+ text: JSON.stringify(matchedItems.map(buildComponentResult))
116
102
  }]
117
103
  };
118
104
  };
@@ -2,18 +2,25 @@
2
2
 
3
3
  import Fuse from 'fuse.js';
4
4
  import { z } from 'zod';
5
- import { cleanQuery, zodToJsonSchema } from '../../helpers';
5
+ import { cleanQuery, mergeMultiTermFuseResults, zodToJsonSchema } from '../../helpers';
6
6
  import { icons } from '../get-all-icons/icons';
7
7
  export const searchIconsInputSchema = z.object({
8
- terms: z.array(z.string()).describe('An array of search terms to find icons by name, keywords, or categorization, eg. `["search", "folder", "user"]`'),
9
- limit: z.number().default(1).describe('Maximum number of results per search term in the array (default: 1)').optional(),
10
- exactName: z.boolean().default(false).describe('Enable to explicitly search icons by the exact name match (when you know the name, but need more details)').optional()
8
+ terms: z.array(z.string()).describe('Required: one or more terms; fuzzy match on icon **componentName**, **iconName**, **keywords**, **categorization**, **type**, and **usage**. Example: `["search", "folder", "user"]`.'),
9
+ limit: z.number().default(2).describe('Max matches **per term** (default 2).').optional()
10
+ });
11
+ const buildIconResult = icon => ({
12
+ componentName: icon.componentName,
13
+ package: icon.package,
14
+ usage: icon.usage
11
15
  });
12
16
  export const listSearchIconsTool = {
13
17
  name: 'ads_search_icons',
14
- description: `Search for Atlassian Design System icons.
18
+ description: `Searches the bundled Atlassian Design System **icon** catalog. Returns JSON with **componentName**, **package**, and **usage** for each match.
19
+
20
+ WHEN TO USE:
21
+ **Choosing an icon** for a control, nav item, empty state, or illustration—find \`@atlaskit/icon\` import paths and usage notes. Prefer \`ads_plan\` when you also need tokens and components together.
15
22
 
16
- Example icon usage:
23
+ Example:
17
24
  \`\`\`tsx
18
25
  import AddIcon from '@atlaskit/icon/core/add';
19
26
  <AddIcon label="Add work item" size="small" />
@@ -27,42 +34,23 @@ import AddIcon from '@atlaskit/icon/core/add';
27
34
  },
28
35
  inputSchema: zodToJsonSchema(searchIconsInputSchema)
29
36
  };
30
- export const searchIconsTool = async params => {
31
- const {
32
- terms,
33
- limit = 1,
34
- exactName = false
35
- } = params;
36
- const searchTerms = terms.filter(Boolean).map(cleanQuery);
37
+ export const searchIconsTool = async ({
38
+ terms,
39
+ limit = 2
40
+ }) => {
41
+ const searchTerms = [...new Set(terms.filter(Boolean).map(cleanQuery))];
37
42
  if (!searchTerms.length) {
38
43
  return {
39
- isError: true,
40
44
  content: [{
41
45
  type: 'text',
42
- text: `Error: Required parameter 'terms' is missing or empty`
46
+ text: '[]'
43
47
  }]
44
48
  };
45
49
  }
46
- if (exactName) {
47
- // for each search term, search for the exact match
48
- const exactNameMatches = searchTerms.map(term => {
49
- return icons.find(icon => icon.componentName.toLowerCase() === term.toLowerCase());
50
- }).filter(Boolean);
51
- if (exactNameMatches.length > 0) {
52
- return {
53
- content: [{
54
- type: 'text',
55
- text: JSON.stringify(exactNameMatches)
56
- }]
57
- };
58
- }
59
- }
60
-
61
- // use Fuse.js to fuzzy-search through the icons
62
50
  const fuse = new Fuse(icons, {
63
51
  keys: [{
64
52
  name: 'componentName',
65
- weight: 3
53
+ weight: 5
66
54
  }, {
67
55
  name: 'iconName',
68
56
  weight: 3
@@ -71,53 +59,40 @@ export const searchIconsTool = async params => {
71
59
  weight: 2
72
60
  }, {
73
61
  name: 'categorization',
74
- weight: 1
62
+ weight: 2
75
63
  }, {
76
64
  name: 'type',
77
65
  weight: 1
78
66
  }, {
79
67
  name: 'usage',
80
- weight: 1
68
+ weight: 2
81
69
  }],
82
- threshold: 0.4
70
+ threshold: 0.4,
71
+ distance: 80,
72
+ minMatchCharLength: 3,
73
+ ignoreFieldNorm: true,
74
+ includeScore: true
83
75
  });
84
-
85
- // every search term, search for the results
86
- const results = searchTerms.map(term => {
87
- // always search exact match from the icons
88
- const exactNameMatch = icons.find(icon => icon.componentName.toLowerCase() === term.toLowerCase());
89
- if (exactNameMatch) {
90
- return [{
91
- item: exactNameMatch
92
- }];
93
- }
94
- return fuse.search(term).slice(0, limit);
95
- }).flat();
96
- if (!results.length) {
76
+ const matchedItems = mergeMultiTermFuseResults({
77
+ searchTerms,
78
+ limit,
79
+ search: query => fuse.search(query, {
80
+ limit: limit * searchTerms.length
81
+ }),
82
+ tokenKey: icon => icon.componentName
83
+ });
84
+ if (!matchedItems.length) {
97
85
  return {
98
- isError: true,
99
86
  content: [{
100
87
  type: 'text',
101
88
  text: `Error: No icons found for '${terms.join(', ')}'. Available icons: ${icons.map(i => i.componentName).join(', ')}`
102
89
  }]
103
90
  };
104
91
  }
105
-
106
- // Remove duplicates based on componentName
107
- const uniqueResults = results.filter((result, index, arr) => {
108
- return arr.findIndex(r => r.item.componentName === result.item.componentName) === index;
109
- });
110
- const matchedIcons = uniqueResults.map(result => {
111
- return {
112
- componentName: result.item.componentName,
113
- package: result.item.package,
114
- usage: result.item.usage
115
- };
116
- });
117
92
  return {
118
93
  content: [{
119
94
  type: 'text',
120
- text: JSON.stringify(matchedIcons)
95
+ text: JSON.stringify(matchedItems.map(buildIconResult))
121
96
  }]
122
97
  };
123
98
  };
@@ -3,17 +3,19 @@
3
3
  import Fuse from 'fuse.js';
4
4
  import { z } from 'zod';
5
5
  import { tokens } from '@atlaskit/tokens/token-metadata';
6
- import { cleanQuery, zodToJsonSchema } from '../../helpers';
6
+ import { cleanQuery, mergeMultiTermFuseResults, zodToJsonSchema } from '../../helpers';
7
7
  export const searchTokensInputSchema = z.object({
8
- terms: z.array(z.string()).describe('An array of search terms to find tokens by name or description, eg. `["spacing", "inverted text", "background primary"]`'),
9
- limit: z.number().default(1).describe('Maximum number of results per search term in the array (default: 1)').optional(),
10
- exactName: z.boolean().default(false).describe('Enable to explicitly search tokens by the exact name match (when you know the name, but need more details)').optional()
8
+ terms: z.array(z.string()).describe('Required: one or more terms; fuzzy match on token **name**, **description**, **exampleValue**, **usageGuidelines.usage**, and **usageGuidelines.cssProperties**. Example: `["spacing", "color.text", "background"]`.'),
9
+ limit: z.number().default(2).describe('Max matches **per term** (default 2).').optional()
11
10
  });
12
11
  export const listSearchTokensTool = {
13
12
  name: 'ads_search_tokens',
14
- description: `Search for Atlassian Design System tokens.
13
+ description: `Searches Atlassian Design System **design tokens** from bundled metadata. Returns JSON objects with **name** and **exampleValue** for each match (search also considers description, usage guidelines, and CSS property hints in metadata).
15
14
 
16
- Example token usage:
15
+ WHEN TO USE:
16
+ **Styling or theming in code**—you need the right \`token('…')\` names for colors, space, typography, etc. Use during layout and visual work when tokens must match ADS. Prefer \`ads_plan\` when you also need icons and components in the same step.
17
+
18
+ Example:
17
19
  \`\`\`tsx
18
20
  import { token } from '@atlaskit/tokens';
19
21
  const styles = css({ color: token('color.text'), padding: token('space.100') });
@@ -27,59 +29,66 @@ const styles = css({ color: token('color.text'), padding: token('space.100') });
27
29
  },
28
30
  inputSchema: zodToJsonSchema(searchTokensInputSchema)
29
31
  };
30
- export const searchTokensTool = async params => {
31
- const {
32
- terms,
33
- limit = 1,
34
- exactName = false
35
- } = params;
36
- const searchTerms = terms.filter(Boolean).map(cleanQuery);
37
- if (exactName) {
38
- // for each search term, search for the exact match
39
- const exactNameMatches = searchTerms.map(term => {
40
- return tokens.find(token => token.name.toLowerCase() === term.toLowerCase());
41
- }).filter(Boolean);
42
- if (exactNameMatches.length > 0) {
43
- return {
44
- content: [{
45
- type: 'text',
46
- text: JSON.stringify(exactNameMatches.map(token => ({
47
- name: token.name,
48
- exampleValue: token.exampleValue
49
- })))
50
- }]
51
- };
52
- }
32
+ export const searchTokensTool = async ({
33
+ terms,
34
+ limit = 2
35
+ }) => {
36
+ // Unique cleaned terms (order preserved) so duplicates don't concatenate into a bogus query.
37
+ const searchTerms = [...new Set(terms.filter(Boolean).map(cleanQuery))];
38
+ if (!searchTerms.length) {
39
+ return {
40
+ content: [{
41
+ type: 'text',
42
+ text: '[]'
43
+ }]
44
+ };
53
45
  }
54
-
55
- // use Fuse.js to fuzzy-search for the tokens
56
46
  const fuse = new Fuse(tokens, {
57
47
  keys: [{
58
48
  name: 'name',
59
- weight: 3
49
+ weight: 5
50
+ }, {
51
+ name: 'path',
52
+ weight: 2
60
53
  }, {
61
54
  name: 'description',
62
55
  weight: 2
56
+ }, {
57
+ name: 'usageGuidelines.usage',
58
+ weight: 2
59
+ }, {
60
+ name: 'usageGuidelines.cssProperties',
61
+ weight: 3
63
62
  }, {
64
63
  name: 'exampleValue',
65
- weight: 1
64
+ weight: 0.5
66
65
  }],
67
- threshold: 0.4
66
+ threshold: 0.4,
67
+ distance: 80,
68
+ minMatchCharLength: 3,
69
+ ignoreFieldNorm: true,
70
+ includeScore: true
68
71
  });
69
- const results = searchTerms.map(term => {
70
- return fuse.search(term).slice(0, limit);
71
- }).flat();
72
-
73
- // Remove duplicates based on token name
74
- const uniqueResults = results.filter((result, index, arr) => {
75
- return arr.findIndex(r => r.item.name === result.item.name) === index;
72
+ const matchedItems = mergeMultiTermFuseResults({
73
+ searchTerms,
74
+ limit,
75
+ search: query => fuse.search(query, {
76
+ limit: limit * searchTerms.length
77
+ }),
78
+ searchTermsJoin: '.'
76
79
  });
77
- const matchedTokens = uniqueResults.map(result => {
80
+ const matchedTokens = matchedItems.map(item => ({
81
+ name: item.name,
82
+ exampleValue: item.exampleValue
83
+ }));
84
+ if (!matchedTokens.length) {
78
85
  return {
79
- name: result.item.name,
80
- exampleValue: result.item.exampleValue
86
+ content: [{
87
+ type: 'text',
88
+ text: `Error: No tokens found for '${terms.join(', ')}'. Available tokens: ${tokens.map(t => t.name).join(', ')}`
89
+ }]
81
90
  };
82
- });
91
+ }
83
92
  return {
84
93
  content: [{
85
94
  type: 'text',
@@ -5,16 +5,25 @@ import { zodToJsonSchema } from '../../helpers';
5
5
  import { accessibilityFixes } from './fixes';
6
6
  import { violationKeywords } from './keywords';
7
7
  export const suggestA11yFixesInputSchema = z.object({
8
- violation: z.string().describe('Description of the accessibility violation'),
9
- code: z.string().describe('The problematic code that needs fixing'),
10
- component: z.string().optional().describe('Component name or type'),
11
- context: z.string().optional().describe('Additional context about the usage')
8
+ violation: z.string().describe('Human-readable description of the issue (e.g. axe rule help text, "button has no accessible name", "missing alt"). Used to match curated recipes when possible; otherwise the tool falls back to generic guidance.'),
9
+ code: z.string().describe('Snippet of the problematic code or markup so the response can be tailored (may be shown in the output for traceability).'),
10
+ component: z.string().optional().describe('Optional: React component name involved (e.g. `Button`, `TextField`)—may be ADS or app-specific.'),
11
+ context: z.string().optional().describe('Optional: where this appears (e.g. modal, table row, page section) or constraints.')
12
12
  });
13
13
  export const listSuggestA11yFixesTool = {
14
14
  name: 'ads_suggest_a11y_fixes',
15
- description: `Suggests specific accessibility fixes using Atlassian Design System components and patterns.`,
15
+ description: `Suggests remediation steps for an accessibility issue described in natural language (often pasted from **axe-core**, ESLint, or review).
16
+
17
+ WHAT YOU GET (varies by match):
18
+ - **Curated hit:** ADS-biased examples and patterns from this server’s recipe map (components, tokens, common fixes).
19
+ - **No strong match:** Generic guidance (e.g. “use ADS components”, labeling, testing)—still useful, but **not** guaranteed to be ADS-specific. May reference axe/atlassian.design resources.
20
+
21
+ WHEN TO USE:
22
+ After \`ads_analyze_a11y\` or \`ads_analyze_localhost_a11y\`, or whenever you have a violation string. For **topic-level** Atlassian Design System accessibility guidance, call \`ads_get_a11y_guidelines\` (this tool is fix-oriented, not a full guideline browse).
23
+
24
+ Does not replace manual testing with assistive technologies or keyboard-only navigation.`,
16
25
  annotations: {
17
- title: 'Suggest Accessibility Fixes',
26
+ title: 'Suggest accessibility fixes',
18
27
  readOnlyHint: true,
19
28
  destructiveHint: false,
20
29
  idempotentHint: true,
@@ -126,7 +135,7 @@ export const suggestA11yFixesTool = async params => {
126
135
  availableFixTypes: Object.keys(accessibilityFixes),
127
136
  suggestions: ['Try describing the issue with keywords like: button, label, missing, text, color, contrast, focus, etc.', 'Use axe-core violation IDs or descriptions directly', 'Be more specific about the element type (button, input, image, etc.)'],
128
137
  additionalResources: ['https://atlassian.design/llms-a11y.txt - Complete ADS accessibility documentation', 'https://atlassian.design/foundations/accessibility - ADS accessibility foundation'],
129
- recommendation: 'Try the get_a11y_guidelines tool for component-specific guidance'
138
+ recommendation: 'Try the ads_get_a11y_guidelines tool for component-specific guidance'
130
139
  }, null, 2)
131
140
  }]
132
141
  };