Skip to content

Commit 8f51e2e

Browse files
authored
Merge pull request #172 from storybookjs/docs
Improve documentation
2 parents fd88a99 + 5af6b6d commit 8f51e2e

File tree

12 files changed

+725
-221
lines changed

12 files changed

+725
-221
lines changed

.changeset/bright-masks-hunt.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@storybook/addon-mcp': patch
3+
'@storybook/mcp': patch
4+
---
5+
6+
Simplify package READMEs for docs-site-first documentation

apps/self-host-mcp/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Self-hosting example (`@storybook/mcp`)
2+
3+
This app shows the smallest practical way to run `@storybook/mcp` as an HTTP endpoint in Node.js or Netlify Functions.
4+
5+
It is available to experiment with at https://storybook-mcp-self-host-example.netlify.app/mcp
6+
7+
## Run
8+
9+
From the repository root:
10+
11+
```bash
12+
pnpm install
13+
cd apps/self-host-mcp
14+
pnpm start
15+
```
16+
17+
MCP endpoint: `http://localhost:13316/mcp`
18+
19+
## Options
20+
21+
```bash
22+
cd apps/self-host-mcp
23+
pnpm start -- --port 13316 --manifestsPath ./manifests
24+
```
25+
26+
- `--port`: HTTP port to serve
27+
- `--manifestsPath`: local directory or remote base URL containing `components.json` and optionally `docs.json`
28+
29+
## Use your own components
30+
31+
To try this server with your own component library, first build your Storybook so it generates manifests, then copy the content of the `manifests` directory from the build-output (usually `./storybook-static/manifests`) into this example's `manifests/` directory.
32+
33+
In practice, you want `components.json` (and `docs.json` if available) in `apps/self-host-mcp/manifests/` before running `pnpm start`.
34+
35+
## Run on Netlify Functions
36+
37+
This example also includes a Netlify function at `netlify/functions/mcp.ts` and routing in `netlify.toml`.
38+
39+
1. Build your Storybook and copy generated manifests into `apps/self-host-mcp/manifests/` (or set `MANIFESTS_PATH` to a remote URL).
40+
2. Deploy `apps/self-host-mcp` as a Netlify project.
41+
3. Your MCP endpoint is available at `/mcp` (rewritten to `/.netlify/functions/mcp`).
42+
43+
## What this demonstrates
44+
45+
- Instantiate a handler with `createStorybookMcpHandler`
46+
- Route only `/mcp` requests to MCP transport
47+
- Provide a custom `manifestProvider` for local or remote manifest sources
48+
- Use the same handler implementation for both Node and Netlify Functions
49+
50+
For full guidance and Netlify Functions adaptation notes, see [packages/mcp/README.md](../../packages/mcp/README.md).
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"v": 1,
3+
"components": {
4+
"button": {
5+
"id": "button",
6+
"name": "Button",
7+
"path": "./src/components/Button.tsx",
8+
"import": "./src/components/Button.tsx",
9+
"summary": "A basic action trigger used throughout the interface.",
10+
"stories": [
11+
{
12+
"id": "button--primary",
13+
"name": "Primary",
14+
"summary": "Primary button style for main actions.",
15+
"snippet": "<Button variant=\"primary\">Save</Button>"
16+
},
17+
{
18+
"id": "button--secondary",
19+
"name": "Secondary",
20+
"summary": "Secondary button style for supporting actions.",
21+
"snippet": "<Button variant=\"secondary\">Cancel</Button>"
22+
}
23+
]
24+
}
25+
}
26+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"v": 1,
3+
"docs": {
4+
"getting-started": {
5+
"id": "getting-started",
6+
"name": "Getting Started",
7+
"title": "Docs/Getting Started",
8+
"path": "./docs/getting-started.mdx",
9+
"summary": "Introduces the design system and component usage conventions.",
10+
"content": "Use Button for interactive actions. Prefer primary for primary actions and secondary for alternate actions."
11+
}
12+
}
13+
}

apps/self-host-mcp/netlify.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[build]
2+
base = "apps/self-host-mcp"
3+
command = "pnpm install --frozen-lockfile"
4+
publish = "."
5+
6+
[[redirects]]
7+
from = "/mcp"
8+
to = "/.netlify/functions/mcp"
9+
status = 200
10+
11+
[functions]
12+
included_files = ["manifests/**"]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { createMcpHandler } from '../../server.ts';
2+
3+
const manifestsPath = process.env.MANIFESTS_PATH ?? './manifests';
4+
5+
let cachedHandlerPromise: ReturnType<typeof createMcpHandler> | undefined;
6+
7+
export default async function handler(request: Request): Promise<Response> {
8+
const pathname = new URL(request.url).pathname;
9+
if (pathname !== '/mcp' && pathname !== '/.netlify/functions/mcp') {
10+
return new Response('Not found', { status: 404 });
11+
}
12+
13+
if (!cachedHandlerPromise) {
14+
cachedHandlerPromise = createMcpHandler(manifestsPath);
15+
}
16+
17+
const mcpHandler = await cachedHandlerPromise;
18+
return mcpHandler(request);
19+
}

apps/self-host-mcp/package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "@storybook/mcp-self-host",
3+
"version": "0.0.0",
4+
"private": true,
5+
"description": "Self-hosting example for @storybook/mcp",
6+
"type": "module",
7+
"scripts": {
8+
"start": "node ./server.ts"
9+
},
10+
"dependencies": {
11+
"@storybook/mcp": "latest",
12+
"srvx": "^0.8.16"
13+
},
14+
"devDependencies": {
15+
"@types/node": "^24.0.0",
16+
"typescript": "~5.9.0"
17+
}
18+
}

apps/self-host-mcp/pnpm-lock.yaml

Lines changed: 162 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/self-host-mcp/server.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { createStorybookMcpHandler } from '@storybook/mcp';
2+
import fs from 'node:fs/promises';
3+
import { basename, resolve } from 'node:path';
4+
5+
export function createMcpHandler(manifestsPath: string) {
6+
return createStorybookMcpHandler({
7+
manifestProvider: async (_request: Request | undefined, path: string) => {
8+
const fileName = basename(path);
9+
10+
if (manifestsPath.startsWith('http://') || manifestsPath.startsWith('https://')) {
11+
const response = await fetch(`${manifestsPath}/${fileName}`);
12+
if (!response.ok) {
13+
throw new Error(
14+
`Failed to fetch manifest from ${manifestsPath}/${fileName}: ${response.status} ${response.statusText}`,
15+
);
16+
}
17+
return await response.text();
18+
}
19+
20+
return await fs.readFile(resolve(manifestsPath, fileName), 'utf-8');
21+
},
22+
});
23+
}
24+
25+
// when running node ./server.ts
26+
if (import.meta.main) {
27+
const [{ serve }, { parseArgs }] = await Promise.all([import('srvx'), import('node:util')]);
28+
29+
const args = parseArgs({
30+
options: {
31+
port: {
32+
type: 'string',
33+
default: '13316',
34+
},
35+
manifestsPath: {
36+
type: 'string',
37+
default: './manifests',
38+
},
39+
},
40+
});
41+
42+
const storybookMcpHandler = await createMcpHandler(args.values.manifestsPath);
43+
44+
serve({
45+
port: Number(args.values.port),
46+
async fetch(request: Request) {
47+
if (new URL(request.url).pathname !== '/mcp') {
48+
return new Response('Not found', { status: 404 });
49+
}
50+
51+
return await storybookMcpHandler(request);
52+
},
53+
});
54+
55+
console.log(`@storybook/mcp example server listening on http://localhost:${port}/mcp`);
56+
}

apps/self-host-mcp/tsconfig.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
// Prevent inherited workspace aliases from resolving @storybook/mcp to source during Netlify function bundling.
5+
"paths": {},
6+
"outDir": "./dist"
7+
},
8+
"include": ["**/*.ts"],
9+
"exclude": ["node_modules", "dist"]
10+
}

0 commit comments

Comments
 (0)