@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.
Files changed (3) hide show
  1. package/README.md +4 -3
  2. package/dist/preset.js +47 -36
  3. 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="https://storybook.js.org/embed/addon-mcp-claude-code-showcase.gif" alt="Storybook MCP Addon Demo" />
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 components, including:
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 IDs to retrieve their documentation.
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-next.1";
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,...payload }) {
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({ toolsets: v.optional(v.object({
73
- dev: v.exactOptional(v.boolean(), true),
74
- docs: v.exactOptional(v.boolean(), true)
75
- }), {
76
- dev: true,
77
- docs: true
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// Full mock (replaces with empty fn functions)\nsb.mock(import('some-library'));\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- `afterEach` hook is now stable (was `experimental_afterEach`)\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";
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
- if (!options.disableTelemetry) collectTelemetry({
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, next, options, addonOptions }) => {
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
- source: `${origin}/manifests/components.json`,
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, foundComponents, notFoundIds }) => {
271
+ onGetComponentDocumentation: async ({ input, foundComponent }) => {
264
272
  await collectTelemetry({
265
273
  event: "tool:getComponentDocumentation",
266
274
  server,
267
- inputComponentCount: input.componentIds.length,
268
- foundCount: foundComponents.length,
269
- notFoundCount: notFoundIds.length
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, { toolsets: options.toolsets ?? {} });
336
- app.post("/mcp", (req, res, next) => mcpServerHandler({
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", async (req, res) => {
345
- if ((req.headers["accept"] || "").includes("text/html")) {
346
- res.writeHead(200, { "Content-Type": "text/html" });
347
- const html = template_default.replace("{{REDIRECT_META}}", shouldRedirect ? "<meta http-equiv=\"refresh\" content=\"10;url=/manifests/components.html\" />" : "<style>#redirect-message { display: none; }</style>");
348
- res.end(html);
349
- } else {
350
- res.writeHead(200, { "Content-Type": "text/plain" });
351
- res.end("Storybook MCP server successfully running via @storybook/addon-mcp");
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-next.1",
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
- "type": "git",
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": "^1.1.0",
33
- "@storybook/mcp": "0.0.6"
31
+ "valibot": "1.1.0",
32
+ "@storybook/mcp": "0.1.0"
34
33
  },
35
34
  "devDependencies": {
36
- "@types/node": "20.19.0",
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 --config ../../tsdown.config.ts",
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
- "typecheck": "tsc --noEmit"
66
+ "publint": "publint",
67
+ "test": "cd ../.. && pnpm vitest --project=@storybook/addon-mcp",
68
+ "typecheck": "tsc"
77
69
  }
78
70
  }