@storybook/addon-mcp 0.1.6 → 0.1.8

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
@@ -191,18 +191,15 @@ export default {
191
191
  };
192
192
  ```
193
193
 
194
- #### 3. List All Components (`list-all-components`)
194
+ #### 3. List All Documentation (`list-all-documentation`)
195
195
 
196
- Returns a list of all available UI components in your component library. Useful for the LLM as discovery and understanding what components are available to use.
196
+ Returns a list of all available UI components as well as standalone docs in your component library. Useful for the LLM as discovery and understanding what components are available to use.
197
197
 
198
- #### 4. Get Component Documentation (`get-component-documentation`)
198
+ #### 4. Get Documentation (`get-documentation`)
199
199
 
200
- Retrieves detailed documentation for a specific component, including:
200
+ Retrieves detailed documentation for a specific component or docs entry.
201
201
 
202
- - Component documentation
203
- - Usage examples
204
-
205
- The agent provides a component ID to retrieve its documentation. To get documentation for multiple components, call this tool multiple times.
202
+ The agent provides a component/docs ID to retrieve its documentation. To get documentation for multiple entries, call this tool multiple times.
206
203
 
207
204
  ## Contributing
208
205
 
package/dist/preset.js CHANGED
@@ -6,12 +6,12 @@ import { storyNameFromExport } from "storybook/internal/csf";
6
6
  import { logger } from "storybook/internal/node-logger";
7
7
  import * as v from "valibot";
8
8
  import { telemetry } from "storybook/internal/telemetry";
9
- import { addGetComponentDocumentationTool, addListAllComponentsTool } from "@storybook/mcp";
9
+ import { addGetDocumentationTool, addListAllDocumentationTool } from "@storybook/mcp";
10
10
  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.6";
14
+ var version = "0.1.8";
15
15
  var description = "Help agents automatically write and test stories for your UI components";
16
16
 
17
17
  //#endregion
@@ -209,16 +209,70 @@ const frameworkToRendererMap = {
209
209
  //#endregion
210
210
  //#region src/tools/is-manifest-available.ts
211
211
  const getManifestStatus = async (options) => {
212
- const [features, componentManifestGenerator] = await Promise.all([options.presets.apply("features"), options.presets.apply("experimental_componentManifestGenerator")]);
213
- const hasGenerator = !!componentManifestGenerator;
212
+ const [features, manifests] = await Promise.all([options.presets.apply("features"), options.presets.apply("experimental_manifests", void 0, { manifestEntries: [] })]);
213
+ const hasManifests = !!manifests;
214
214
  const hasFeatureFlag = !!features?.experimentalComponentsManifest;
215
215
  return {
216
- available: hasFeatureFlag && hasGenerator,
217
- hasGenerator,
216
+ available: hasFeatureFlag && hasManifests,
217
+ hasManifests,
218
218
  hasFeatureFlag
219
219
  };
220
220
  };
221
221
 
222
+ //#endregion
223
+ //#region src/utils/estimate-tokens.ts
224
+ /**
225
+ * Checks if a character code is whitespace (space, tab, newline, carriage return)
226
+ *
227
+ * Checking char codes is slightly faster than using regex or string methods.
228
+ */
229
+ function isWhitespace(code) {
230
+ return code === 32 || code === 9 || code === 10 || code === 13;
231
+ }
232
+ /**
233
+ * Checks if a character code is alphanumeric or underscore
234
+ * 0-9 (48-57), A-Z (65-90), a-z (97-122), underscore (95)
235
+ *
236
+ * Checking char codes is slightly faster than using regex or string methods.
237
+ */
238
+ function isAlphanumeric(code) {
239
+ return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95;
240
+ }
241
+ /**
242
+ * Estimates token count from text using a fast approximation.
243
+ * Counts:
244
+ * - Continuous whitespace as a single token
245
+ * - Continuous alphanumeric sequences as single tokens
246
+ * - Each special character as an individual token
247
+ *
248
+ * This is a cheap approximation suitable for telemetry purposes.
249
+ *
250
+ * @param text - The text to estimate token count for
251
+ * @returns Estimated token count
252
+ */
253
+ function estimateTokens(text) {
254
+ if (!text) return 0;
255
+ let tokenCount = 0;
256
+ let i = 0;
257
+ const len = text.length;
258
+ while (i < len) {
259
+ const code = text.charCodeAt(i);
260
+ if (isWhitespace(code)) {
261
+ tokenCount++;
262
+ i++;
263
+ while (i < len && isWhitespace(text.charCodeAt(i))) i++;
264
+ } else if (isAlphanumeric(code)) {
265
+ tokenCount++;
266
+ i++;
267
+ while (i < len && isAlphanumeric(text.charCodeAt(i))) i++;
268
+ } else {
269
+ tokenCount++;
270
+ i++;
271
+ }
272
+ }
273
+ return tokenCount;
274
+ }
275
+
222
276
  //#endregion
223
277
  //#region src/mcp-handler.ts
224
278
  let transport;
@@ -246,8 +300,8 @@ const initializeMCPServer = async (options) => {
246
300
  if ((await getManifestStatus(options)).available) {
247
301
  logger.info("Experimental components manifest feature detected - registering component tools");
248
302
  const contextAwareEnabled = () => server.ctx.custom?.toolsets?.docs ?? true;
249
- await addListAllComponentsTool(server, contextAwareEnabled);
250
- await addGetComponentDocumentationTool(server, contextAwareEnabled);
303
+ await addListAllDocumentationTool(server, contextAwareEnabled);
304
+ await addGetDocumentationTool(server, contextAwareEnabled);
251
305
  }
252
306
  transport = new HttpTransport(server, { path: null });
253
307
  origin = `http://localhost:${options.port}`;
@@ -266,21 +320,24 @@ const mcpServerHandler = async ({ req, res, options, addonOptions }) => {
266
320
  disableTelemetry,
267
321
  request: webRequest,
268
322
  ...!disableTelemetry && {
269
- onListAllComponents: async ({ manifest }) => {
323
+ onListAllDocumentation: async ({ manifests, resultText }) => {
270
324
  await collectTelemetry({
271
- event: "tool:listAllComponents",
325
+ event: "tool:listAllDocumentation",
272
326
  server,
273
327
  toolset: "docs",
274
- componentCount: Object.keys(manifest.components).length
328
+ componentCount: Object.keys(manifests.componentManifest.components).length,
329
+ docsCount: Object.keys(manifests.docsManifest?.docs || {}).length,
330
+ resultTokenCount: estimateTokens(resultText)
275
331
  });
276
332
  },
277
- onGetComponentDocumentation: async ({ input, foundComponent }) => {
333
+ onGetDocumentation: async ({ input, foundDocumentation, resultText }) => {
278
334
  await collectTelemetry({
279
- event: "tool:getComponentDocumentation",
335
+ event: "tool:getDocumentation",
280
336
  server,
281
337
  toolset: "docs",
282
- componentId: input.componentId,
283
- found: !!foundComponent
338
+ componentId: input.id,
339
+ found: !!foundDocumentation,
340
+ resultTokenCount: estimateTokens(resultText ?? "")
284
341
  });
285
342
  }
286
343
  }
@@ -341,7 +398,7 @@ function getToolsets(request, addonOptions) {
341
398
 
342
399
  //#endregion
343
400
  //#region src/template.html
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";
401
+ 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-documentation</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";
345
402
 
346
403
  //#endregion
347
404
  //#region src/preset.ts
@@ -368,7 +425,7 @@ const experimental_devServer = async (app, options) => {
368
425
  });
369
426
  res.writeHead(200, { "Content-Type": "text/html" });
370
427
  let docsNotice = "";
371
- if (!manifestStatus.hasGenerator) docsNotice = `<div class="toolset-notice">
428
+ if (!manifestStatus.hasManifests) docsNotice = `<div class="toolset-notice">
372
429
  This toolset is only supported in React-based setups.
373
430
  </div>`;
374
431
  else if (!manifestStatus.hasFeatureFlag) docsNotice = `<div class="toolset-notice">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storybook/addon-mcp",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Help agents automatically write and test stories for your UI components",
5
5
  "keywords": [
6
6
  "storybook-addon",
@@ -29,10 +29,10 @@
29
29
  "@tmcp/transport-http": "^0.8.0",
30
30
  "tmcp": "^1.16.0",
31
31
  "valibot": "1.2.0",
32
- "@storybook/mcp": "0.1.1"
32
+ "@storybook/mcp": "0.2.1"
33
33
  },
34
34
  "devDependencies": {
35
- "storybook": "10.1.2"
35
+ "storybook": "10.2.0-alpha.14"
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"