@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 +5 -8
- package/dist/preset.js +74 -17
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -191,18 +191,15 @@ export default {
|
|
|
191
191
|
};
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
#### 3. List All
|
|
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
|
|
198
|
+
#### 4. Get Documentation (`get-documentation`)
|
|
199
199
|
|
|
200
|
-
Retrieves detailed documentation for a specific component
|
|
200
|
+
Retrieves detailed documentation for a specific component or docs entry.
|
|
201
201
|
|
|
202
|
-
|
|
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 {
|
|
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.
|
|
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,
|
|
213
|
-
const
|
|
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 &&
|
|
217
|
-
|
|
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
|
|
250
|
-
await
|
|
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
|
-
|
|
323
|
+
onListAllDocumentation: async ({ manifests, resultText }) => {
|
|
270
324
|
await collectTelemetry({
|
|
271
|
-
event: "tool:
|
|
325
|
+
event: "tool:listAllDocumentation",
|
|
272
326
|
server,
|
|
273
327
|
toolset: "docs",
|
|
274
|
-
componentCount: Object.keys(
|
|
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
|
-
|
|
333
|
+
onGetDocumentation: async ({ input, foundDocumentation, resultText }) => {
|
|
278
334
|
await collectTelemetry({
|
|
279
|
-
event: "tool:
|
|
335
|
+
event: "tool:getDocumentation",
|
|
280
336
|
server,
|
|
281
337
|
toolset: "docs",
|
|
282
|
-
componentId: input.
|
|
283
|
-
found: !!
|
|
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-
|
|
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.
|
|
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.
|
|
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.
|
|
32
|
+
"@storybook/mcp": "0.2.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"storybook": "10.
|
|
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"
|