@storybook/addon-mcp 0.1.4 → 0.1.6

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 CHANGED
@@ -48,12 +48,15 @@ export default {
48
48
  options: {
49
49
  toolsets: {
50
50
  dev: true, // Tools for story URL retrieval and UI building instructions (default: true)
51
- docs: true, // Tools for component manifest and documentation (default: true, requires experimental feature)
51
+ docs: true, // Tools for component manifest and documentation (default: true, requires experimental feature flag below 👇)
52
52
  },
53
53
  experimentalFormat: 'markdown', // Output format: 'markdown' (default) or 'xml'
54
54
  },
55
55
  },
56
56
  ],
57
+ features: {
58
+ experimentalComponentsManifest: true, // Enable manifest generation for the docs toolset, only supported in React-based setups.
59
+ },
57
60
  };
58
61
  ```
59
62
 
package/dist/preset.js CHANGED
@@ -11,7 +11,7 @@ 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.6";
15
15
  var description = "Help agents automatically write and test stories for your UI components";
16
16
 
17
17
  //#endregion
@@ -26,7 +26,7 @@ async function collectTelemetry({ event, server, ...payload }) {
26
26
  ...payload
27
27
  });
28
28
  } catch (error) {
29
- logger.debug("Error collecting telemetry:", error);
29
+ logger.debug(`Error collecting telemetry: ${error}`);
30
30
  }
31
31
  }
32
32
 
@@ -41,11 +41,11 @@ async function collectTelemetry({ event, server, ...payload }) {
41
41
  */
42
42
  async function fetchStoryIndex(origin$1) {
43
43
  const indexUrl = `${origin$1}/index.json`;
44
- logger.debug("Fetching story index from:", indexUrl);
44
+ logger.debug(`Fetching story index from: ${indexUrl}`);
45
45
  const response = await fetch(indexUrl);
46
46
  if (!response.ok) throw new Error(`Failed to fetch story index: ${response.status} ${response.statusText}`);
47
47
  const index = await response.json();
48
- logger.debug("Story index entries found:", Object.keys(index.entries).length);
48
+ logger.debug(`Story index entries found: ${Object.keys(index.entries).length}`);
49
49
  return index;
50
50
  }
51
51
 
@@ -126,7 +126,7 @@ async function addGetStoryUrlsTool(server) {
126
126
  });
127
127
  const foundStoryId = entriesList.find((entry) => entry.importPath === relativePath && [explicitStoryName, storyNameFromExport(exportName)].includes(entry.name))?.id;
128
128
  if (foundStoryId) {
129
- logger.debug("Found story ID:", foundStoryId);
129
+ logger.debug(`Found story ID: ${foundStoryId}`);
130
130
  result.push(`${origin$1}/?path=/story/${foundStoryId}`);
131
131
  foundStoryCount++;
132
132
  } else {
@@ -208,9 +208,15 @@ const frameworkToRendererMap = {
208
208
 
209
209
  //#endregion
210
210
  //#region src/tools/is-manifest-available.ts
211
- const isManifestAvailable = async (options) => {
211
+ const getManifestStatus = async (options) => {
212
212
  const [features, componentManifestGenerator] = await Promise.all([options.presets.apply("features"), options.presets.apply("experimental_componentManifestGenerator")]);
213
- return features.experimentalComponentsManifest && componentManifestGenerator;
213
+ const hasGenerator = !!componentManifestGenerator;
214
+ const hasFeatureFlag = !!features?.experimentalComponentsManifest;
215
+ return {
216
+ available: hasFeatureFlag && hasGenerator,
217
+ hasGenerator,
218
+ hasFeatureFlag
219
+ };
214
220
  };
215
221
 
216
222
  //#endregion
@@ -237,7 +243,7 @@ const initializeMCPServer = async (options) => {
237
243
  });
238
244
  await addGetStoryUrlsTool(server);
239
245
  await addGetUIBuildingInstructionsTool(server);
240
- if (await isManifestAvailable(options)) {
246
+ if ((await getManifestStatus(options)).available) {
241
247
  logger.info("Experimental components manifest feature detected - registering component tools");
242
248
  const contextAwareEnabled = () => server.ctx.custom?.toolsets?.docs ?? true;
243
249
  await addListAllComponentsTool(server, contextAwareEnabled);
@@ -245,7 +251,7 @@ const initializeMCPServer = async (options) => {
245
251
  }
246
252
  transport = new HttpTransport(server, { path: null });
247
253
  origin = `http://localhost:${options.port}`;
248
- logger.debug("MCP server origin:", origin);
254
+ logger.debug(`MCP server origin: ${origin}`);
249
255
  return server;
250
256
  };
251
257
  const mcpServerHandler = async ({ req, res, options, addonOptions }) => {
@@ -335,7 +341,7 @@ function getToolsets(request, addonOptions) {
335
341
 
336
342
  //#endregion
337
343
  //#region src/template.html
338
- var template_default = "<!doctype html>\n<html>\n <head>\n {{REDIRECT_META}}\n <style>\n @font-face {\n font-family: 'Nunito Sans';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url('./sb-common-assets/nunito-sans-regular.woff2') format('woff2');\n }\n\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n html,\n body {\n height: 100%;\n font-family:\n 'Nunito Sans',\n -apple-system,\n BlinkMacSystemFont,\n 'Segoe UI',\n Roboto,\n Oxygen,\n Ubuntu,\n Cantarell,\n sans-serif;\n }\n\n body {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n text-align: center;\n padding: 2rem;\n background-color: #ffffff;\n color: rgb(46, 52, 56);\n line-height: 1.6;\n }\n\n p {\n margin-bottom: 1rem;\n }\n\n code {\n font-family: 'Monaco', 'Courier New', monospace;\n background: #f5f5f5;\n padding: 0.2em 0.4em;\n border-radius: 3px;\n }\n\n a {\n color: #1ea7fd;\n }\n\n @media (prefers-color-scheme: dark) {\n body {\n background-color: rgb(34, 36, 37);\n color: rgb(201, 205, 207);\n }\n\n code {\n background: rgba(255, 255, 255, 0.1);\n }\n }\n </style>\n </head>\n <body>\n <div>\n <p>\n Storybook MCP server successfully running via\n <code>@storybook/addon-mcp</code>.\n </p>\n <p>\n See how to connect to it from your coding agent in\n <a\n target=\"_blank\"\n href=\"https://github.com/storybookjs/mcp/tree/main/packages/addon-mcp#configuring-your-agent\"\n >the addon's README</a\n >.\n </p>\n <p id=\"redirect-message\">\n Automatically redirecting to\n <a href=\"/manifests/components.html\">component manifest</a>\n in <span id=\"countdown\">10</span> seconds...\n </p>\n </div>\n <script>\n let countdown = 10;\n const countdownElement = document.getElementById('countdown');\n if (countdownElement) {\n setInterval(() => {\n countdown -= 1;\n countdownElement.textContent = countdown.toString();\n }, 1000);\n }\n <\/script>\n </body>\n</html>\n";
344
+ var template_default = "<!doctype html>\n<html>\n <head>\n {{REDIRECT_META}}\n <style>\n @font-face {\n font-family: 'Nunito Sans';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url('./sb-common-assets/nunito-sans-regular.woff2') format('woff2');\n }\n\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n html,\n body {\n height: 100%;\n font-family:\n 'Nunito Sans',\n -apple-system,\n BlinkMacSystemFont,\n 'Segoe UI',\n Roboto,\n Oxygen,\n Ubuntu,\n Cantarell,\n sans-serif;\n }\n\n body {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n text-align: center;\n padding: 2rem;\n background-color: #ffffff;\n color: rgb(46, 52, 56);\n line-height: 1.6;\n }\n\n p {\n margin-bottom: 1rem;\n }\n\n code {\n font-family: 'Monaco', 'Courier New', monospace;\n background: #f5f5f5;\n padding: 0.2em 0.4em;\n border-radius: 3px;\n }\n\n a {\n color: #1ea7fd;\n }\n\n .container {\n display: flex;\n flex-direction: column;\n align-items: center;\n }\n\n .toolsets {\n margin: 1.5rem 0;\n text-align: left;\n max-width: 500px;\n }\n\n .toolsets h3 {\n font-size: 1rem;\n margin-bottom: 0.75rem;\n text-align: center;\n }\n\n .toolset {\n margin-bottom: 1rem;\n padding: 0.75rem 1rem;\n border-radius: 6px;\n background: #f8f9fa;\n border: 1px solid #e9ecef;\n }\n\n .toolset-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-weight: 600;\n margin-bottom: 0.5rem;\n }\n\n .toolset-status {\n display: inline-block;\n padding: 0.15em 0.5em;\n border-radius: 3px;\n font-size: 0.75rem;\n font-weight: 500;\n text-transform: uppercase;\n }\n\n .toolset-status.enabled {\n background: #d4edda;\n color: #155724;\n }\n\n .toolset-status.disabled {\n background: #f8d7da;\n color: #721c24;\n }\n\n .toolset-tools {\n font-size: 0.875rem;\n color: #6c757d;\n padding-left: 1.5rem;\n margin: 0;\n }\n\n .toolset-tools li {\n margin-bottom: 0.25rem;\n }\n\n .toolset-tools code {\n font-size: 0.8rem;\n }\n\n .toolset-notice {\n font-size: 0.8rem;\n color: #856404;\n background: #fff3cd;\n padding: 0.5rem;\n border-radius: 4px;\n margin-top: 0.5rem;\n }\n\n .toolset-notice a {\n color: #533f03;\n }\n\n @media (prefers-color-scheme: dark) {\n body {\n background-color: rgb(34, 36, 37);\n color: rgb(201, 205, 207);\n }\n\n code {\n background: rgba(255, 255, 255, 0.1);\n }\n\n .toolset {\n background: rgba(255, 255, 255, 0.05);\n border-color: rgba(255, 255, 255, 0.1);\n }\n\n .toolset-tools {\n color: #adb5bd;\n }\n\n .toolset-status.enabled {\n background: rgba(40, 167, 69, 0.2);\n color: #75d67e;\n }\n\n .toolset-status.disabled {\n background: rgba(220, 53, 69, 0.2);\n color: #f5a6ad;\n }\n\n .toolset-notice {\n background: rgba(255, 193, 7, 0.15);\n color: #ffc107;\n }\n\n .toolset-notice a {\n color: #ffe066;\n }\n }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <p>\n Storybook MCP server successfully running via\n <code>@storybook/addon-mcp</code>.\n </p>\n <p>\n See how to connect to it from your coding agent in\n <a\n target=\"_blank\"\n href=\"https://github.com/storybookjs/mcp/tree/main/packages/addon-mcp#configuring-your-agent\"\n >the addon's README</a\n >.\n </p>\n\n <div class=\"toolsets\">\n <h3>Available Toolsets</h3>\n\n <div class=\"toolset\">\n <div class=\"toolset-header\">\n <span>dev</span>\n <span class=\"toolset-status {{DEV_STATUS}}\">{{DEV_STATUS}}</span>\n </div>\n <ul class=\"toolset-tools\">\n <li><code>get-story-urls</code></li>\n <li><code>get-ui-building-instructions</code></li>\n </ul>\n </div>\n\n <div class=\"toolset\">\n <div class=\"toolset-header\">\n <span>docs</span>\n <span class=\"toolset-status {{DOCS_STATUS}}\">{{DOCS_STATUS}}</span>\n </div>\n <ul class=\"toolset-tools\">\n <li><code>list-all-components</code></li>\n <li><code>get-component-documentation</code></li>\n </ul>\n {{DOCS_NOTICE}}\n </div>\n </div>\n\n <p id=\"redirect-message\">\n Automatically redirecting to\n <a href=\"/manifests/components.html\">component manifest</a>\n in <span id=\"countdown\">10</span> seconds...\n </p>\n </div>\n <script>\n let countdown = 10;\n const countdownElement = document.getElementById('countdown');\n if (countdownElement) {\n setInterval(() => {\n countdown -= 1;\n countdownElement.textContent = countdown.toString();\n }, 1000);\n }\n <\/script>\n </body>\n</html>\n";
339
345
 
340
346
  //#endregion
341
347
  //#region src/preset.ts
@@ -350,7 +356,9 @@ const experimental_devServer = async (app, options) => {
350
356
  options,
351
357
  addonOptions
352
358
  }));
353
- const shouldRedirect = await isManifestAvailable(options);
359
+ const manifestStatus = await getManifestStatus(options);
360
+ const isDevEnabled = addonOptions.toolsets?.dev ?? true;
361
+ const isDocsEnabled = manifestStatus.available && (addonOptions.toolsets?.docs ?? true);
354
362
  app.get("/mcp", (req, res) => {
355
363
  if (!req.headers["accept"]?.includes("text/html")) return mcpServerHandler({
356
364
  req,
@@ -359,7 +367,15 @@ const experimental_devServer = async (app, options) => {
359
367
  addonOptions
360
368
  });
361
369
  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>");
370
+ let docsNotice = "";
371
+ if (!manifestStatus.hasGenerator) docsNotice = `<div class="toolset-notice">
372
+ This toolset is only supported in React-based setups.
373
+ </div>`;
374
+ else if (!manifestStatus.hasFeatureFlag) docsNotice = `<div class="toolset-notice">
375
+ This toolset requires enabling the experimental component manifest feature.
376
+ <a target="_blank" href="https://github.com/storybookjs/mcp/tree/main/packages/addon-mcp#docs-tools-experimental">Learn how to enable it</a>
377
+ </div>`;
378
+ const html = template_default.replace("{{REDIRECT_META}}", manifestStatus.available ? "<meta http-equiv=\"refresh\" content=\"10;url=/manifests/components.html\" />" : "<style>#redirect-message { display: none; }</style>").replaceAll("{{DEV_STATUS}}", isDevEnabled ? "enabled" : "disabled").replaceAll("{{DOCS_STATUS}}", isDocsEnabled ? "enabled" : "disabled").replace("{{DOCS_NOTICE}}", docsNotice);
363
379
  res.end(html);
364
380
  });
365
381
  return app;
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.6",
4
4
  "description": "Help agents automatically write and test stories for your UI components",
5
5
  "keywords": [
6
6
  "storybook-addon",
@@ -28,11 +28,11 @@
28
28
  "@tmcp/adapter-valibot": "^0.1.4",
29
29
  "@tmcp/transport-http": "^0.8.0",
30
30
  "tmcp": "^1.16.0",
31
- "valibot": "1.1.0",
32
- "@storybook/mcp": "0.1.0"
31
+ "valibot": "1.2.0",
32
+ "@storybook/mcp": "0.1.1"
33
33
  },
34
34
  "devDependencies": {
35
- "storybook": "10.1.0-alpha.11"
35
+ "storybook": "10.1.2"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "storybook": "^9.1.16 || ^10.0.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0"