@storybook/addon-mcp 0.1.4-next.1 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/preset.js +47 -36
- package/package.json +9 -17
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ It enables a workflow where for each UI component created, the agent will automa
|
|
|
7
7
|
The addon provides tools to improve agents' UI development capabilities, retrieve story URLs, and access component documentation.
|
|
8
8
|
|
|
9
9
|
<div align="center">
|
|
10
|
-
<img src="
|
|
10
|
+
<img src="./addon-mcp-claude-code-showcase.gif" alt="Storybook MCP Addon Demo" />
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
13
|
## Getting Started
|
|
@@ -50,6 +50,7 @@ export default {
|
|
|
50
50
|
dev: true, // Tools for story URL retrieval and UI building instructions (default: true)
|
|
51
51
|
docs: true, // Tools for component manifest and documentation (default: true, requires experimental feature)
|
|
52
52
|
},
|
|
53
|
+
experimentalFormat: 'markdown', // Output format: 'markdown' (default) or 'xml'
|
|
53
54
|
},
|
|
54
55
|
},
|
|
55
56
|
],
|
|
@@ -193,12 +194,12 @@ Returns a list of all available UI components in your component library. Useful
|
|
|
193
194
|
|
|
194
195
|
#### 4. Get Component Documentation (`get-component-documentation`)
|
|
195
196
|
|
|
196
|
-
Retrieves detailed documentation for specific
|
|
197
|
+
Retrieves detailed documentation for a specific component, including:
|
|
197
198
|
|
|
198
199
|
- Component documentation
|
|
199
200
|
- Usage examples
|
|
200
201
|
|
|
201
|
-
The agent provides component
|
|
202
|
+
The agent provides a component ID to retrieve its documentation. To get documentation for multiple components, call this tool multiple times.
|
|
202
203
|
|
|
203
204
|
## Contributing
|
|
204
205
|
|
package/dist/preset.js
CHANGED
|
@@ -11,12 +11,12 @@ import { buffer } from "node:stream/consumers";
|
|
|
11
11
|
|
|
12
12
|
//#region package.json
|
|
13
13
|
var name = "@storybook/addon-mcp";
|
|
14
|
-
var version = "0.1.4
|
|
14
|
+
var version = "0.1.4";
|
|
15
15
|
var description = "Help agents automatically write and test stories for your UI components";
|
|
16
16
|
|
|
17
17
|
//#endregion
|
|
18
18
|
//#region src/telemetry.ts
|
|
19
|
-
async function collectTelemetry({ event, server
|
|
19
|
+
async function collectTelemetry({ event, server, ...payload }) {
|
|
20
20
|
try {
|
|
21
21
|
return await telemetry("addon-mcp", {
|
|
22
22
|
event,
|
|
@@ -69,13 +69,16 @@ const errorToMCPContent = (error) => {
|
|
|
69
69
|
|
|
70
70
|
//#endregion
|
|
71
71
|
//#region src/types.ts
|
|
72
|
-
const AddonOptions = v.object({
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
const AddonOptions = v.object({
|
|
73
|
+
toolsets: v.optional(v.object({
|
|
74
|
+
dev: v.exactOptional(v.boolean(), true),
|
|
75
|
+
docs: v.exactOptional(v.boolean(), true)
|
|
76
|
+
}), {
|
|
77
|
+
dev: true,
|
|
78
|
+
docs: true
|
|
79
|
+
}),
|
|
80
|
+
experimentalFormat: v.optional(v.picklist(["xml", "markdown"]), "markdown")
|
|
81
|
+
});
|
|
79
82
|
/**
|
|
80
83
|
* Schema for a single story input when requesting story URLs.
|
|
81
84
|
*/
|
|
@@ -106,7 +109,7 @@ async function addGetStoryUrlsTool(server) {
|
|
|
106
109
|
enabled: () => server.ctx.custom?.toolsets?.dev ?? true
|
|
107
110
|
}, async (input) => {
|
|
108
111
|
try {
|
|
109
|
-
const { origin: origin$1, disableTelemetry } = server.ctx.custom ?? {};
|
|
112
|
+
const { origin: origin$1, disableTelemetry: disableTelemetry$1 } = server.ctx.custom ?? {};
|
|
110
113
|
if (!origin$1) throw new Error("Origin is required in addon context");
|
|
111
114
|
const index = await fetchStoryIndex(origin$1);
|
|
112
115
|
const entriesList = Object.values(index.entries);
|
|
@@ -133,9 +136,10 @@ async function addGetStoryUrlsTool(server) {
|
|
|
133
136
|
result.push(errorMessage);
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
|
-
if (!disableTelemetry) await collectTelemetry({
|
|
139
|
+
if (!disableTelemetry$1) await collectTelemetry({
|
|
137
140
|
event: "tool:getStoryUrls",
|
|
138
141
|
server,
|
|
142
|
+
toolset: "dev",
|
|
139
143
|
inputStoryCount: input.stories.length,
|
|
140
144
|
outputStoryCount: foundStoryCount
|
|
141
145
|
});
|
|
@@ -151,7 +155,7 @@ async function addGetStoryUrlsTool(server) {
|
|
|
151
155
|
|
|
152
156
|
//#endregion
|
|
153
157
|
//#region src/ui-building-instructions.md
|
|
154
|
-
var ui_building_instructions_default = "# Writing User Interfaces\n\nWhen writing UI, prefer breaking larger components up into smaller parts.\n\nALWAYS write a Storybook story for any component written. If editing a component, ensure appropriate changes have been made to stories for that component.\n\n## Storybook 9 Essential Changes for Story Writing\n\n### Package Consolidation\n\n#### `Meta` and `StoryObj` imports\n\nUpdate story imports to use the framework package:\n\n```diff\n- import { Meta, StoryObj } from '{{RENDERER}}';\n+ import { Meta, StoryObj } from '{{FRAMEWORK}}';\n```\n\n#### Test utility imports\n\nUpdate test imports to use `storybook/test` instead of `@storybook/test`\n\n```diff\n- import { fn } from '@storybook/test';\n+ import { fn } from 'storybook/test';\n```\n\n### Global State Changes\n\nThe `globals` annotation has be renamed to `initialGlobals`:\n\n```diff\n// .storybook/preview.js\nexport default {\n- globals: { theme: 'light' }\n+ initialGlobals: { theme: 'light' }\n};\n```\n\n### Autodocs Configuration\n\nInstead of `parameters.docs.autodocs` in main.js, use tags:\n\n```js\n// .storybook/preview.js or in individual stories\nexport default {\n tags: ['autodocs'], // generates autodocs for all stories\n};\n```\n\n### Mocking imports in Storybook\n\nTo mock imports in Storybook, use Storybook's mocking features. ALWAYS mock external dependencies to ensure stories render consistently.\n\n1. **Register in the mock in Storybook's preview file**:\n To mock dependendencies, you MUST register a module mock in `.storybook/preview.ts` (or equivalent):\n\n```js\nimport { sb } from 'storybook/test';\n\n//
|
|
158
|
+
var ui_building_instructions_default = "# Writing User Interfaces\n\nWhen writing UI, prefer breaking larger components up into smaller parts.\n\nALWAYS write a Storybook story for any component written. If editing a component, ensure appropriate changes have been made to stories for that component.\n\n## Storybook 9 Essential Changes for Story Writing\n\n### Package Consolidation\n\n#### `Meta` and `StoryObj` imports\n\nUpdate story imports to use the framework package:\n\n```diff\n- import { Meta, StoryObj } from '{{RENDERER}}';\n+ import { Meta, StoryObj } from '{{FRAMEWORK}}';\n```\n\n#### Test utility imports\n\nUpdate test imports to use `storybook/test` instead of `@storybook/test`\n\n```diff\n- import { fn } from '@storybook/test';\n+ import { fn } from 'storybook/test';\n```\n\n### Global State Changes\n\nThe `globals` annotation has be renamed to `initialGlobals`:\n\n```diff\n// .storybook/preview.js\nexport default {\n- globals: { theme: 'light' }\n+ initialGlobals: { theme: 'light' }\n};\n```\n\n### Autodocs Configuration\n\nInstead of `parameters.docs.autodocs` in main.js, use tags:\n\n```js\n// .storybook/preview.js or in individual stories\nexport default {\n tags: ['autodocs'], // generates autodocs for all stories\n};\n```\n\n### Mocking imports in Storybook\n\nTo mock imports in Storybook, use Storybook's mocking features. ALWAYS mock external dependencies to ensure stories render consistently.\n\n1. **Register in the mock in Storybook's preview file**:\n To mock dependendencies, you MUST register a module mock in `.storybook/preview.ts` (or equivalent):\n\n```js\nimport { sb } from 'storybook/test';\n\n// Prefer spy mocks (keeps functions, but allows to override them and spy on them)\nsb.mock(import('some-library'), { spy: true });\n```\n\n2. **Specify mock values in stories**:\n You can override the behaviour of the mocks per-story using `beforeEach` and the `mocked()` type function:\n\n```js\nimport { expect, mocked, fn } from 'storybook/test';\nimport { library } from 'some-library';\n\nconst meta = {\n component: AuthButton,\n beforeEach: async () => {\n mocked(library).mockResolvedValue({ user: 'data' });\n },\n};\n\nexport const LoggedIn: Story = {\n play: async ({ canvas }) => {\n await expect(library).toHaveBeenCalled();\n },\n};\n```\n\nBefore doing this ensure you have mocked the import in the preview file.\n\n### Key Requirements\n\n- **Node.js 20+**, **TypeScript 4.9+**, **Vite 5+**\n- React Native uses `.rnstorybook` directory\n\n## Story Linking Agent Behavior\n\n- ALWAYS provide story links after any changes to stories files, including changes to existing stories.\n- After changing any UI components, ALWAYS search for related stories that might cover the changes you've made. If you find any, provide the story links to the user. THIS IS VERY IMPORTANT, as it allows the user to visually inspect the changes you've made. Even later in a session when changing UI components or stories that have already been linked to previously, YOU MUST PROVIDE THE LINKS AGAIN.\n- Use the {{GET_STORY_URLS_TOOL_NAME}} tool to get the correct URLs for links to stories.\n";
|
|
155
159
|
|
|
156
160
|
//#endregion
|
|
157
161
|
//#region src/tools/get-ui-building-instructions.ts
|
|
@@ -167,11 +171,12 @@ async function addGetUIBuildingInstructionsTool(server) {
|
|
|
167
171
|
enabled: () => server.ctx.custom?.toolsets?.dev ?? true
|
|
168
172
|
}, async () => {
|
|
169
173
|
try {
|
|
170
|
-
const { options, disableTelemetry } = server.ctx.custom ?? {};
|
|
174
|
+
const { options, disableTelemetry: disableTelemetry$1 } = server.ctx.custom ?? {};
|
|
171
175
|
if (!options) throw new Error("Options are required in addon context");
|
|
172
|
-
if (!disableTelemetry) await collectTelemetry({
|
|
176
|
+
if (!disableTelemetry$1) await collectTelemetry({
|
|
173
177
|
event: "tool:getUIBuildingInstructions",
|
|
174
|
-
server
|
|
178
|
+
server,
|
|
179
|
+
toolset: "dev"
|
|
175
180
|
});
|
|
176
181
|
const frameworkPreset = await options.presets.apply("framework");
|
|
177
182
|
const framework = typeof frameworkPreset === "string" ? frameworkPreset : frameworkPreset?.name;
|
|
@@ -213,7 +218,9 @@ const isManifestAvailable = async (options) => {
|
|
|
213
218
|
let transport;
|
|
214
219
|
let origin;
|
|
215
220
|
let initialize;
|
|
221
|
+
let disableTelemetry;
|
|
216
222
|
const initializeMCPServer = async (options) => {
|
|
223
|
+
disableTelemetry = (await options.presets.apply("core", {}))?.disableTelemetry ?? false;
|
|
217
224
|
const server = new McpServer({
|
|
218
225
|
name,
|
|
219
226
|
version,
|
|
@@ -222,8 +229,8 @@ const initializeMCPServer = async (options) => {
|
|
|
222
229
|
adapter: new ValibotJsonSchemaAdapter(),
|
|
223
230
|
capabilities: { tools: { listChanged: true } }
|
|
224
231
|
}).withContext();
|
|
225
|
-
server.on("initialize", () => {
|
|
226
|
-
|
|
232
|
+
if (!disableTelemetry) server.on("initialize", async () => {
|
|
233
|
+
await collectTelemetry({
|
|
227
234
|
event: "session:initialized",
|
|
228
235
|
server
|
|
229
236
|
});
|
|
@@ -241,32 +248,33 @@ const initializeMCPServer = async (options) => {
|
|
|
241
248
|
logger.debug("MCP server origin:", origin);
|
|
242
249
|
return server;
|
|
243
250
|
};
|
|
244
|
-
const mcpServerHandler = async ({ req, res,
|
|
245
|
-
const disableTelemetry = options.disableTelemetry ?? false;
|
|
251
|
+
const mcpServerHandler = async ({ req, res, options, addonOptions }) => {
|
|
246
252
|
if (!initialize) initialize = initializeMCPServer(options);
|
|
247
253
|
const server = await initialize;
|
|
248
254
|
const webRequest = await incomingMessageToWebRequest(req);
|
|
249
255
|
const addonContext = {
|
|
250
256
|
options,
|
|
251
257
|
toolsets: getToolsets(webRequest, addonOptions),
|
|
258
|
+
format: addonOptions.experimentalFormat,
|
|
252
259
|
origin,
|
|
253
260
|
disableTelemetry,
|
|
254
|
-
|
|
261
|
+
request: webRequest,
|
|
255
262
|
...!disableTelemetry && {
|
|
256
263
|
onListAllComponents: async ({ manifest }) => {
|
|
257
264
|
await collectTelemetry({
|
|
258
265
|
event: "tool:listAllComponents",
|
|
259
266
|
server,
|
|
267
|
+
toolset: "docs",
|
|
260
268
|
componentCount: Object.keys(manifest.components).length
|
|
261
269
|
});
|
|
262
270
|
},
|
|
263
|
-
onGetComponentDocumentation: async ({ input,
|
|
271
|
+
onGetComponentDocumentation: async ({ input, foundComponent }) => {
|
|
264
272
|
await collectTelemetry({
|
|
265
273
|
event: "tool:getComponentDocumentation",
|
|
266
274
|
server,
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
275
|
+
toolset: "docs",
|
|
276
|
+
componentId: input.componentId,
|
|
277
|
+
found: !!foundComponent
|
|
270
278
|
});
|
|
271
279
|
}
|
|
272
280
|
}
|
|
@@ -332,24 +340,27 @@ var template_default = "<!doctype html>\n<html>\n <head>\n {{REDIRECT_META}}\n
|
|
|
332
340
|
//#endregion
|
|
333
341
|
//#region src/preset.ts
|
|
334
342
|
const experimental_devServer = async (app, options) => {
|
|
335
|
-
const addonOptions = v.parse(AddonOptions, {
|
|
336
|
-
|
|
343
|
+
const addonOptions = v.parse(AddonOptions, {
|
|
344
|
+
toolsets: "toolsets" in options ? options.toolsets : {},
|
|
345
|
+
experimentalFormat: "experimentalFormat" in options ? options.experimentalFormat : "markdown"
|
|
346
|
+
});
|
|
347
|
+
app.post("/mcp", (req, res) => mcpServerHandler({
|
|
337
348
|
req,
|
|
338
349
|
res,
|
|
339
|
-
next,
|
|
340
350
|
options,
|
|
341
351
|
addonOptions
|
|
342
352
|
}));
|
|
343
353
|
const shouldRedirect = await isManifestAvailable(options);
|
|
344
|
-
app.get("/mcp",
|
|
345
|
-
if (
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
354
|
+
app.get("/mcp", (req, res) => {
|
|
355
|
+
if (!req.headers["accept"]?.includes("text/html")) return mcpServerHandler({
|
|
356
|
+
req,
|
|
357
|
+
res,
|
|
358
|
+
options,
|
|
359
|
+
addonOptions
|
|
360
|
+
});
|
|
361
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
362
|
+
const html = template_default.replace("{{REDIRECT_META}}", shouldRedirect ? "<meta http-equiv=\"refresh\" content=\"10;url=/manifests/components.html\" />" : "<style>#redirect-message { display: none; }</style>");
|
|
363
|
+
res.end(html);
|
|
353
364
|
});
|
|
354
365
|
return app;
|
|
355
366
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storybook/addon-mcp",
|
|
3
|
-
"version": "0.1.4
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Help agents automatically write and test stories for your UI components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"storybook-addon",
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
"ai"
|
|
9
9
|
],
|
|
10
10
|
"repository": {
|
|
11
|
-
"
|
|
12
|
-
"url": "https://github.com/storybookjs/mcp.git",
|
|
11
|
+
"url": "git+https://github.com/storybookjs/mcp.git",
|
|
13
12
|
"directory": "packages/addon-mcp"
|
|
14
13
|
},
|
|
15
14
|
"license": "MIT",
|
|
@@ -29,18 +28,11 @@
|
|
|
29
28
|
"@tmcp/adapter-valibot": "^0.1.4",
|
|
30
29
|
"@tmcp/transport-http": "^0.8.0",
|
|
31
30
|
"tmcp": "^1.16.0",
|
|
32
|
-
"valibot": "
|
|
33
|
-
"@storybook/mcp": "0.0
|
|
31
|
+
"valibot": "1.1.0",
|
|
32
|
+
"@storybook/mcp": "0.1.0"
|
|
34
33
|
},
|
|
35
34
|
"devDependencies": {
|
|
36
|
-
"
|
|
37
|
-
"@vitest/coverage-v8": "3.2.4",
|
|
38
|
-
"storybook": "^10.1.0-alpha.2",
|
|
39
|
-
"ts-dedent": "^2.2.0",
|
|
40
|
-
"tsdown": "^0.15.12",
|
|
41
|
-
"typescript": "^5.9.3",
|
|
42
|
-
"vite": "^7.0.5",
|
|
43
|
-
"vitest": "3.2.4"
|
|
35
|
+
"storybook": "10.1.0-alpha.11"
|
|
44
36
|
},
|
|
45
37
|
"peerDependencies": {
|
|
46
38
|
"storybook": "^9.1.16 || ^10.0.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0"
|
|
@@ -69,10 +61,10 @@
|
|
|
69
61
|
},
|
|
70
62
|
"scripts": {
|
|
71
63
|
"prebuild": "node -e \"fs.rmSync('./dist', { recursive: true, force: true })\"",
|
|
72
|
-
"build": "tsdown
|
|
73
|
-
"dev": "pnpm run build -- --watch",
|
|
74
|
-
"test": "vitest",
|
|
64
|
+
"build": "tsdown",
|
|
75
65
|
"inspect": "mcp-inspector --config ../../.mcp.inspect.json --server storybook-addon-mcp",
|
|
76
|
-
"
|
|
66
|
+
"publint": "publint",
|
|
67
|
+
"test": "cd ../.. && pnpm vitest --project=@storybook/addon-mcp",
|
|
68
|
+
"typecheck": "tsc"
|
|
77
69
|
}
|
|
78
70
|
}
|