|
| 1 | +# Copilot Instructions for Storybook MCP Addon |
| 2 | + |
| 3 | +## Architecture Overview |
| 4 | + |
| 5 | +This is a **pnpm monorepo** with two distinct MCP implementations: |
| 6 | + |
| 7 | +- **`packages/addon-mcp`**: Storybook addon using `@modelcontextprotocol/sdk`, exposes MCP server at `/mcp` via Vite middleware |
| 8 | +- **`packages/mcp`**: Standalone MCP library using `tmcp`, reusable outside Storybook |
| 9 | +- **`apps/internal-storybook`**: Test environment for addon integration |
| 10 | + |
| 11 | +**Critical distinction**: The two packages use **different MCP libraries** (`@modelcontextprotocol/sdk` vs `tmcp`). Don't confuse their APIs or patterns. |
| 12 | + |
| 13 | +### Addon Architecture |
| 14 | + |
| 15 | +The addon uses a **Vite plugin workaround** to inject middleware (see `packages/addon-mcp/src/preset.ts`): |
| 16 | + |
| 17 | +- Storybook doesn't expose an API for addons to register server middleware |
| 18 | +- Solution: Inject a Vite plugin via `viteFinal` that adds `/mcp` endpoint |
| 19 | +- Handler in `mcp-handler.ts` creates session-based MCP servers using `StreamableHTTPServerTransport` |
| 20 | + |
| 21 | +### MCP Library Architecture |
| 22 | + |
| 23 | +The `@storybook/mcp` package (in `packages/mcp`) is framework-agnostic: |
| 24 | + |
| 25 | +- Uses `tmcp` with HTTP transport and Valibot schema validation |
| 26 | +- Factory pattern: `createStorybookMcpHandler()` returns a request handler |
| 27 | +- Context-based: handlers accept `StorybookContext` to override source URLs |
| 28 | + |
| 29 | +## Development Environment |
| 30 | + |
| 31 | +**Prerequisites:** |
| 32 | + |
| 33 | +- Node.js **24+** (enforced by `.nvmrc`) |
| 34 | +- pnpm **10.19.0+** (strict `packageManager` in root `package.json`) |
| 35 | + |
| 36 | +**Monorepo orchestration:** |
| 37 | + |
| 38 | +- Turborepo manages build dependencies (see `turbo.json`) |
| 39 | +- Run `pnpm dev` at root for parallel development |
| 40 | +- Run `pnpm storybook` to test addon (starts internal-storybook + addon dev mode) |
| 41 | + |
| 42 | +**Build tools differ by package:** |
| 43 | + |
| 44 | +- `packages/mcp`: Uses `tsdown` (rolldown-based, faster builds) |
| 45 | +- `packages/addon-mcp`: Uses `tsup` (esbuild-based) |
| 46 | + |
| 47 | +**Testing:** |
| 48 | + |
| 49 | +- Only `packages/mcp` has tests (Vitest with coverage) |
| 50 | +- Run `pnpm test run --coverage` in mcp package |
| 51 | +- Prefer TDD when adding new tools |
| 52 | + |
| 53 | +**Debugging MCP servers:** |
| 54 | + |
| 55 | +```bash |
| 56 | +pnpm inspect # Launches MCP inspector using .mcp.inspect.json config |
| 57 | +``` |
| 58 | + |
| 59 | +## Code Style & Conventions |
| 60 | + |
| 61 | +**ESM-only codebase:** |
| 62 | + |
| 63 | +- All packages have `"type": "module"` |
| 64 | +- **ALWAYS include file extensions** in imports: `import { foo } from './bar.ts'` (not `./bar`) |
| 65 | +- Exception: Package imports don't need extensions |
| 66 | + |
| 67 | +**JSON imports:** |
| 68 | + |
| 69 | +```typescript |
| 70 | +import pkgJson from '../package.json' with { type: 'json' }; |
| 71 | +``` |
| 72 | + |
| 73 | +**TypeScript config:** |
| 74 | + |
| 75 | +- Uses `@tsconfig/node24` base |
| 76 | +- Module resolution: `bundler` |
| 77 | +- Module format: `preserve` |
| 78 | + |
| 79 | +**Naming:** |
| 80 | + |
| 81 | +- Constants: `SCREAMING_SNAKE_CASE` (e.g., `GET_STORY_URLS_TOOL_NAME`) |
| 82 | +- Functions: `camelCase` |
| 83 | +- Types: `PascalCase` |
| 84 | + |
| 85 | +## Adding MCP Tools |
| 86 | + |
| 87 | +### In addon package (`packages/addon-mcp`): |
| 88 | + |
| 89 | +1. Create `src/tools/my-tool.ts`: |
| 90 | + |
| 91 | +```typescript |
| 92 | +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; |
| 93 | +import z from 'zod'; |
| 94 | + |
| 95 | +export const MY_TOOL_NAME = 'my_tool'; |
| 96 | + |
| 97 | +export function registerMyTool({ |
| 98 | + server, |
| 99 | + options, |
| 100 | +}: { |
| 101 | + server: McpServer; |
| 102 | + options: Options; |
| 103 | +}) { |
| 104 | + server.registerTool( |
| 105 | + MY_TOOL_NAME, |
| 106 | + { |
| 107 | + title: 'My Tool', |
| 108 | + description: 'What it does', |
| 109 | + inputSchema: z.object({ param: z.string() }), |
| 110 | + }, |
| 111 | + async ({ param }, { sessionId }) => { |
| 112 | + // Implementation |
| 113 | + return { |
| 114 | + /* result */ |
| 115 | + }; |
| 116 | + }, |
| 117 | + ); |
| 118 | +} |
| 119 | +``` |
| 120 | + |
| 121 | +2. Register in `src/mcp-handler.ts` after existing tools |
| 122 | + |
| 123 | +### In mcp package (`packages/mcp`): |
| 124 | + |
| 125 | +1. Create `src/tools/my-tool.ts`: |
| 126 | + |
| 127 | +```typescript |
| 128 | +export async function addMyTool(server: McpServer<any, StorybookContext>) { |
| 129 | + server.tool({ name: 'my_tool', description: 'What it does' }, async () => ({ |
| 130 | + content: [{ type: 'text', text: 'result' }], |
| 131 | + })); |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +2. Import and call in `src/index.ts` within `createStorybookMcpHandler` |
| 136 | + |
| 137 | +## Integration Points |
| 138 | + |
| 139 | +**Storybook internals used:** |
| 140 | + |
| 141 | +- `storybook/internal/csf` - `storyNameFromExport()` for story name conversion |
| 142 | +- `storybook/internal/types` - TypeScript types for Options, StoryIndex |
| 143 | +- `storybook/internal/node-logger` - Logging utilities |
| 144 | +- Framework detection via `options.presets.apply('framework')` |
| 145 | + |
| 146 | +**Story URL generation:** |
| 147 | + |
| 148 | +- Fetches `http://localhost:${port}/index.json` for story index |
| 149 | +- Matches stories by `importPath` (relative from cwd) and `name` |
| 150 | +- Returns URLs like `http://localhost:6006/?path=/story/button--primary` |
| 151 | + |
| 152 | +**Telemetry:** |
| 153 | + |
| 154 | +- Addon collects usage data (see `src/telemetry.ts`) |
| 155 | +- Respects `disableTelemetry` from Storybook core config |
| 156 | +- Tracks session initialization and tool usage |
| 157 | + |
| 158 | +## Special Build Considerations |
| 159 | + |
| 160 | +**JSON tree-shaking:** |
| 161 | + |
| 162 | +- `packages/mcp/tsdown.config.ts` has custom plugin to work around rolldown bug |
| 163 | +- Only includes specified package.json keys in bundle (name, version, description) |
| 164 | +- If adding new package.json properties to code, update plugin |
| 165 | + |
| 166 | +**Package exports:** |
| 167 | + |
| 168 | +- Addon exports only `./preset` (Storybook convention) |
| 169 | +- MCP package exports main module with types |
| 170 | + |
| 171 | +## Release Process |
| 172 | + |
| 173 | +Uses Changesets for versioning: |
| 174 | + |
| 175 | +```bash |
| 176 | +pnpm changeset # Create a changeset for your changes |
| 177 | +pnpm release # Build and publish (CI handles this) |
| 178 | +``` |
| 179 | + |
| 180 | +## Testing with Internal Storybook |
| 181 | + |
| 182 | +The `apps/internal-storybook` provides a real Storybook instance: |
| 183 | + |
| 184 | +- Runs on port 6006 |
| 185 | +- Addon MCP endpoint: `http://localhost:6006/mcp` |
| 186 | +- Test with `.mcp.json` config pointing to localhost:6006 |
| 187 | + |
| 188 | +## Package-Specific Instructions |
| 189 | + |
| 190 | +For detailed package-specific guidance, see: |
| 191 | + |
| 192 | +- `packages/addon-mcp/**` → `.github/instructions/addon-mcp.instructions.md` |
| 193 | +- `packages/mcp/**` → `.github/instructions/mcp.instructions.md` |
0 commit comments