diff --git a/.changeset/spotty-buses-sip.md b/.changeset/spotty-buses-sip.md
new file mode 100644
index 0000000..ca240a3
--- /dev/null
+++ b/.changeset/spotty-buses-sip.md
@@ -0,0 +1,5 @@
+---
+'@storybook/mcp': patch
+---
+
+Support error.name in manifests
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index cc49e2f..f511fed 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -66,10 +66,11 @@ jobs:
uses: ./.github/actions/setup-node-and-install
- name: Run tests with coverage
- run: pnpm --filter @storybook/mcp test run --coverage --reporter=junit --outputFile=test-report.junit.xml
+ run: pnpm --filter @storybook/mcp test run --coverage --reporter=default --reporter=junit --outputFile=test-report.junit.xml
- name: Upload test and coverage artifact
uses: actions/upload-artifact@v4
+ if: always()
with:
name: test-mcp
path: |
@@ -90,9 +91,10 @@ jobs:
run: pnpm build --filter @storybook/mcp
- name: Run tests with coverage
- run: pnpm --filter @storybook/addon-mcp test run --coverage --reporter=junit --outputFile=test-report.junit.xml
+ run: pnpm --filter @storybook/addon-mcp test run --coverage --reporter=default --reporter=junit --outputFile=test-report.junit.xml
- name: Upload test and coverage artifact
+ if: always()
uses: actions/upload-artifact@v4
with:
name: test-addon-mcp
diff --git a/packages/mcp/fixtures/with-errors.fixture.json b/packages/mcp/fixtures/with-errors.fixture.json
new file mode 100644
index 0000000..b1a8da8
--- /dev/null
+++ b/packages/mcp/fixtures/with-errors.fixture.json
@@ -0,0 +1,163 @@
+{
+ "v": 1,
+ "components": {
+ "success-component-with-mixed-stories": {
+ "id": "success-component-with-mixed-stories",
+ "path": "src/components/SuccessWithMixedStories.tsx",
+ "name": "SuccessWithMixedStories",
+ "description": "A component that loaded successfully but has some stories that failed to generate.",
+ "summary": "Success component with both working and failing stories",
+ "import": "import { SuccessWithMixedStories } from '@storybook/design-system';",
+ "reactDocgen": {
+ "props": {
+ "text": {
+ "description": "The text to display",
+ "required": true,
+ "tsType": { "name": "string" }
+ },
+ "variant": {
+ "description": "The visual variant",
+ "required": false,
+ "tsType": {
+ "name": "union",
+ "raw": "\"primary\" | \"secondary\"",
+ "elements": [
+ { "name": "literal", "value": "\"primary\"" },
+ { "name": "literal", "value": "\"secondary\"" }
+ ]
+ },
+ "defaultValue": { "value": "\"primary\"", "computed": false }
+ }
+ }
+ },
+ "stories": [
+ {
+ "id": "success-component-with-mixed-stories--working",
+ "name": "Working",
+ "description": "This story generated successfully.",
+ "summary": "A working story",
+ "import": "import { SuccessWithMixedStories } from '@storybook/design-system';",
+ "snippet": "const Working = () => "
+ },
+ {
+ "id": "success-component-with-mixed-stories--failed",
+ "name": "Failed",
+ "error": {
+ "name": "SyntaxError",
+ "message": "Unexpected token in story code. Unable to generate code snippet."
+ }
+ }
+ ]
+ },
+ "error-component-with-success-stories": {
+ "id": "error-component-with-success-stories",
+ "path": "src/components/ErrorWithSuccessStories.tsx",
+ "name": "ErrorWithSuccessStories",
+ "error": {
+ "name": "TypeError",
+ "message": "Failed to parse component: Cannot read property 'name' of undefined in react-docgen parser"
+ },
+ "stories": [
+ {
+ "id": "error-component-with-success-stories--basic",
+ "name": "Basic",
+ "description": "Even though the component parsing failed, this story's code snippet was generated.",
+ "summary": "Basic usage story",
+ "snippet": "const Basic = () => Content"
+ },
+ {
+ "id": "error-component-with-success-stories--advanced",
+ "name": "Advanced",
+ "description": "Another successfully generated story despite component-level errors.",
+ "summary": "Advanced usage story",
+ "snippet": "const Advanced = () => (\n \n Advanced Content\n \n)"
+ }
+ ]
+ },
+ "error-component-with-error-stories": {
+ "id": "error-component-with-error-stories",
+ "path": "src/components/ErrorWithErrorStories.tsx",
+ "name": "ErrorWithErrorStories",
+ "error": {
+ "name": "Error",
+ "message": "Failed to extract component metadata: File not found or contains invalid TypeScript"
+ },
+ "stories": [
+ {
+ "id": "error-component-with-error-stories--broken-story-1",
+ "name": "BrokenStory1",
+ "description": "This story failed to generate.",
+ "error": {
+ "name": "Error",
+ "message": "Story render function is too complex to analyze"
+ }
+ },
+ {
+ "id": "error-component-with-error-stories--broken-story-2",
+ "name": "BrokenStory2",
+ "description": "This story also failed to generate.",
+ "error": {
+ "name": "ReferenceError",
+ "message": "Undefined variable referenced in story: missingImport"
+ }
+ }
+ ]
+ },
+ "complete-error-component": {
+ "id": "complete-error-component",
+ "path": "src/components/CompleteError.tsx",
+ "name": "CompleteError",
+ "error": {
+ "name": "ModuleNotFoundError",
+ "message": "Cannot find module './CompleteError' or its corresponding type declarations"
+ }
+ },
+ "partial-success": {
+ "id": "partial-success",
+ "path": "src/components/PartialSuccess.tsx",
+ "name": "PartialSuccess",
+ "description": "A component where everything worked except one story.",
+ "summary": "Mostly working component with one failing story",
+ "import": "import { PartialSuccess } from '@storybook/design-system';",
+ "reactDocgen": {
+ "props": {
+ "title": {
+ "description": "The title text",
+ "required": true,
+ "tsType": { "name": "string" }
+ },
+ "subtitle": {
+ "description": "Optional subtitle",
+ "required": false,
+ "tsType": { "name": "string" }
+ }
+ }
+ },
+ "stories": [
+ {
+ "id": "partial-success--default",
+ "name": "Default",
+ "description": "Default usage of the component.",
+ "import": "import { PartialSuccess } from '@storybook/design-system';",
+ "snippet": "const Default = () => "
+ },
+ {
+ "id": "partial-success--with-subtitle",
+ "name": "WithSubtitle",
+ "description": "Component with both title and subtitle.",
+ "import": "import { PartialSuccess } from '@storybook/design-system';",
+ "snippet": "const WithSubtitle = () => "
+ },
+ {
+ "id": "partial-success--complex-case",
+ "name": "ComplexCase",
+ "description": "A complex story that failed to generate.",
+ "error": {
+ "name": "Error",
+ "message": "Story uses hooks that cannot be statically analyzed"
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/packages/mcp/src/types.ts b/packages/mcp/src/types.ts
index d13998d..c36e2f6 100644
--- a/packages/mcp/src/types.ts
+++ b/packages/mcp/src/types.ts
@@ -45,6 +45,7 @@ const BaseManifest = v.object({
jsDocTags: v.optional(JSDocTag),
error: v.optional(
v.object({
+ name: v.string(),
message: v.string(),
}),
),
diff --git a/packages/mcp/src/utils/error-to-mcp-content.test.ts b/packages/mcp/src/utils/error-to-mcp-content.test.ts
index 4e190df..9d8bf7d 100644
--- a/packages/mcp/src/utils/error-to-mcp-content.test.ts
+++ b/packages/mcp/src/utils/error-to-mcp-content.test.ts
@@ -3,10 +3,7 @@ import { errorToMCPContent, ManifestGetError } from './get-manifest.ts';
describe('errorToMCPContent', () => {
it('should convert ManifestGetError to MCP error content', () => {
- const error = new ManifestGetError(
- 'Failed to get',
- 'https://example.com',
- );
+ const error = new ManifestGetError('Failed to get', 'https://example.com');
const result = errorToMCPContent(error);
diff --git a/packages/mcp/src/utils/format-manifest.test.ts b/packages/mcp/src/utils/format-manifest.test.ts
index 448d636..26319de 100644
--- a/packages/mcp/src/utils/format-manifest.test.ts
+++ b/packages/mcp/src/utils/format-manifest.test.ts
@@ -5,6 +5,7 @@ import {
} from './format-manifest';
import type { ComponentManifest, ComponentManifestMap } from '../types';
import fullManifestFixture from '../../fixtures/full-manifest.fixture.json' with { type: 'json' };
+import withErrorsFixture from '../../fixtures/with-errors.fixture.json' with { type: 'json' };
describe('formatComponentManifest', () => {
it('formats all full fixtures', () => {
@@ -874,4 +875,175 @@ describe('formatComponentManifestMapToList', () => {
`);
});
});
+
+ describe('with-errors fixture', () => {
+ it('should format success component with mixed stories (only successful ones)', () => {
+ const component =
+ withErrorsFixture.components['success-component-with-mixed-stories'];
+ const result = formatComponentManifest(component);
+ expect(result).toMatchInlineSnapshot(`
+ "
+ success-component-with-mixed-stories
+ SuccessWithMixedStories
+
+ A component that loaded successfully but has some stories that failed to generate.
+
+
+ Working
+
+ This story generated successfully.
+
+
+ import { SuccessWithMixedStories } from '@storybook/design-system';
+
+ const Working = () =>
+
+
+
+
+ text
+
+ The text to display
+
+ string
+ true
+
+
+ variant
+
+ The visual variant
+
+ "primary" | "secondary"
+ false
+ "primary"
+
+
+ "
+ `);
+ });
+
+ it('should format error component with success stories', () => {
+ const component =
+ withErrorsFixture.components['error-component-with-success-stories'];
+ const result = formatComponentManifest(component);
+ expect(result).toMatchInlineSnapshot(`
+ "
+ error-component-with-success-stories
+ ErrorWithSuccessStories
+
+ Basic
+
+ Even though the component parsing failed, this story's code snippet was generated.
+
+
+ const Basic = () => Content
+
+
+
+ Advanced
+
+ Another successfully generated story despite component-level errors.
+
+
+ const Advanced = () => (
+
+ Advanced Content
+
+ )
+
+
+ "
+ `);
+ });
+
+ it('should format partial success component (skips failed story)', () => {
+ const component = withErrorsFixture.components['partial-success'];
+ const result = formatComponentManifest(component);
+ expect(result).toMatchInlineSnapshot(`
+ "
+ partial-success
+ PartialSuccess
+
+ A component where everything worked except one story.
+
+
+ Default
+
+ Default usage of the component.
+
+
+ import { PartialSuccess } from '@storybook/design-system';
+
+ const Default = () =>
+
+
+
+ With Subtitle
+
+ Component with both title and subtitle.
+
+
+ import { PartialSuccess } from '@storybook/design-system';
+
+ const WithSubtitle = () =>
+
+
+
+
+ title
+
+ The title text
+
+ string
+ true
+
+
+ subtitle
+
+ Optional subtitle
+
+ string
+ false
+
+
+ "
+ `);
+ });
+
+ it('should format list of components with errors', () => {
+ const result = formatComponentManifestMapToList(
+ withErrorsFixture as ComponentManifestMap,
+ );
+ expect(result).toMatchInlineSnapshot(`
+ "
+
+ success-component-with-mixed-stories
+ SuccessWithMixedStories
+
+ Success component with both working and failing stories
+
+
+
+ error-component-with-success-stories
+ ErrorWithSuccessStories
+
+
+ error-component-with-error-stories
+ ErrorWithErrorStories
+
+
+ complete-error-component
+ CompleteError
+
+
+ partial-success
+ PartialSuccess
+
+ Mostly working component with one failing story
+
+
+ "
+ `);
+ });
+ });
});