@storybook/addon-mcp 0.1.4-next.2 → 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 +8 -4
- package/dist/preset.js +71 -43
- package/package.json +7 -16
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="
|
|
10
|
+
<img src="./addon-mcp-claude-code-showcase.gif" alt="Storybook MCP Addon Demo" />
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
13
|
## Getting Started
|
|
@@ -48,11 +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
|
+
experimentalFormat: 'markdown', // Output format: 'markdown' (default) or 'xml'
|
|
53
54
|
},
|
|
54
55
|
},
|
|
55
56
|
],
|
|
57
|
+
features: {
|
|
58
|
+
experimentalComponentsManifest: true, // Enable manifest generation for the docs toolset, only supported in React-based setups.
|
|
59
|
+
},
|
|
56
60
|
};
|
|
57
61
|
```
|
|
58
62
|
|
|
@@ -193,12 +197,12 @@ Returns a list of all available UI components in your component library. Useful
|
|
|
193
197
|
|
|
194
198
|
#### 4. Get Component Documentation (`get-component-documentation`)
|
|
195
199
|
|
|
196
|
-
Retrieves detailed documentation for specific
|
|
200
|
+
Retrieves detailed documentation for a specific component, including:
|
|
197
201
|
|
|
198
202
|
- Component documentation
|
|
199
203
|
- Usage examples
|
|
200
204
|
|
|
201
|
-
The agent provides component
|
|
205
|
+
The agent provides a component ID to retrieve its documentation. To get documentation for multiple components, call this tool multiple times.
|
|
202
206
|
|
|
203
207
|
## Contributing
|
|
204
208
|
|
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.
|
|
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
|
|
18
18
|
//#region src/telemetry.ts
|
|
19
|
-
async function collectTelemetry({ event, server
|
|
19
|
+
async function collectTelemetry({ event, server, ...payload }) {
|
|
20
20
|
try {
|
|
21
21
|
return await telemetry("addon-mcp", {
|
|
22
22
|
event,
|
|
@@ -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
|
|
|
@@ -69,13 +69,16 @@ const errorToMCPContent = (error) => {
|
|
|
69
69
|
|
|
70
70
|
//#endregion
|
|
71
71
|
//#region src/types.ts
|
|
72
|
-
const AddonOptions = v.object({
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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);
|
|
@@ -123,7 +126,7 @@ async function addGetStoryUrlsTool(server) {
|
|
|
123
126
|
});
|
|
124
127
|
const foundStoryId = entriesList.find((entry) => entry.importPath === relativePath && [explicitStoryName, storyNameFromExport(exportName)].includes(entry.name))?.id;
|
|
125
128
|
if (foundStoryId) {
|
|
126
|
-
logger.debug(
|
|
129
|
+
logger.debug(`Found story ID: ${foundStoryId}`);
|
|
127
130
|
result.push(`${origin$1}/?path=/story/${foundStoryId}`);
|
|
128
131
|
foundStoryCount++;
|
|
129
132
|
} else {
|
|
@@ -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//
|
|
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;
|
|
@@ -203,9 +208,15 @@ const frameworkToRendererMap = {
|
|
|
203
208
|
|
|
204
209
|
//#endregion
|
|
205
210
|
//#region src/tools/is-manifest-available.ts
|
|
206
|
-
const
|
|
211
|
+
const getManifestStatus = async (options) => {
|
|
207
212
|
const [features, componentManifestGenerator] = await Promise.all([options.presets.apply("features"), options.presets.apply("experimental_componentManifestGenerator")]);
|
|
208
|
-
|
|
213
|
+
const hasGenerator = !!componentManifestGenerator;
|
|
214
|
+
const hasFeatureFlag = !!features?.experimentalComponentsManifest;
|
|
215
|
+
return {
|
|
216
|
+
available: hasFeatureFlag && hasGenerator,
|
|
217
|
+
hasGenerator,
|
|
218
|
+
hasFeatureFlag
|
|
219
|
+
};
|
|
209
220
|
};
|
|
210
221
|
|
|
211
222
|
//#endregion
|
|
@@ -213,7 +224,9 @@ const isManifestAvailable = async (options) => {
|
|
|
213
224
|
let transport;
|
|
214
225
|
let origin;
|
|
215
226
|
let initialize;
|
|
227
|
+
let disableTelemetry;
|
|
216
228
|
const initializeMCPServer = async (options) => {
|
|
229
|
+
disableTelemetry = (await options.presets.apply("core", {}))?.disableTelemetry ?? false;
|
|
217
230
|
const server = new McpServer({
|
|
218
231
|
name,
|
|
219
232
|
version,
|
|
@@ -222,15 +235,15 @@ const initializeMCPServer = async (options) => {
|
|
|
222
235
|
adapter: new ValibotJsonSchemaAdapter(),
|
|
223
236
|
capabilities: { tools: { listChanged: true } }
|
|
224
237
|
}).withContext();
|
|
225
|
-
server.on("initialize", async () => {
|
|
226
|
-
|
|
238
|
+
if (!disableTelemetry) server.on("initialize", async () => {
|
|
239
|
+
await collectTelemetry({
|
|
227
240
|
event: "session:initialized",
|
|
228
241
|
server
|
|
229
242
|
});
|
|
230
243
|
});
|
|
231
244
|
await addGetStoryUrlsTool(server);
|
|
232
245
|
await addGetUIBuildingInstructionsTool(server);
|
|
233
|
-
if (await
|
|
246
|
+
if ((await getManifestStatus(options)).available) {
|
|
234
247
|
logger.info("Experimental components manifest feature detected - registering component tools");
|
|
235
248
|
const contextAwareEnabled = () => server.ctx.custom?.toolsets?.docs ?? true;
|
|
236
249
|
await addListAllComponentsTool(server, contextAwareEnabled);
|
|
@@ -238,35 +251,36 @@ const initializeMCPServer = async (options) => {
|
|
|
238
251
|
}
|
|
239
252
|
transport = new HttpTransport(server, { path: null });
|
|
240
253
|
origin = `http://localhost:${options.port}`;
|
|
241
|
-
logger.debug(
|
|
254
|
+
logger.debug(`MCP server origin: ${origin}`);
|
|
242
255
|
return server;
|
|
243
256
|
};
|
|
244
257
|
const mcpServerHandler = async ({ req, res, options, addonOptions }) => {
|
|
245
|
-
const disableTelemetry = options.disableTelemetry ?? false;
|
|
246
258
|
if (!initialize) initialize = initializeMCPServer(options);
|
|
247
259
|
const server = await initialize;
|
|
248
260
|
const webRequest = await incomingMessageToWebRequest(req);
|
|
249
261
|
const addonContext = {
|
|
250
262
|
options,
|
|
251
263
|
toolsets: getToolsets(webRequest, addonOptions),
|
|
264
|
+
format: addonOptions.experimentalFormat,
|
|
252
265
|
origin,
|
|
253
266
|
disableTelemetry,
|
|
254
|
-
|
|
267
|
+
request: webRequest,
|
|
255
268
|
...!disableTelemetry && {
|
|
256
269
|
onListAllComponents: async ({ manifest }) => {
|
|
257
270
|
await collectTelemetry({
|
|
258
271
|
event: "tool:listAllComponents",
|
|
259
272
|
server,
|
|
273
|
+
toolset: "docs",
|
|
260
274
|
componentCount: Object.keys(manifest.components).length
|
|
261
275
|
});
|
|
262
276
|
},
|
|
263
|
-
onGetComponentDocumentation: async ({ input,
|
|
277
|
+
onGetComponentDocumentation: async ({ input, foundComponent }) => {
|
|
264
278
|
await collectTelemetry({
|
|
265
279
|
event: "tool:getComponentDocumentation",
|
|
266
280
|
server,
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
281
|
+
toolset: "docs",
|
|
282
|
+
componentId: input.componentId,
|
|
283
|
+
found: !!foundComponent
|
|
270
284
|
});
|
|
271
285
|
}
|
|
272
286
|
}
|
|
@@ -327,28 +341,42 @@ function getToolsets(request, addonOptions) {
|
|
|
327
341
|
|
|
328
342
|
//#endregion
|
|
329
343
|
//#region src/template.html
|
|
330
|
-
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";
|
|
331
345
|
|
|
332
346
|
//#endregion
|
|
333
347
|
//#region src/preset.ts
|
|
334
348
|
const experimental_devServer = async (app, options) => {
|
|
335
|
-
const addonOptions = v.parse(AddonOptions, {
|
|
349
|
+
const addonOptions = v.parse(AddonOptions, {
|
|
350
|
+
toolsets: "toolsets" in options ? options.toolsets : {},
|
|
351
|
+
experimentalFormat: "experimentalFormat" in options ? options.experimentalFormat : "markdown"
|
|
352
|
+
});
|
|
336
353
|
app.post("/mcp", (req, res) => mcpServerHandler({
|
|
337
354
|
req,
|
|
338
355
|
res,
|
|
339
356
|
options,
|
|
340
357
|
addonOptions
|
|
341
358
|
}));
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
359
|
+
const manifestStatus = await getManifestStatus(options);
|
|
360
|
+
const isDevEnabled = addonOptions.toolsets?.dev ?? true;
|
|
361
|
+
const isDocsEnabled = manifestStatus.available && (addonOptions.toolsets?.docs ?? true);
|
|
362
|
+
app.get("/mcp", (req, res) => {
|
|
363
|
+
if (!req.headers["accept"]?.includes("text/html")) return mcpServerHandler({
|
|
364
|
+
req,
|
|
365
|
+
res,
|
|
366
|
+
options,
|
|
367
|
+
addonOptions
|
|
368
|
+
});
|
|
369
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
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);
|
|
379
|
+
res.end(html);
|
|
352
380
|
});
|
|
353
381
|
return app;
|
|
354
382
|
};
|
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",
|
|
@@ -28,19 +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": "
|
|
32
|
-
"@storybook/mcp": "0.
|
|
31
|
+
"valibot": "1.1.0",
|
|
32
|
+
"@storybook/mcp": "0.1.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"
|
|
36
|
-
"@vitest/coverage-v8": "3.2.4",
|
|
37
|
-
"publint": "0.3.15",
|
|
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.2"
|
|
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,11 +61,10 @@
|
|
|
69
61
|
},
|
|
70
62
|
"scripts": {
|
|
71
63
|
"prebuild": "node -e \"fs.rmSync('./dist', { recursive: true, force: true })\"",
|
|
72
|
-
"build": "tsdown
|
|
73
|
-
"dev": "pnpm run build -- --watch",
|
|
64
|
+
"build": "tsdown",
|
|
74
65
|
"inspect": "mcp-inspector --config ../../.mcp.inspect.json --server storybook-addon-mcp",
|
|
75
66
|
"publint": "publint",
|
|
76
|
-
"test": "vitest",
|
|
77
|
-
"typecheck": "tsc
|
|
67
|
+
"test": "cd ../.. && pnpm vitest --project=@storybook/addon-mcp",
|
|
68
|
+
"typecheck": "tsc"
|
|
78
69
|
}
|
|
79
70
|
}
|