From a13c170259abb8246866a38c3516d762619cc1db Mon Sep 17 00:00:00 2001 From: chinawch007 Date: Tue, 25 Aug 2026 03:17:39 +0800 Subject: [PATCH 1/2] feat(storybook): expose component docs to agents via official MCP addon (#3527) Self-host the Storybook MCP endpoint at http://localhost:6006/mcp with only the docs toolset enabled: - install @storybook/addon-mcp in apps/desktop and register it in .storybook/main.ts with dev/test toolsets off (test needs @storybook/addon-vitest and stays a follow-up per the issue) - register the endpoint for agent clients in a new root .mcp.json ("type": "http" client convention; $schema omitted since no official schema covers this client config format) - add AGENTS.md guidance to query the MCP before writing UI code in packages/ui or apps/desktop, preferring existing components Verified: storybook boots unchanged, tools/list exposes exactly the three docs tools, and list-all-documentation / get-documentation / get-documentation-for-story return real story docs over the endpoint. Generated-by: ZCode --- .mcp.json | 8 ++ AGENTS.md | 50 +++++++++ apps/desktop/.storybook/main.ts | 15 +++ apps/desktop/package.json | 1 + package-lock.json | 173 ++++++++++++++++++++++++++++++++ 5 files changed, 247 insertions(+) create mode 100644 .mcp.json create mode 100644 AGENTS.md diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000000..51cb758767 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "storybook": { + "type": "http", + "url": "http://localhost:6006/mcp" + } + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..a2f6683641 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,50 @@ + + +# AGENTS.md + +Guidance for AI coding agents working in this repository. + +## Frontend work: query the Storybook MCP before writing UI code + +UI code lives in `packages/ui` (shared components) and `apps/desktop` (desktop +app). Before writing or modifying UI code, discover what already exists through +the Storybook MCP server instead of reading component sources to guess props +and usage: + +1. Start Storybook on demand (dev-only server; nothing auto-starts it): + + ``` + npm --workspace @maka/desktop run storybook -- --no-open + ``` + +2. Connect to the MCP endpoint at `http://localhost:6006/mcp` (registered for + agents in the root `.mcp.json`). The endpoint only responds while Storybook + is running; if it is unreachable, start it as above. + +3. Query, then write: + + - `list-all-documentation` — every documented story/component + - `get-documentation-for-story` — props and usage for one story + - `get-documentation` — docs entry for a component + +Prefer composing existing `packages/ui` components. Introduce a new component +only when nothing existing fits, and give it a story so it becomes discoverable +through the same interface. Avoid adding third-party UI dependencies without a +clear need. diff --git a/apps/desktop/.storybook/main.ts b/apps/desktop/.storybook/main.ts index 8aa012a7dd..4c4816d79c 100644 --- a/apps/desktop/.storybook/main.ts +++ b/apps/desktop/.storybook/main.ts @@ -35,6 +35,21 @@ const config: StorybookConfig = { '../../../packages/ui/stories/**/*.stories.@(ts|tsx)', resolve(REPO_ROOT, 'apps/desktop/stories/**/*.stories.@(ts|tsx)'), ], + addons: [ + { + name: '@storybook/addon-mcp', + options: { + // Issue #3527: agents get a queryable component-docs interface. + // Docs toolset only; dev/test toolsets are deliberate follow-ups + // (test additionally needs @storybook/addon-vitest). + toolsets: { + dev: false, + test: false, + docs: true, + }, + }, + }, + ], framework: { name: '@storybook/react-vite', options: {}, diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 7551ebbc5c..a6854223ba 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -66,6 +66,7 @@ "@maka/ui": "0.1.0", "@modelcontextprotocol/sdk": "^1.26.0", "@playwright/test": "^1.62.1", + "@storybook/addon-mcp": "^0.7.0", "@storybook/react-vite": "^10.5.10", "@types/react": "^19.2.18", "@types/react-dom": "^19.2.4", diff --git a/package-lock.json b/package-lock.json index a7755125cd..e40889aa39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,6 +67,7 @@ "@maka/ui": "0.1.0", "@modelcontextprotocol/sdk": "^1.26.0", "@playwright/test": "^1.62.1", + "@storybook/addon-mcp": "^0.7.0", "@storybook/react-vite": "^10.5.10", "@types/react": "^19.2.18", "@types/react-dom": "^19.2.4", @@ -3721,6 +3722,30 @@ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "license": "MIT" }, + "node_modules/@storybook/addon-mcp": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@storybook/addon-mcp/-/addon-mcp-0.7.0.tgz", + "integrity": "sha512-f/IWGRMzWynBg5kDJ3DYvvnafuSX88kykGNFzzLOlkLVpfxEZoAmQF6AS47tw25GuH6EFIqSo6ZDBLHLVyZ/IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/mcp": "0.8.0", + "@tmcp/adapter-valibot": "^0.1.5", + "@tmcp/transport-http": "^0.8.5", + "picoquery": "^2.5.0", + "tmcp": "^1.19.4", + "valibot": "1.2.0" + }, + "peerDependencies": { + "@storybook/addon-vitest": "^0.0.0-0 || ^9.1.16 || ^10.0.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 || ^10.5.0-0", + "storybook": "^0.0.0-0 || ^9.1.16 || ^10.0.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 || ^10.5.0-0" + }, + "peerDependenciesMeta": { + "@storybook/addon-vitest": { + "optional": true + } + } + }, "node_modules/@storybook/builder-vite": { "version": "10.5.10", "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.5.10.tgz", @@ -3792,6 +3817,19 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@storybook/mcp": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@storybook/mcp/-/mcp-0.8.0.tgz", + "integrity": "sha512-G+XDgoWGrE98moXqKSee8fQyMQxoIWxRAbPG8r/HQ95pKodratevDqNyOgW+t6ZigM6AAVN1WHiFc5MI+hc8sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tmcp/adapter-valibot": "^0.1.5", + "@tmcp/transport-http": "^0.8.5", + "tmcp": "^1.19.4", + "valibot": "1.2.0" + } + }, "node_modules/@storybook/react": { "version": "10.5.10", "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.5.10.tgz", @@ -3974,6 +4012,77 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@tmcp/adapter-valibot": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@tmcp/adapter-valibot/-/adapter-valibot-0.1.6.tgz", + "integrity": "sha512-drirZeNinhYLiRSMksN+m//u0ImFxtGRk1Vp425Xp/7CbBXFQdjAG+f7grssyHAukbVTGzmWsMMP6ejrGVErUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@valibot/to-json-schema": "^1.3.0", + "valibot": "^1.1.0" + }, + "peerDependencies": { + "tmcp": "^1.17.0", + "valibot": "^1.1.0" + } + }, + "node_modules/@tmcp/adapter-valibot/node_modules/@valibot/to-json-schema": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@valibot/to-json-schema/-/to-json-schema-1.7.1.tgz", + "integrity": "sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "valibot": "^1.4.0" + } + }, + "node_modules/@tmcp/adapter-valibot/node_modules/valibot": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.4.2.tgz", + "integrity": "sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@tmcp/session-manager": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@tmcp/session-manager/-/session-manager-0.2.2.tgz", + "integrity": "sha512-UrCRpTsxh5XnMbplspvftEYboiZWgAiXqqAUbyFTHoHMJ0LoNDy8bQd0+7qtxtT4S5Qsnv650gvs/Nbec5NTCQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "tmcp": "^1.16.3" + } + }, + "node_modules/@tmcp/transport-http": { + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/@tmcp/transport-http/-/transport-http-0.8.6.tgz", + "integrity": "sha512-iLcxu+tEMbkVHbhFfyXQhxfPDDTfm+F0kEw8Xg/a1rm29s4cBg1vwcpbtk02XTxsdDh8RJ1AZkQwF9WDGeb/IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tmcp/session-manager": "^0.2.2", + "esm-env": "^1.2.2" + }, + "peerDependencies": { + "@tmcp/auth": "^0.3.3 || ^0.4.0", + "tmcp": "^1.18.0" + }, + "peerDependenciesMeta": { + "@tmcp/auth": { + "optional": true + } + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", @@ -7623,6 +7732,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -9009,6 +9125,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-rpc-2.0": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/json-rpc-2.0/-/json-rpc-2.0-1.7.1.tgz", + "integrity": "sha512-JqZjhjAanbpkXIzFE7u8mE/iFblawwlXtONaCvRqI+pyABVz7B4M1EUNpyVW+dZjqgQ2L5HFmZCmOCgUKm00hg==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", @@ -11087,6 +11210,13 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/picoquery": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/picoquery/-/picoquery-2.5.0.tgz", + "integrity": "sha512-j1kgOFxtaCyoFCkpoYG2Oj3OdGakadO7HZ7o5CqyRazlmBekKhbDoUnNnXASE07xSY4nDImWZkrZv7toSxMi/g==", + "dev": true, + "license": "MIT" + }, "node_modules/pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -12497,6 +12627,13 @@ "license": "BSD-3-Clause", "optional": true }, + "node_modules/sqids": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/sqids/-/sqids-0.3.0.tgz", + "integrity": "sha512-lOQK1ucVg+W6n3FhRwwSeUijxe93b51Bfz5PMRMihVf1iVkl82ePQG7V5vwrhzB11v0NtsR25PSZRGiSomJaJw==", + "dev": true, + "license": "MIT" + }, "node_modules/stat-mode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", @@ -13190,6 +13327,20 @@ "node": ">=14.0.0" } }, + "node_modules/tmcp": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/tmcp/-/tmcp-1.20.0.tgz", + "integrity": "sha512-dcDximKQBGqLP/aEAVA26HcWRHhKipstg1w0fbDDJ0HcFZ6lolLU1YYmStYEn/73f534i7WrDNcjRfRYdV9CoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "json-rpc-2.0": "^1.7.1", + "sqids": "^0.3.0", + "uri-template-matcher": "^1.1.1", + "valibot": "^1.1.0" + } + }, "node_modules/tmp": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", @@ -13501,6 +13652,13 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-template-matcher": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/uri-template-matcher/-/uri-template-matcher-1.1.2.tgz", + "integrity": "sha512-uZc1h12jdO3m/R77SfTEOuo6VbMhgWznaawKpBjRGSJb7i91x5PgI37NQJtG+Cerxkk0yr1pylBY2qG1kQ+aEQ==", + "dev": true, + "license": "ISC" + }, "node_modules/use-sync-external-store": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", @@ -13538,6 +13696,21 @@ "uuid": "dist-node/bin/uuid" } }, + "node_modules/valibot": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz", + "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", From 928917553ce7e4defd33a9c0890e9e0330b4804e Mon Sep 17 00:00:00 2001 From: chinawch007 Date: Tue, 25 Aug 2026 17:28:13 +0800 Subject: [PATCH 2/2] fix(storybook): bind dev server to loopback and stop overclaiming scope (#3527) Review follow-up addressing the two P2s: - storybook script now passes --host 127.0.0.1 --exact-port, so the unauthenticated /mcp endpoint is loopback-only. The X-MCP-Toolsets override itself is addon-level behavior (see getToolsets() in @storybook/addon-mcp) and needs an upstream fix; with loopback-only binding a caller already has direct local filesystem access, so the override grants nothing it could not do directly. - .storybook/main.ts comment now states the toolsets config is a default selection, not an allowlist. - AGENTS.md no longer claims universal .mcp.json registration: clients that read the root .mcp.json auto-discover the endpoint; other clients (Codex, Cursor) register the same URL in their own config. Verified: lsof reports 127.0.0.1:6006 (previously *:6006); MCP initialize, tools/list, and get-documentation still work over both localhost and 127.0.0.1; lint and format:check pass. Generated-by: ZCode --- AGENTS.md | 9 ++++++--- apps/desktop/.storybook/main.ts | 7 +++++-- apps/desktop/package.json | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a2f6683641..17450b7a2d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,9 +34,12 @@ and usage: npm --workspace @maka/desktop run storybook -- --no-open ``` -2. Connect to the MCP endpoint at `http://localhost:6006/mcp` (registered for - agents in the root `.mcp.json`). The endpoint only responds while Storybook - is running; if it is unreachable, start it as above. +2. Connect to the MCP endpoint at `http://localhost:6006/mcp` (loopback + only, by design). Agent clients that read the root `.mcp.json` discover + it automatically; other clients (e.g. Codex, Cursor) need a one-time + registration in their own MCP config pointing at the same URL. The + endpoint only responds while Storybook is running; if it is unreachable, + start it as above. 3. Query, then write: diff --git a/apps/desktop/.storybook/main.ts b/apps/desktop/.storybook/main.ts index 4c4816d79c..61d42a6469 100644 --- a/apps/desktop/.storybook/main.ts +++ b/apps/desktop/.storybook/main.ts @@ -40,8 +40,11 @@ const config: StorybookConfig = { name: '@storybook/addon-mcp', options: { // Issue #3527: agents get a queryable component-docs interface. - // Docs toolset only; dev/test toolsets are deliberate follow-ups - // (test additionally needs @storybook/addon-vitest). + // Docs is the default toolset selection, not an allowlist: the addon + // lets callers override it per request via the X-MCP-Toolsets + // header, so the security boundary is the dev server's loopback + // binding (see the storybook script in package.json). dev/test stay + // off by default; test additionally needs @storybook/addon-vitest. toolsets: { dev: false, test: false, diff --git a/apps/desktop/package.json b/apps/desktop/package.json index a6854223ba..a8498c9833 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -13,7 +13,7 @@ "prepare:dev-app": "node scripts/prepare-dev-app.mjs", "dev": "node scripts/dev.mjs", "dev:hmr": "node scripts/dev.mjs", - "storybook": "storybook dev -p 6006 -c .storybook", + "storybook": "storybook dev -p 6006 -c .storybook --host 127.0.0.1 --exact-port", "build-storybook": "storybook build -c .storybook --output-dir storybook-static", "smoke:storybook": "node ../../scripts/storybook-visual-smoke.mjs", "build": "npm run build:resources && npm run build:main && npm run build:preload && npm run build:overlay && npm run build:renderer",