UNPKG

@storybook/addon-mcp

Version:

Help agents automatically write and test stories for your UI components

175 lines (172 loc) • 6.07 kB
// src/constants.ts var MCP_APP_PARAM = "mcp-app", MCP_APP_SIZE_CHANGED_EVENT = "storybook-mcp:size-changed"; // package.json var package_default = { name: "@storybook/addon-mcp", version: "10.6.5", description: "Help agents automatically write and test stories for your UI components", keywords: [ "ai", "mcp", "storybook-addon" ], homepage: "https://github.com/storybookjs/storybook/tree/next/code/addons/mcp", bugs: { url: "https://github.com/storybookjs/storybook/issues" }, repository: { type: "git", url: "https://github.com/storybookjs/storybook.git", directory: "code/addons/mcp" }, funding: { type: "opencollective", url: "https://opencollective.com/storybook" }, license: "MIT", author: "Storybook Core Team", type: "module", exports: { "./internal/preview-stories-app-script": "./dist/tools/preview-stories/preview-stories-app-script.js", "./package.json": "./package.json", "./preset": "./dist/preset.js" }, files: [ "dist/**/*", "README.md", "*.js", "*.d.ts", "!src/**/*" ], dependencies: { "@tmcp/adapter-valibot": "^0.1.5", "@tmcp/transport-http": "^0.8.5", tmcp: "^1.19.4", valibot: "^1.4.0" }, devDependencies: { "@storybook/addon-a11y": "workspace:*", "@storybook/addon-vitest": "workspace:*", storybook: "workspace:*", typescript: "^6.0.3" }, peerDependencies: { "@storybook/addon-vitest": "workspace:^", storybook: "workspace:^" }, peerDependenciesMeta: { "@storybook/addon-vitest": { optional: !0 } }, publishConfig: { access: "public" }, mcpName: "io.github.storybookjs/addon-mcp", storybook: { displayName: "Addon MCP", supportedFrameworks: [ "react", "vue3", "angular", "web-components", "html", "svelte", "preact", "react-native" ], icon: "https://user-images.githubusercontent.com/321738/63501763-88dbf600-c4cc-11e9-96cd-94adadc2fd72.png" } }; // src/tools/preview-stories/preview-stories-app-script.ts var LATEST_PROTOCOL_VERSION = "2025-11-21", METHODS = { // Lifecycle INITIALIZE: "ui/initialize", INITIALIZED: "ui/notifications/initialized", // Tool data (Host -> Guest) TOOL_INPUT: "ui/notifications/tool-input", TOOL_INPUT_PARTIAL: "ui/notifications/tool-input-partial", TOOL_RESULT: "ui/notifications/tool-result", TOOL_CANCELLED: "ui/notifications/tool-cancelled", // Context & UI HOST_CONTEXT_CHANGED: "ui/notifications/host-context-changed", SIZE_CHANGED: "ui/notifications/size-changed", RESOURCE_TEARDOWN: "ui/resource-teardown", // Standard MCP methods TOOLS_CALL: "tools/call", NOTIFICATIONS_MESSAGE: "notifications/message", OPEN_LINK: "ui/open-link", MESSAGE: "ui/message" }, nextId = 1; function sendHostRequest(method, params) { let id = nextId++, { promise, resolve, reject } = Promise.withResolvers(); return window.parent.postMessage({ jsonrpc: "2.0", id, method, params }, "*"), window.addEventListener("message", function listener(event) { event.data?.id === id && (window.removeEventListener("message", listener), event.data?.result ? resolve(event.data.result) : event.data?.error && reject(new Error(String(event.data.error)))); }), promise; } function sendHostNotification(method, params) { window.parent.postMessage({ jsonrpc: "2.0", method, params }, "*"); } function onHostNotification(method, handler) { window.addEventListener("message", function(event) { event.data?.method === method && handler(event.data.params); }); } var initializeResult = await sendHostRequest(METHODS.INITIALIZE, { appInfo: { name: "storybook-story-preview", version: package_default.version }, appCapabilities: {}, protocolVersion: LATEST_PROTOCOL_VERSION }); applyHostStyles(initializeResult?.hostContext); onHostNotification(METHODS.TOOL_RESULT, loadStoryIframes); onHostNotification(METHODS.HOST_CONTEXT_CHANGED, applyHostStyles); sendHostNotification(METHODS.INITIALIZED, {}); window.addEventListener("message", function(event) { if (event.data?.type !== MCP_APP_SIZE_CHANGED_EVENT) return; let iframes = document.querySelectorAll(".story-iframe"), hasResizedIframes = !1; for (let iframe of iframes) if (iframe.contentWindow === event.source) { iframe.style.height = (event.data.height ?? 0) + "px", hasResizedIframes = !0; break; } hasResizedIframes && resizeApp(); }); function applyHostStyles(hostContext) { if (hostContext?.theme && document.documentElement.setAttribute("data-theme", hostContext.theme), !!hostContext?.styles?.variables) { for (let [key, value] of Object.entries(hostContext.styles.variables)) value && document.documentElement.style.setProperty(key, value); resizeApp(); } } function resizeApp() { console.log("Resizing app to fit content", { width: document.body.scrollWidth, height: document.body.scrollHeight }), sendHostNotification(METHODS.SIZE_CHANGED, { width: document.body.scrollWidth, height: document.body.scrollHeight }); } function loadStoryIframes(params) { let stories = params.structuredContent?.stories; if (!stories || stories.length === 0) { console.warn("No preview URLs found in tool result."); return; } let template = document.getElementById("preview-template"); for (let storyResult of stories) { if ("error" in storyResult) { console.warn("Skipping story with error:", storyResult.error); continue; } let clone = template.content.cloneNode(!0), article = clone.querySelector("article"), heading = clone.querySelector("h1"), iframe = clone.querySelector("iframe"); heading.textContent = `${storyResult.title} - ${storyResult.name}`, iframe.style.width = "100%", iframe.style.height = "0"; let iframeSrc = storyResult.previewUrl.replace("/?path=/story/", "/iframe.html?id="), url = new URL(iframeSrc); url.searchParams.set(MCP_APP_PARAM, "true"), iframe.src = url.toString(), document.body.appendChild(article); } resizeApp(); }