Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/eleven-buttons-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@storybook/addon-mcp': patch
'@storybook/mcp': patch
---

include prop types in component documentation tool
47 changes: 45 additions & 2 deletions .github/instructions/mcp.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ This is a Model Context Protocol (MCP) server for Storybook that serves knowledg
### Key Components

- **MCP Server**: Built using the `tmcp` library with HTTP transport
- **Tools System**: Extensible tool registration system (currently includes `list_all_components`)
- **Tools System**: Extensible tool registration system (includes `list_all_components` and `get_component_documentation`)
- **Component Manifest**: Parses and formats component documentation including React prop information from react-docgen
- **Schema Validation**: Uses Valibot for JSON schema validation via `@tmcp/adapter-valibot`
- **HTTP Transport**: Provides HTTP-based MCP communication via `@tmcp/transport-http`

Expand All @@ -24,7 +25,15 @@ src/
index.ts # Main entry point - exports createStorybookMcpHandler
serve.ts # Development server setup
tools/
list.ts # Tool definitions (e.g., list_all_components)
list-all-components.ts # List all components tool
get-component-documentation.ts # Get component documentation tool
utils/
format-manifest.ts # Format component manifest to XML
parse-react-docgen.ts # Parse react-docgen output
get-manifest.ts # Fetch and validate manifest
dedent.ts # Template string dedentation
error-to-mcp-content.test.ts # Error formatting utilities
types.ts # TypeScript types and Valibot schemas
```

### Key Design Patterns
Expand All @@ -33,6 +42,40 @@ src/
2. **Tool Registration**: Tools are added to the server using `server.tool()` method
3. **Async Handler**: Returns a Promise-based request handler compatible with standard HTTP servers

### Component Manifest and ReactDocgen Support

Component manifests can include a `reactDocgen` property containing prop information parsed by [react-docgen](https://github.com/reactjs/react-docgen). This library analyzes React components to extract prop types, descriptions, default values, and other metadata.

**How it works:**

1. **Input**: A component manifest may include a `reactDocgen` field containing the raw output from react-docgen's `Documentation` type
2. **Parsing**: The `parseReactDocgen()` utility in `src/utils/parse-react-docgen.ts` converts the react-docgen output into a simplified structure:
- Extracts prop names
- Serializes TypeScript types into readable strings (handles unions, intersections, functions, objects, etc.)
- Includes optional fields: `description`, `type`, `defaultValue`, `required`
3. **Formatting**: The `formatComponentManifest()` function in `src/utils/format-manifest.ts` generates an XML representation of the component including a `<props>` section when `reactDocgen` is present
4. **Output**: Each prop is formatted as:
```xml
<prop>
<prop_name>propName</prop_name>
<prop_type>string | number</prop_type>
<prop_required>false</prop_required>
<prop_default>"default"</prop_default>
<prop_description>
Prop description text
</prop_description>
</prop>
```

**Type serialization examples:**
- Unions: `"primary" | "secondary"`
- Functions: `(event: MouseEvent) => void`
- Objects: `{ name: string; age?: number }`
- Arrays: `string[]`
- Generics: `Promise<Data>`

All optional fields (`description`, `type`, `defaultValue`, `required`) are only included in the output when they have defined values.

## Development Workflow

### Prerequisites
Expand Down
81 changes: 55 additions & 26 deletions packages/mcp/fixtures/button.fixture.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,75 @@
"description": "A versatile button component that supports multiple variants, sizes, and states.\n\nThe Button component is a fundamental building block for user interactions. It can be styled as primary, secondary, or tertiary actions, and supports disabled and loading states.\n\n## Usage\n\nButtons should be used for actions that affect the current page or trigger operations. For navigation, consider using a Link component instead.",
"summary": "A versatile button component for user interactions",
"import": "import { Button } from '@storybook/design-system';",
"props": {
"type": "object",
"properties": {
"reactDocgen": {
"props": {
"variant": {
"type": "string",
"enum": ["primary", "secondary", "tertiary", "danger"],
"default": "primary",
"description": "The visual style variant of the button"
"description": "The visual style variant of the button",
"required": false,
"tsType": {
"name": "union",
"raw": "\"primary\" | \"secondary\" | \"tertiary\" | \"danger\"",
"elements": [
{ "name": "literal", "value": "\"primary\"" },
{ "name": "literal", "value": "\"secondary\"" },
{ "name": "literal", "value": "\"tertiary\"" },
{ "name": "literal", "value": "\"danger\"" }
]
},
"defaultValue": { "value": "\"primary\"", "computed": false }
},
"size": {
"type": "string",
"enum": ["small", "medium", "large"],
"default": "medium",
"description": "The size of the button"
"description": "The size of the button",
"required": false,
"tsType": {
"name": "union",
"raw": "\"small\" | \"medium\" | \"large\"",
"elements": [
{ "name": "literal", "value": "\"small\"" },
{ "name": "literal", "value": "\"medium\"" },
{ "name": "literal", "value": "\"large\"" }
]
},
"defaultValue": { "value": "\"medium\"", "computed": false }
},
"disabled": {
"type": "boolean",
"default": false,
"description": "Whether the button is disabled"
"description": "Whether the button is disabled",
"required": false,
"tsType": { "name": "boolean" },
"defaultValue": { "value": "false", "computed": false }
},
"loading": {
"type": "boolean",
"default": false,
"description": "Whether the button is in a loading state"
"description": "Whether the button is in a loading state",
"required": false,
"tsType": { "name": "boolean" },
"defaultValue": { "value": "false", "computed": false }
},
"fullWidth": {
"type": "boolean",
"default": false,
"description": "Whether the button should take up the full width of its container"
"description": "Whether the button should take up the full width of its container",
"required": false,
"tsType": { "name": "boolean" },
"defaultValue": { "value": "false", "computed": false }
},
"onClick": {
"type": "function",
"description": "Callback function when the button is clicked"
"description": "Callback function when the button is clicked",
"required": false,
"tsType": {
"name": "signature",
"type": "function",
"signature": {
"arguments": [
{ "name": "event", "type": { "name": "MouseEvent" } }
],
"return": { "name": "void" }
}
}
},
"children": {
"type": "string",
"description": "The content of the button"
"description": "The content of the button",
"required": true,
"tsType": { "name": "ReactNode" }
}
},
"required": ["children"]
}
},
"examples": [
{
Expand Down
77 changes: 53 additions & 24 deletions packages/mcp/fixtures/card.fixture.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,73 @@
"description": "A flexible container component for grouping related content with optional header, footer, and action areas.\n\nThe Card component provides a consistent way to present information in a contained, elevated surface. It's commonly used for displaying articles, products, user profiles, or any grouped content that benefits from visual separation.\n\n## Design Principles\n\n- Cards should contain a single subject or action\n- Maintain consistent padding and spacing\n- Use elevation to indicate interactive vs static cards\n- Keep content hierarchy clear with proper use of typography",
"summary": "A flexible container component for grouping related content",
"import": "import { Card } from '@storybook/design-system';",
"props": {
"type": "object",
"properties": {
"reactDocgen": {
"props": {
"variant": {
"type": "string",
"enum": ["elevated", "outlined", "flat"],
"default": "elevated",
"description": "The visual style variant of the card"
"description": "The visual style variant of the card",
"required": false,
"tsType": {
"name": "union",
"raw": "\"elevated\" | \"outlined\" | \"flat\"",
"elements": [
{ "name": "literal", "value": "\"elevated\"" },
{ "name": "literal", "value": "\"outlined\"" },
{ "name": "literal", "value": "\"flat\"" }
]
},
"defaultValue": { "value": "\"elevated\"", "computed": false }
},
"padding": {
"type": "string",
"enum": ["none", "small", "medium", "large"],
"default": "medium",
"description": "The amount of internal padding"
"description": "The amount of internal padding",
"required": false,
"tsType": {
"name": "union",
"raw": "\"none\" | \"small\" | \"medium\" | \"large\"",
"elements": [
{ "name": "literal", "value": "\"none\"" },
{ "name": "literal", "value": "\"small\"" },
{ "name": "literal", "value": "\"medium\"" },
{ "name": "literal", "value": "\"large\"" }
]
},
"defaultValue": { "value": "\"medium\"", "computed": false }
},
"clickable": {
"type": "boolean",
"default": false,
"description": "Whether the entire card is clickable/interactive"
"description": "Whether the entire card is clickable/interactive",
"required": false,
"tsType": { "name": "boolean" },
"defaultValue": { "value": "false", "computed": false }
},
"header": {
"type": "node",
"description": "Content to display in the card header"
"description": "Content to display in the card header",
"required": false,
"tsType": { "name": "ReactNode" }
},
"footer": {
"type": "node",
"description": "Content to display in the card footer"
"description": "Content to display in the card footer",
"required": false,
"tsType": { "name": "ReactNode" }
},
"children": {
"type": "node",
"description": "The main content of the card"
"description": "The main content of the card",
"required": true,
"tsType": { "name": "ReactNode" }
},
"onClick": {
"type": "function",
"description": "Callback function when the card is clicked (requires clickable=true)"
"description": "Callback function when the card is clicked (requires clickable=true)",
"required": false,
"tsType": {
"name": "signature",
"type": "function",
"signature": {
"arguments": [
{ "name": "event", "type": { "name": "MouseEvent" } }
],
"return": { "name": "void" }
}
}
}
},
"required": ["children"]
}
},
"examples": [
{
Expand Down
Loading
Loading