From 02c1271478f2df90ed70fe04f2726305bbfae3a0 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Tue, 5 May 2026 13:50:44 -0400 Subject: [PATCH 01/15] design-system-mcp: Add support for fetching multiple component details at once --- packages/design-system-mcp/CHANGELOG.md | 4 ++ .../src/tools/get-component-details.ts | 40 +++++++++++++++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/packages/design-system-mcp/CHANGELOG.md b/packages/design-system-mcp/CHANGELOG.md index ed0c2fd5f25e95..f6704efe83bf4e 100644 --- a/packages/design-system-mcp/CHANGELOG.md +++ b/packages/design-system-mcp/CHANGELOG.md @@ -7,3 +7,7 @@ ## 0.2.0 (2026-04-29) - Initial release. + +### Enhancements + +- `get_component_details` now optionally accepts an array of component names so multiple components can be fetched in a single call. diff --git a/packages/design-system-mcp/src/tools/get-component-details.ts b/packages/design-system-mcp/src/tools/get-component-details.ts index 4aaa5633abb50f..d20439d544bd52 100644 --- a/packages/design-system-mcp/src/tools/get-component-details.ts +++ b/packages/design-system-mcp/src/tools/get-component-details.ts @@ -14,35 +14,59 @@ export function register( server: McpServer ): void { { title: 'Get Component Details', description: - 'Get detailed documentation for a WordPress Design System component including props, usage examples, and import statements.', + 'Get detailed documentation for one or more WordPress Design System components including props, usage examples, and import statements. Pass multiple names to fetch several components in a single call instead of making repeated calls.', inputSchema: z.object( { name: z - .string() - .min( 1 ) - .describe( 'The component name (e.g. "Button", "Tabs")' ), + .union( [ + z.string().min( 1 ), + z.array( z.string().min( 1 ) ).min( 1 ), + ] ) + .describe( + 'A component name, or an array of component names to fetch in a single call (e.g. "Button" or ["Button", "Tabs"]).' + ), } ), annotations: { readOnlyHint: true, }, }, async ( { name } ) => { - const detail = await getComponentDetail( name ); - if ( ! detail ) { + const names = Array.isArray( name ) ? name : [ name ]; + const sections: string[] = []; + const missing: string[] = []; + + for ( const componentName of names ) { + const detail = await getComponentDetail( componentName ); + if ( detail ) { + sections.push( formatComponentDetail( detail ) ); + } else { + missing.push( componentName ); + } + } + + if ( sections.length === 0 ) { + const list = missing.map( ( n ) => `"${ n }"` ).join( ', ' ); return { content: [ { type: 'text', - text: `No component named "${ name }" was found.`, + text: `No components were found for: ${ list }.`, }, ], isError: true, }; } + + let text = sections.join( '\n\n---\n\n' ); + if ( missing.length > 0 ) { + const list = missing.map( ( n ) => `"${ n }"` ).join( ', ' ); + text += `\n\n---\n\n_No components were found for: ${ list }._`; + } + return { content: [ { type: 'text', - text: formatComponentDetail( detail ), + text, }, ], }; From e23c69f4aa2900e63cd6b5c03f8926e9d6e8ab95 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Mon, 11 May 2026 16:21:51 -0400 Subject: [PATCH 02/15] design-system-mcp: Add test coverage for get_details response --- .../src/tools/get-component-details.ts | 108 +++++++-------- .../src/tools/test/get-component-details.ts | 126 ++++++++++++++++++ 2 files changed, 182 insertions(+), 52 deletions(-) create mode 100644 packages/design-system-mcp/src/tools/test/get-component-details.ts diff --git a/packages/design-system-mcp/src/tools/get-component-details.ts b/packages/design-system-mcp/src/tools/get-component-details.ts index d20439d544bd52..ac6a4e929d9ac0 100644 --- a/packages/design-system-mcp/src/tools/get-component-details.ts +++ b/packages/design-system-mcp/src/tools/get-component-details.ts @@ -3,6 +3,60 @@ import { z } from 'zod'; import { getComponentDetail } from '../data'; import { formatComponentDetail } from '../format'; +const inputSchema = z.object( { + name: z + .union( [ + z.string().min( 1 ), + z.array( z.string().min( 1 ) ).min( 1 ), + ] ) + .describe( + 'A component name, or an array of component names to fetch in a single call (e.g. "Button" or ["Button", "Tabs"]).' + ), +} ); + +export async function handler( { name }: z.infer< typeof inputSchema > ) { + const names = Array.isArray( name ) ? name : [ name ]; + const sections: string[] = []; + const missing: string[] = []; + + for ( const componentName of names ) { + const detail = await getComponentDetail( componentName ); + if ( detail ) { + sections.push( formatComponentDetail( detail ) ); + } else { + missing.push( componentName ); + } + } + + if ( sections.length === 0 ) { + const list = missing.map( ( n ) => `"${ n }"` ).join( ', ' ); + return { + content: [ + { + type: 'text' as const, + text: `No components were found for: ${ list }.`, + }, + ], + isError: true, + }; + } + + let text = sections.join( '\n\n---\n\n' ); + if ( missing.length > 0 ) { + const list = missing.map( ( n ) => `"${ n }"` ).join( ', ' ); + text += `\n\n---\n\n_No components were found for: ${ list }._`; + } + + return { + content: [ + { + type: 'text' as const, + text, + }, + ], + }; +} + /** * Register the get_component_details tool. * @@ -15,61 +69,11 @@ export function register( server: McpServer ): void { title: 'Get Component Details', description: 'Get detailed documentation for one or more WordPress Design System components including props, usage examples, and import statements. Pass multiple names to fetch several components in a single call instead of making repeated calls.', - inputSchema: z.object( { - name: z - .union( [ - z.string().min( 1 ), - z.array( z.string().min( 1 ) ).min( 1 ), - ] ) - .describe( - 'A component name, or an array of component names to fetch in a single call (e.g. "Button" or ["Button", "Tabs"]).' - ), - } ), + inputSchema, annotations: { readOnlyHint: true, }, }, - async ( { name } ) => { - const names = Array.isArray( name ) ? name : [ name ]; - const sections: string[] = []; - const missing: string[] = []; - - for ( const componentName of names ) { - const detail = await getComponentDetail( componentName ); - if ( detail ) { - sections.push( formatComponentDetail( detail ) ); - } else { - missing.push( componentName ); - } - } - - if ( sections.length === 0 ) { - const list = missing.map( ( n ) => `"${ n }"` ).join( ', ' ); - return { - content: [ - { - type: 'text', - text: `No components were found for: ${ list }.`, - }, - ], - isError: true, - }; - } - - let text = sections.join( '\n\n---\n\n' ); - if ( missing.length > 0 ) { - const list = missing.map( ( n ) => `"${ n }"` ).join( ', ' ); - text += `\n\n---\n\n_No components were found for: ${ list }._`; - } - - return { - content: [ - { - type: 'text', - text, - }, - ], - }; - } + handler ); } diff --git a/packages/design-system-mcp/src/tools/test/get-component-details.ts b/packages/design-system-mcp/src/tools/test/get-component-details.ts new file mode 100644 index 00000000000000..c649281b893d91 --- /dev/null +++ b/packages/design-system-mcp/src/tools/test/get-component-details.ts @@ -0,0 +1,126 @@ +import { handler } from '../get-component-details'; +import { getComponentDetail } from '../../data'; +import { formatComponentDetail } from '../../format'; +import type { ComponentDetail } from '../../types'; + +jest.mock( '../../data' ); + +const mockGetComponentDetail = getComponentDetail as jest.MockedFunction< + typeof getComponentDetail +>; + +function fakeDetail( name: string ): ComponentDetail { + return { + name, + description: `${ name } description.`, + packageName: '@wordpress/ui', + importStatement: `import { ${ name } } from '@wordpress/ui';`, + props: [], + stories: [], + }; +} + +describe( 'handler', () => { + beforeEach( () => { + mockGetComponentDetail.mockReset(); + } ); + + it( 'returns a single formatted section for a string name', async () => { + const button = fakeDetail( 'Button' ); + mockGetComponentDetail.mockResolvedValueOnce( button ); + + const result = await handler( { name: 'Button' } ); + + expect( mockGetComponentDetail ).toHaveBeenCalledTimes( 1 ); + expect( mockGetComponentDetail ).toHaveBeenCalledWith( 'Button' ); + expect( result ).toEqual( { + content: [ + { type: 'text', text: formatComponentDetail( button ) }, + ], + } ); + } ); + + it( 'joins multiple components with the section separator', async () => { + const button = fakeDetail( 'Button' ); + const tabs = fakeDetail( 'Tabs' ); + mockGetComponentDetail + .mockResolvedValueOnce( button ) + .mockResolvedValueOnce( tabs ); + + const result = await handler( { name: [ 'Button', 'Tabs' ] } ); + + expect( mockGetComponentDetail.mock.calls ).toEqual( [ + [ 'Button' ], + [ 'Tabs' ], + ] ); + expect( result ).toEqual( { + content: [ + { + type: 'text', + text: `${ formatComponentDetail( + button + ) }\n\n---\n\n${ formatComponentDetail( tabs ) }`, + }, + ], + } ); + } ); + + it( 'appends a missing footer when some names are not found', async () => { + const button = fakeDetail( 'Button' ); + mockGetComponentDetail + .mockResolvedValueOnce( button ) + .mockResolvedValueOnce( undefined ); + + const result = await handler( { name: [ 'Button', 'Nope' ] } ); + + expect( result ).toEqual( { + content: [ + { + type: 'text', + text: `${ formatComponentDetail( + button + ) }\n\n---\n\n_No components were found for: "Nope"._`, + }, + ], + } ); + } ); + + it( 'quotes and comma-joins multiple missing names in the footer', async () => { + const button = fakeDetail( 'Button' ); + mockGetComponentDetail + .mockResolvedValueOnce( button ) + .mockResolvedValueOnce( undefined ) + .mockResolvedValueOnce( undefined ); + + const result = await handler( { + name: [ 'Button', 'Nope', 'AlsoNope' ], + } ); + + expect( result ).toEqual( { + content: [ + { + type: 'text', + text: `${ formatComponentDetail( + button + ) }\n\n---\n\n_No components were found for: "Nope", "AlsoNope"._`, + }, + ], + } ); + } ); + + it( 'returns isError when no components are found', async () => { + mockGetComponentDetail.mockResolvedValue( undefined ); + + const result = await handler( { name: [ 'Foo', 'Bar' ] } ); + + expect( result ).toEqual( { + content: [ + { + type: 'text', + text: 'No components were found for: "Foo", "Bar".', + }, + ], + isError: true, + } ); + } ); +} ); From b15e25516c6449be398b2a1917257b1c9367bae2 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Mon, 11 May 2026 16:31:11 -0400 Subject: [PATCH 03/15] design-system-mcp: Fix types on mocked component details --- .../src/tools/test/get-component-details.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/design-system-mcp/src/tools/test/get-component-details.ts b/packages/design-system-mcp/src/tools/test/get-component-details.ts index c649281b893d91..41daa77a5d961d 100644 --- a/packages/design-system-mcp/src/tools/test/get-component-details.ts +++ b/packages/design-system-mcp/src/tools/test/get-component-details.ts @@ -69,7 +69,7 @@ describe( 'handler', () => { const button = fakeDetail( 'Button' ); mockGetComponentDetail .mockResolvedValueOnce( button ) - .mockResolvedValueOnce( undefined ); + .mockResolvedValueOnce( null ); const result = await handler( { name: [ 'Button', 'Nope' ] } ); @@ -89,8 +89,8 @@ describe( 'handler', () => { const button = fakeDetail( 'Button' ); mockGetComponentDetail .mockResolvedValueOnce( button ) - .mockResolvedValueOnce( undefined ) - .mockResolvedValueOnce( undefined ); + .mockResolvedValueOnce( null ) + .mockResolvedValueOnce( null ); const result = await handler( { name: [ 'Button', 'Nope', 'AlsoNope' ], @@ -109,7 +109,7 @@ describe( 'handler', () => { } ); it( 'returns isError when no components are found', async () => { - mockGetComponentDetail.mockResolvedValue( undefined ); + mockGetComponentDetail.mockResolvedValue( null ); const result = await handler( { name: [ 'Foo', 'Bar' ] } ); From d97a8c7d19667e89cf91b16e2001438effbc6e1e Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Mon, 11 May 2026 17:02:26 -0400 Subject: [PATCH 04/15] Add PR link to CHANGELOG --- packages/design-system-mcp/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/design-system-mcp/CHANGELOG.md b/packages/design-system-mcp/CHANGELOG.md index f6704efe83bf4e..27b857b7470034 100644 --- a/packages/design-system-mcp/CHANGELOG.md +++ b/packages/design-system-mcp/CHANGELOG.md @@ -10,4 +10,4 @@ ### Enhancements -- `get_component_details` now optionally accepts an array of component names so multiple components can be fetched in a single call. +- `get_component_details` now optionally accepts an array of component names so multiple components can be fetched in a single call. ([#78185](https://github.com/WordPress/gutenberg/pull/78185)) From 30ca8b19a8000d231c4251b29f02b64caf08943d Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 14 May 2026 10:08:10 -0400 Subject: [PATCH 05/15] Extend Storybook internal manifest type for component manifest May help catch future issues if the shape changes in future updates (i.e. TypeScript will start failing) --- packages/design-system-mcp/package.json | 3 ++- packages/design-system-mcp/src/types.ts | 13 +++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/design-system-mcp/package.json b/packages/design-system-mcp/package.json index 3dba5ea1adef0d..018cc020a14125 100644 --- a/packages/design-system-mcp/package.json +++ b/packages/design-system-mcp/package.json @@ -46,7 +46,8 @@ "zod": "4.3.6" }, "devDependencies": { - "@types/jest": "^29.5.14" + "@types/jest": "^29.5.14", + "storybook": "^10.2.8" }, "publishConfig": { "access": "public" diff --git a/packages/design-system-mcp/src/types.ts b/packages/design-system-mcp/src/types.ts index d89cb8bb1ba397..9f9668768f8125 100644 --- a/packages/design-system-mcp/src/types.ts +++ b/packages/design-system-mcp/src/types.ts @@ -1,13 +1,6 @@ -export interface ManifestComponent { - id: string; - name: string; - path: string; - description?: string; - stories?: Array< { - name: string; - snippet?: string; - description?: string; - } >; +import type { ComponentManifest } from 'storybook/internal/types'; + +export interface ManifestComponent extends ComponentManifest { reactDocgen?: { description?: string; displayName?: string; From 484a92790ceaa99c17e82ef7cf0530900d0f8bc4 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 14 May 2026 10:18:54 -0400 Subject: [PATCH 06/15] design-system-mcp: Add validation to check manifest shape --- .github/workflows/storybook-check.yml | 3 ++ .../bin/validate-manifest.mjs | 29 +++++++++++++++++++ packages/design-system-mcp/src/index.ts | 2 ++ 3 files changed, 34 insertions(+) create mode 100755 packages/design-system-mcp/bin/validate-manifest.mjs diff --git a/.github/workflows/storybook-check.yml b/.github/workflows/storybook-check.yml index 191f06cd2a4b84..ddd20d0b890a1f 100644 --- a/.github/workflows/storybook-check.yml +++ b/.github/workflows/storybook-check.yml @@ -37,6 +37,9 @@ jobs: NODE_ENV: test run: npm run storybook:build + - name: Validate manifest matches design-system-mcp contract + run: node packages/design-system-mcp/scripts/validate-manifest.mjs + - name: Build E2E Storybook env: NODE_ENV: test diff --git a/packages/design-system-mcp/bin/validate-manifest.mjs b/packages/design-system-mcp/bin/validate-manifest.mjs new file mode 100755 index 00000000000000..dd110e9b2f0e2a --- /dev/null +++ b/packages/design-system-mcp/bin/validate-manifest.mjs @@ -0,0 +1,29 @@ +#!/usr/bin/env node +import { readFile } from 'node:fs/promises'; +import { + parseComponents, + parseComponentDetail, +} from '@wordpress/design-system-mcp'; + +const path = process.argv[ 2 ] ?? 'storybook/build/manifests/components.json'; +const manifest = JSON.parse( await readFile( path, 'utf8' ) ); + +const components = parseComponents( manifest.components ); +if ( components.length === 0 ) { + console.error( + `No components parsed from ${ path }. Manifest shape may have changed.` + ); + process.exit( 1 ); +} + +const component = parseComponentDetail( manifest.components, 'Badge' ); +if ( ! component || component.props.length === 0 ) { + console.error( + `Sample component (Badge) has no props. Manifest shape may have changed.` + ); + process.exit( 1 ); +} + +console.log( + `Successfully validated manifest with ${ components.length } components.` +); diff --git a/packages/design-system-mcp/src/index.ts b/packages/design-system-mcp/src/index.ts index d7747f36e590ab..11f14636d2abb6 100644 --- a/packages/design-system-mcp/src/index.ts +++ b/packages/design-system-mcp/src/index.ts @@ -1,6 +1,8 @@ import { McpServer } from '@modelcontextprotocol/server'; import { registerTools } from './tools/index'; +export { parseComponents, parseComponentDetail } from './parse-components'; + export function createServer() { const server = new McpServer( { name: 'WordPress Design System', From 9fcbb058eea064d5ead9f446cebc33c1730843b1 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 14 May 2026 10:48:22 -0400 Subject: [PATCH 07/15] Use correct path for MCP manifest validation script --- .github/workflows/storybook-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/storybook-check.yml b/.github/workflows/storybook-check.yml index ddd20d0b890a1f..b76fb0d0ee989f 100644 --- a/.github/workflows/storybook-check.yml +++ b/.github/workflows/storybook-check.yml @@ -38,7 +38,7 @@ jobs: run: npm run storybook:build - name: Validate manifest matches design-system-mcp contract - run: node packages/design-system-mcp/scripts/validate-manifest.mjs + run: node packages/design-system-mcp/bin/validate-manifest.mjs - name: Build E2E Storybook env: From bd0831095c2aa9cd419f1b18187cfa536865b663 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 14 May 2026 10:49:09 -0400 Subject: [PATCH 08/15] Add required properties for complete manifest type --- packages/design-system-mcp/src/test/parse-components.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/design-system-mcp/src/test/parse-components.ts b/packages/design-system-mcp/src/test/parse-components.ts index 04dcc2ada1334b..728550afc44061 100644 --- a/packages/design-system-mcp/src/test/parse-components.ts +++ b/packages/design-system-mcp/src/test/parse-components.ts @@ -153,6 +153,8 @@ function createComponents( path: value.path ?? `../packages/ui/src/${ key }/stories/index.story.tsx`, + stories: [], + jsDocTags: {}, ...value, }; } From c59f6f5b57842258b1406a60084fcf34b897ae42 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 14 May 2026 10:50:08 -0400 Subject: [PATCH 09/15] Move CHANGELOG note under Unreleased --- packages/design-system-mcp/CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/design-system-mcp/CHANGELOG.md b/packages/design-system-mcp/CHANGELOG.md index 27b857b7470034..eec5c5540d8b25 100644 --- a/packages/design-system-mcp/CHANGELOG.md +++ b/packages/design-system-mcp/CHANGELOG.md @@ -2,12 +2,16 @@ ## Unreleased +### Enhancements + +- `get_component_details` now optionally accepts an array of component names so multiple components can be fetched in a single call. ([#78185](https://github.com/WordPress/gutenberg/pull/78185)) + ## 0.3.0 (2026-05-14) ## 0.2.0 (2026-04-29) - Initial release. -### Enhancements +## 0.2.0 (2026-04-29) -- `get_component_details` now optionally accepts an array of component names so multiple components can be fetched in a single call. ([#78185](https://github.com/WordPress/gutenberg/pull/78185)) +- Initial release. From 47fabb5a87fec43208c18949111b3236e1963229 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 14 May 2026 12:58:56 -0400 Subject: [PATCH 10/15] Sync lockfile --- package-lock.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 154a657fa3c985..440df02b7068c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61002,7 +61002,8 @@ "design-system-mcp": "bin/design-system-mcp.mjs" }, "devDependencies": { - "@types/jest": "^29.5.14" + "@types/jest": "^29.5.14", + "storybook": "^10.2.8" }, "engines": { "node": ">=20.10.0", From b4c7534c0949f1b64c49c729a27e384bbaa93933 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 14 May 2026 12:59:19 -0400 Subject: [PATCH 11/15] Storybook: Generate components manifest in test env Because we're now testing the generated manifest --- storybook/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storybook/main.ts b/storybook/main.ts index f50442c2d9e74f..ae886c7d141c41 100644 --- a/storybook/main.ts +++ b/storybook/main.ts @@ -57,7 +57,7 @@ const config: StorybookConfig = { ], framework: '@storybook/react-vite', features: { - experimentalComponentsManifest: NODE_ENV === 'production', + experimentalComponentsManifest: NODE_ENV !== 'development', }, typescript: { reactDocgen: 'react-docgen-typescript', From c5fd61f63db66b9ad68c6afb28f32e1c0bb0a869 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 14 May 2026 14:18:33 -0400 Subject: [PATCH 12/15] CI: Run Storybook check on trunk pushes --- .github/workflows/storybook-check.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/storybook-check.yml b/.github/workflows/storybook-check.yml index b76fb0d0ee989f..184442420dfe6f 100644 --- a/.github/workflows/storybook-check.yml +++ b/.github/workflows/storybook-check.yml @@ -1,6 +1,10 @@ name: Storybook build and Smoke Tests -on: pull_request +on: + pull_request: + push: + branches: + - trunk # Cancels all previous workflow runs for pull requests that have not completed. concurrency: From 26b98f00597b9fa1e515badd80e341667e785807 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 14 May 2026 14:19:22 -0400 Subject: [PATCH 13/15] design-system-mcp: Move manifest validation to scripts directory Exclude from published build --- .github/workflows/storybook-check.yml | 2 +- .../design-system-mcp/{bin => scripts}/validate-manifest.mjs | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/design-system-mcp/{bin => scripts}/validate-manifest.mjs (100%) diff --git a/.github/workflows/storybook-check.yml b/.github/workflows/storybook-check.yml index 184442420dfe6f..bbab59f1d39743 100644 --- a/.github/workflows/storybook-check.yml +++ b/.github/workflows/storybook-check.yml @@ -42,7 +42,7 @@ jobs: run: npm run storybook:build - name: Validate manifest matches design-system-mcp contract - run: node packages/design-system-mcp/bin/validate-manifest.mjs + run: node packages/design-system-mcp/scripts/validate-manifest.mjs - name: Build E2E Storybook env: diff --git a/packages/design-system-mcp/bin/validate-manifest.mjs b/packages/design-system-mcp/scripts/validate-manifest.mjs similarity index 100% rename from packages/design-system-mcp/bin/validate-manifest.mjs rename to packages/design-system-mcp/scripts/validate-manifest.mjs From 3c016f197bf88f5d8866f5db5238b301f97df996 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 14 May 2026 14:31:27 -0400 Subject: [PATCH 14/15] Set maximum of 10 component detail fetches Avoid running into issues where agent clients won't process large tool responses. 10 components should be sufficient for most usage and fall within most client limitations. --- packages/design-system-mcp/src/tools/get-component-details.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/design-system-mcp/src/tools/get-component-details.ts b/packages/design-system-mcp/src/tools/get-component-details.ts index ac6a4e929d9ac0..d0ca75ded10e0f 100644 --- a/packages/design-system-mcp/src/tools/get-component-details.ts +++ b/packages/design-system-mcp/src/tools/get-component-details.ts @@ -7,7 +7,7 @@ const inputSchema = z.object( { name: z .union( [ z.string().min( 1 ), - z.array( z.string().min( 1 ) ).min( 1 ), + z.array( z.string().min( 1 ) ).min( 1 ).max( 10 ), ] ) .describe( 'A component name, or an array of component names to fetch in a single call (e.g. "Button" or ["Button", "Tabs"]).' From d851ecb8c9eb71879a8bd51db9e325e181b77592 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 14 May 2026 14:37:30 -0400 Subject: [PATCH 15/15] Assert any parseable component props Avoid hard-coding specific component name. We're primarily interested that we didn't lose the ability to parse props across components. --- .../scripts/validate-manifest.mjs | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/packages/design-system-mcp/scripts/validate-manifest.mjs b/packages/design-system-mcp/scripts/validate-manifest.mjs index dd110e9b2f0e2a..6ea1c524da4d45 100755 --- a/packages/design-system-mcp/scripts/validate-manifest.mjs +++ b/packages/design-system-mcp/scripts/validate-manifest.mjs @@ -1,29 +1,23 @@ #!/usr/bin/env node import { readFile } from 'node:fs/promises'; +import assert from 'node:assert'; import { parseComponents, parseComponentDetail, } from '@wordpress/design-system-mcp'; const path = process.argv[ 2 ] ?? 'storybook/build/manifests/components.json'; -const manifest = JSON.parse( await readFile( path, 'utf8' ) ); +const { components } = JSON.parse( await readFile( path, 'utf8' ) ); +const names = parseComponents( components ).map( ( { name } ) => name ); -const components = parseComponents( manifest.components ); -if ( components.length === 0 ) { - console.error( - `No components parsed from ${ path }. Manifest shape may have changed.` - ); - process.exit( 1 ); -} - -const component = parseComponentDetail( manifest.components, 'Badge' ); -if ( ! component || component.props.length === 0 ) { - console.error( - `Sample component (Badge) has no props. Manifest shape may have changed.` - ); - process.exit( 1 ); -} +assert( + names.length > 0, + `No components parsed from ${ path }. Manifest shape may have changed.` +); -console.log( - `Successfully validated manifest with ${ components.length } components.` +assert( + names.some( + ( name ) => parseComponentDetail( components, name )?.props.length > 0 + ), + `No components have parsed props. Manifest shape may have changed.` );