@storybook/addon-mcp 0.1.4 → 0.1.5
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 -1
- package/dist/preset.js +28 -12
- package/package.json +2 -2
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.
|
|
14
|
+
var version = "0.1.5";
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
211
|
+
const getManifestStatus = async (options) => {
|
|
212
212
|
const [features, componentManifestGenerator] = await Promise.all([options.presets.apply("features"), options.presets.apply("experimental_componentManifestGenerator")]);
|
|
213
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Help agents automatically write and test stories for your UI components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"storybook-addon",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@storybook/mcp": "0.1.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"storybook": "10.1.
|
|
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"
|