feat(storybook): expose component docs to agents via official MCP addon (#3527) - #3736
feat(storybook): expose component docs to agents via official MCP addon (#3527)#3736chinawch007 wants to merge 2 commits into
Conversation
Astro-Han
left a comment
There was a problem hiding this comment.
I reviewed this head and found blocking issues.
[P2] MCP addon docs-only claim is not a server boundary and the endpoint is exposed on LAN
@storybook/addon-mcp replaces options via X-MCP-Toolsets (main.ts:45). The live tools/list with a dev header changes from 3 docs tools to 4 dev tools. The storybook script does not bind to loopback, so *:6006 is reachable from 10.7.0.13:6006 without auth — an on-LAN caller can re-enable the dev toolset and obtain a filesystem existence oracle (/etc/passwd vs pathNotFound).
Fix: bind to loopback (and --exact-port) and intersect request-selected toolsets with the configured allowlist; cover with header-injection tests.
[P2] Root .mcp.json is not discovered by Codex
AGENTS.md claims the endpoint is registered, but Codex discovers via .codex/config.toml [mcp_servers], not the Claude-style root JSON. Result: Codex sees the mandatory query instruction but has no tools.
Fix: ship a real project config for each claimed client or narrow the claim; verify via actual client discovery.
Checks on d75281cb67 are test: failure (unrelated CLI EOF backoff flake, but still red — not green). Code issues are independent of CI.
简体中文
存在局域网暴露与 Codex 发现失败两项阻断。|
Thanks — both P2s are addressed in d9671e0. Details per finding: [P2] Toolset override + LAN exposure
[P2] Root .mcp.json not discovered by Codex
CIThe red run is the CLI-workspace EOF-backoff flake you identified. Nothing in |
d9671e0 to
f3427b8
Compare
…on (apache#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
…pe (apache#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
f3427b8 to
9289175
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
Adds @storybook/addon-mcp to apps/desktop/.storybook/main.ts so an agent can query component docs over MCP instead of reading source, wires .mcp.json, pins the dev server with --exact-port, and adds a root AGENTS.md.
The plumbing is clean and I want to credit that before the findings. The addon goes into apps/desktop devDependencies, lock-pinned, and I confirmed it stays out of the shipped closure: third-party-closure.mjs:98-129 defines that closure as the prod closure plus maka.rendererBundledDependencies, and the addon is in neither, so audit-shipped-dependencies.mjs and the generated THIRD_PARTY_NOTICES are unaffected and there is no NOTICE obligation. The twelve transitive packages are MIT except one ISC, all ASF Category A. There is no CI install gap either — every isStorybookPath trigger in ci-test-plan.mjs:398-415 also sets code = true, which is what gates npm ci. Telemetry is a non-issue: the addon gates on core.disableTelemetry, which main.ts already sets.
The problem is not the plumbing. It is what this installs as an authority.
P1 — the root AGENTS.md is a tracked file at a path this project deliberately keeps untracked, and it is injected into Maka's own system prompt.
Two separate reasons this cannot land as part of this PR.
First, AGENTS.md is not tracked on main, and maintainer checkouts keep a local one via .git/info/exclude. Committing a tracked root AGENTS.md will make git pull refuse in every checkout that has a local file at that path. That is not a style objection; it breaks the working tree of anyone set up the current way.
Second, and more important: WORKSPACE_INSTRUCTION_FILES in packages/runtime/src/system-prompt/workspace-instructions.ts:40 is ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md']. Maka injects the project's AGENTS.md into the system prompt. So this file is not documentation — it is behavior, for every agent run inside this repository. And what it says is "Prefer composing existing packages/ui components. Introduce a new component only when nothing existing fits", with no mention of Astryx at all. Our actual authority is DESIGN.md:186, "Use Astryx primitives as the default seam", backed by the CI-gated surface inventory. An agent that reads only the injected instruction will build a new packages/ui component where DESIGN.md requires an Astryx primitive, and the surface-inventory gate will grade the result.
Please split the AGENTS.md out of this PR. If you want project instructions for agents, that is a worthwhile change but it is its own decision, it has to reconcile with DESIGN.md rather than restate it more weakly, and it needs the untracked-file collision resolved first.
P2 — the addon injects vendor-authored instructions that claim exclusive authority over component APIs, and our catalog cannot back that claim.
@storybook/addon-mcp's preset puts STORYBOOK_MCP_INSTRUCTIONS into the MCP server's instructions field, which clients inject into the system prompt. That text says to answer questions about component props and usage from the documentation tools and never from source or type definitions, and that if something is not documented, it does not exist.
Against our tree that instruction is actively wrong. Only 5 of 32 story files declare meta.component, there is no autodocs tag anywhere, and @astryxdesign/core — the primitive layer DESIGN.md makes the default seam — has no stories at all. So: an agent needs a busy button. DESIGN.md:229 requires the Astryx isLoading prop, "always — no hand-swapped labels, icons, or disable-plus-spinner recreations". The manifest has no such component, the injected instruction says the prop does not exist, and the agent hand-rolls a disable-plus-spinner. That is precisely the drift DESIGN.md exists to prevent, now instructed.
Unlike the AGENTS.md above, this string is vendor-controlled and can change on any npm update inside ^0.7.0, so it cannot be reviewed per change. If we want this addon, the honest sequencing is to get meta.component and autodocs coverage up — and Astryx covered at all — before an instruction that says "if it is not documented, it does not exist" is true enough to inject.
Three P3s:
- The
main.tscomment says the security boundary is the dev server's loopback binding. It is not. The addon'sX-MCP-Toolsets: devheader overrides the configured toolsets per request against registrations that are toolset-blind, and what actually blocks a malicious page today is that the transport is constructed with nocorsoption, so noAccess-Control-Allow-Originis emitted. The addon never validatesOrigin. Consequence is bounded — I found nofswrites in the bundle; the dev toolset's worst reach isexecSync('git status --porcelain')and story-source reads the Vite dev server already serves on that port. One thing I could not verify: whether Storybook's own host check runs before the addon's/mcproute. If it does not, DNS rebinding lands same-origin and the CORS absence buys nothing. Worth checking withcurl -X POST -H 'Host: evil.example' -H 'Origin: https://evil.example' -H 'X-MCP-Toolsets: dev' http://127.0.0.1:6006/mcpbefore merge. Please fix the comment either way — a wrong stated boundary is worse than none. --exact-portturns a port collision intoprocess.exit(-1)instead of falling forward to 6007. This project's workflow encourages multiple worktrees, andapps/desktop/tests/smoke.md:71documents that command. A deliberate trade for the hardcoded.mcp.jsonURL, but say so..mcp.jsonis unclassified byci-test-plan.mjs, so it falls through tounknownCode = trueand forces a full run. Any future.mcp.json-only PR runs the entire suite for nothing.
Ungraded notes: the body answers "does this PR entail a change in behavior?" with No, but the addon unconditionally sets features.componentsManifest: true and Storybook applies that to builds, so CI's build-storybook now generates component manifests — and the body does not claim build-storybook was run locally. "Exposes exactly the three docs tools" holds only for the default request path; the header override is disclosed in the main.ts comment but not in the summary. Separately, .gitattributes:8-11 export-ignores /.claude as repository-local agent configuration; .mcp.json is the same category and is not export-ignored, so it would ship in the ASF source artifact — my call to make, flagging it. Finally, npm run lint is Biome only while CI runs npx knip --workspace apps/desktop separately; the addon publishes no "." export and knip's storybook plugin routes addon names through deferred resolution. I could not verify whether that resolves cleanly — please confirm the knip step passes.
Root cause: right seam, wrong authority. Extending the existing Storybook config instead of building a parallel harness is the correct instinct. But what gets installed is a second, vendor-authored source of truth about component APIs, in a repository whose authority is DESIGN.md plus Astryx primitives plus the generated surface inventory — and then a root AGENTS.md restates that authority a third time, more weakly and without Astryx. One fact, three representations, one of them written by a third party and shipped inside a caret range.
AI use: Claude Code (Opus) produced the initial findings, including unpacking @storybook/addon-mcp@0.7.0 and its transitive tree to read the actual bundles and license set. I independently re-verified the P1 — that AGENTS.md is absent from origin/main, excluded via .git/info/exclude, and listed in WORKSPACE_INSTRUCTION_FILES — and the DESIGN.md authority lines it conflicts with. The DNS-rebinding ordering question is explicitly unverified. The reviewer of record reviewed and accepted this.
简体中文
在 apps/desktop/.storybook/main.ts 中加入 @storybook/addon-mcp,使 agent 可以通过 MCP 查询组件文档而非读源码;同时接入 .mcp.json、用 --exact-port 固定开发服务器端口,并新增根目录 AGENTS.md。
管道部分是干净的,先肯定这一点。addon 进的是 apps/desktop 的 devDependencies 并已锁定版本,我确认它不进入分发闭包:third-party-closure.mjs:98-129 把该闭包定义为生产闭包加 maka.rendererBundledDependencies,而 addon 两者都不在,因此 audit-shipped-dependencies.mjs 与生成的 THIRD_PARTY_NOTICES 不受影响,也没有 NOTICE 义务。十二个传递依赖除一个 ISC 外均为 MIT,都属 ASF Category A。CI 也没有安装缺口——ci-test-plan.mjs:398-415 中每个 isStorybookPath 触发都会同时置 code = true,而后者正是 npm ci 的门槛。遥测不是问题:addon 会检查 core.disableTelemetry,而 main.ts 已将其置为 true。
问题不在管道,而在于它安装了什么样的权威。
P1 —— 根目录 AGENTS.md 是一个被纳入版本控制的文件,而本项目刻意让该路径不受版本控制;并且它会被注入 Maka 自己的系统提示。
两个各自独立的理由使它不能作为本 PR 的一部分落地。
其一,AGENTS.md 在 main 上并未被跟踪,维护者的检出通过 .git/info/exclude 保留本地副本。提交一个被跟踪的根 AGENTS.md,会让所有在该路径存在本地文件的检出 git pull 失败。这不是风格意见,而是会破坏按现有方式配置者的工作树。
其二,也更重要:packages/runtime/src/system-prompt/workspace-instructions.ts:40 的 WORKSPACE_INSTRUCTION_FILES 是 ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md']。Maka 会把项目的 AGENTS.md 注入系统提示。因此该文件不是文档,而是行为,作用于本仓库内每一次 agent 运行。而它写的是"优先组合已有的 packages/ui 组件,只有在没有现成组件合适时才引入新组件",全文未提 Astryx。我们真正的权威是 DESIGN.md:186——"Use Astryx primitives as the default seam",并有 CI 把关的 surface inventory 支撑。只读到注入指令的 agent,会在 DESIGN.md 要求使用 Astryx 原语的地方新建 packages/ui 组件,然后被 surface-inventory 闸门判定。
请把 AGENTS.md 从本 PR 拆出。若你想为 agent 提供项目指令,这是有价值的改动,但它是独立决策,需要与 DESIGN.md 调和而非更弱地复述,并且要先解决未跟踪文件的冲突。
P2 —— addon 注入了由第三方撰写、宣称对组件 API 拥有排他权威的指令,而我们的目录支撑不了该主张。
@storybook/addon-mcp 的 preset 会把 STORYBOOK_MCP_INSTRUCTIONS 放进 MCP 服务器的 instructions 字段,客户端会将其注入系统提示。该文本要求:关于组件 props 与用法的问题一律用文档工具回答,绝不依据源码或类型定义;且"若未被文档记录,它就不存在"。
对照我们的代码树,这条指令是主动错误的。32 个 story 文件中只有 5 个声明了 meta.component,全仓没有任何 autodocs 标签,而 DESIGN.md 指定为默认接缝的原语层 @astryxdesign/core 完全没有 story。于是:agent 需要一个 busy 按钮,DESIGN.md:229 要求"always"使用 Astryx 的 isLoading prop,"不得手工替换文案、图标,或用 disable 加 spinner 重造"。而清单里没有这个组件,注入的指令说该 prop 不存在,agent 于是手搓了 disable 加 spinner——这正是 DESIGN.md 要防止的漂移,如今被写成了指令。
与上面的 AGENTS.md 不同,这个字符串由第三方控制,可能在 ^0.7.0 范围内的任何一次 npm update 中改变,无法逐次评审。若我们要用这个 addon,诚实的次序是:先把 meta.component 与 autodocs 覆盖率提上去、并让 Astryx 有覆盖,再注入"未记录即不存在"这类足够为真的指令。
三条 P3:
main.ts注释称安全边界是开发服务器的 loopback 绑定。并非如此。addon 的X-MCP-Toolsets: dev请求头可逐请求覆盖已配置的工具集,而注册本身对工具集无感知;今天真正阻挡恶意页面的,是传输层构造时未传cors选项,因而不会输出Access-Control-Allow-Origin。addon 从不校验Origin。后果是有界的——我在整个 bundle 中未发现fs写操作,dev 工具集最大触达是execSync('git status --porcelain')和 story 源码读取,而后者 Vite 开发服务器本就在同一端口提供。有一点我无法核实:Storybook 自身的 host 检查是否先于 addon 的/mcp路由运行。若否,DNS rebinding 将落在同源,CORS 的缺失也就毫无意义。合并前值得用curl -X POST -H 'Host: evil.example' -H 'Origin: https://evil.example' -H 'X-MCP-Toolsets: dev' http://127.0.0.1:6006/mcp验一下。无论结果如何请修正该注释——错误的边界声明比没有更糟。--exact-port把端口冲突从回退到 6007 变成process.exit(-1)。本项目的工作流鼓励多 worktree,且apps/desktop/tests/smoke.md:71记录了该命令。作为写死.mcp.jsonURL 的有意取舍可以接受,但请写明。.mcp.json未被ci-test-plan.mjs分类,会落到unknownCode = true而强制全量运行。将来任何只改.mcp.json的 PR 都会白跑整套测试。
未定级说明:正文对"本 PR 是否带来行为变化"回答 No,但 addon 无条件设置 features.componentsManifest: true,而 Storybook 会把它应用于构建,因此 CI 的 build-storybook 现在会生成组件清单——正文也未声明本地跑过 build-storybook。"仅暴露三个文档工具"只在默认请求路径成立;请求头覆盖在 main.ts 注释中有交代,但摘要里没有。另外 .gitattributes:8-11 将 /.claude 标为 export-ignore,理由是仓库本地的 agent 配置;.mcp.json 属同一类别却未被 export-ignore,会进入 ASF 源码制品——这由我来定,先标出。最后,npm run lint 只有 Biome,而 CI 另外跑 npx knip --workspace apps/desktop;该 addon 未发布 "." 导出,knip 的 storybook 插件对 addon 名走延迟解析。我无法核实它能否干净解析——请确认 knip 步骤通过。
根因:接缝对,权威错。 扩展既有 Storybook 配置而非另建一套 harness,直觉是对的。但被安装进来的,是关于组件 API 的第二个、由第三方撰写的事实来源,而本仓库的权威是 DESIGN.md 加 Astryx 原语加生成的 surface inventory;随后根 AGENTS.md 又把该权威第三次复述,更弱且不提 Astryx。一个事实,三份表示,其中一份由第三方撰写并随 caret 范围分发。
|
Thanks for the depth of this review — the "right seam, wrong authority" framing P1 — AGENTS.md: the issue's ask and the repo's reality are in conflictIssue #3527 explicitly asks for usage guidance in a root
So the dilemma is real: omitting the file leaves the issue's guidance ask One mitigating fact worth putting on the record: the core propagation the P2 — the injected instructions vs. our catalog coverageWe confirmed the injected string ("Undocumented props do not exist — ... not Coverage first. The cost is not writing the stories — it's the judgment
Local patch. Replacing the injected instructions with a repo-authored The call we'd like from maintainers: (1) merge the interface with the |
Summary
Agents writing UI code in this repo had no machine-readable interface to
learn which components exist and how to use them, so they grepped and read
component sources to guess props and usage. This PR self-hosts Storybook's
official MCP addon at
http://localhost:6006/mcp, exposing the docstoolset only, so agents can query the existing story catalog before
writing code.
apps/desktop: install@storybook/addon-mcpand register it in.storybook/main.tswithtoolsets: { dev: false, test: false, docs: true }(test toolset needs
@storybook/addon-vitestand stays a follow-up perthe issue)
.mcp.jsonregistering the endpoint for agent clientsAGENTS.md: start Storybook on demand, querylist-all-documentation/get-documentationbefore writing UI code,prefer existing
packages/uicomponentsFixes #3527
Verification
Manual verification succeeded against a running dev server:
npm --workspace @maka/desktop run storybook -- --no-openbootsunchanged; existing stories render as before
MCP
initializeoverPOST /mcpreturns a session idtools/listexposes exactly the three docs tools —list-all-documentation,get-documentation,get-documentation-for-story— confirming thedev/test toolsets are off:
all three tools return real content, e.g.
get-documentationforprimitives-toastreturns the story list with usage snippetsnpm run lintandnpm run format:checkpassNot run / known gaps: full
npm run buildandnpm test(config-onlychange, no unit suite covers Storybook config); desktop-workspace
typecheck reports 40 pre-existing errors that reproduce identically on
clean
mainin this environment (stale workspace dists) — none touch thechanged files, which typecheck clean.
Review focus
.mcp.jsonuses the"type": "http"+"url"client convention(Claude Code / Cursor-class agent CLIs), not an MCP spec format; it is
dev-time tooling config, unrelated to
packages/mcp's product config.$schemais omitted — no official schema covers this client format..github/copilot-instructions.md(code-review guidance only) and the newAGENTS.mdcover disjoint topics;AGENTS.mdis the cross-toolcanonical location. Mirroring into copilot-instructions.md is left as a
maintainer decision.
AI use
Select exactly one:
Tool(s) and scope: ZCode authored the config and documentation changes and
ran the verification above; the human contributor reviewed the result and
performed the final verification on a live server.
Checklist
Does this PR entail a change in behavior?