Storybook MCP 按文件路径预览 Story:解析 914-preview-story-by-path 评测场景与 stories-preview 工具
本篇围绕 Storybook 仓库中 agent-eval 评测套件里的 914-preview-story-by-path 场景展开:它给 AI 编码 Agent 下达了一个两步任务——先阅读 stories 文件并说明其导入内容,再调用 Storybook MCP 的 preview 工具按「文件路径 + 导出名」预览 Primary 与 Secondary 两个 Button story。读完本文,你能理解 path-based story 选择器的参数契约、评测如何断言 Agent 的工具调用,以及该场景与 storyId 选择方式(915-preview-story-by-id)之间的设计取舍。
评测场景的定位
Storybook 仓库在 agent-eval 目录下内置了一套 Agent 评测套件:它在沙箱中运行编码 Agent(Claude Code / Codex),让 Agent 对着 fixture 项目执行 Storybook 工作流(编写 story、预览或审阅 story、运行 story 测试),然后断言 Agent 确实遵循了仓库提供的 MCP 工具或 plugin 技能所定义的工作流。每个评测场景是一个独立目录,由三部分组成:
| 文件 | 作用 |
|---|---|
PROMPT.md |
直接投喂给 Agent 的任务提示词 |
EVAL.ts |
vitest 测试,解析 Agent 的调用记录并断言工作流结果 |
package.json |
声明该评测使用的 fixture 模板(本场景为 reshaped-storybook) |
914-preview-story-by-path 属于 9xx 系列的「MCP 工具行为」评测,其核心考察点是:当用户以故事文件路径(而非 storyId)指代 story 时,Agent 能否正确使用 stories-preview 工具的路径形态选择器。
原始任务提示词(PROMPT.md)
PROMPT.md 全文如下,仅两句话、两步操作:
Read the stories file for the button at
stories/Button.stories.tsx, and tell me what that file imports.Then afterwards, show a preview of the Primary and Secondary button stories using the Storybook MCP preview tool.
拆解这个任务,它刻意包含两种不同性质的动作:
- 静态阅读:读取
stories/Button.stories.tsx,向用户报告该文件导入了什么。这一步不依赖 MCP,考察 Agent 的基础文件理解能力。 - 工具调用:使用 Storybook MCP 的 preview 工具,预览
Primary和Secondary两个 story。关键在于提示词给出的是文件路径 + 导出名(stories/Button.stories.tsx中的Primary/Secondary),而不是button--primary这类 storyId——因此 Agent 应当使用 preview 工具输入 schema 中的 path-based 形态。
Fixture 项目:任务中"正确答案"的来源
评测目录自带的 fixture 代码即任务的全部上下文。stories/Button.stories.tsx 完整内容如下:
import type { Meta, StoryObj } from '@storybook/react';
import Button from '../src/components/Button';
const meta = {
title: 'Example/Button',
component: Button,
tags: ['test'],
args: {
label: 'Click me',
disabled: false,
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
label: 'Primary',
},
};
export const Secondary: Story = {
args: {
label: 'Secondary',
},
};
对照提示词第一步,该文件共导入两处:@storybook/react 的 Meta、StoryObj 类型(type-only 导入),以及相对路径 ../src/components/Button 的默认导出 Button 组件。这个组件本身极简——一个受 label / disabled props 驱动的 <button>,带 data-testid="button-component" 属性,供后续断言或测试定位使用:
type ButtonProps = {
label: string;
disabled?: boolean;
};
export default function Button({ label, disabled = false }: ButtonProps) {
return (
<button type="button" disabled={disabled} data-testid="button-component">
{label}
</button>
);
}
meta.title 为 'Example/Button',因此两个 story 在 Storybook 中的完整定位是 Example/Button 下的 Primary、Secondary。tags: ['test'] 标记使其同时可被 story 测试工具链发现。fixture 的 package.json 只声明了 "template": "reshaped-storybook",说明沙箱会基于 agent-eval/templates/reshaped-storybook 模板搭建完整可运行的 Storybook 项目,再叠加这些 fixture 文件。
断言逻辑:EVAL.ts 如何验证 Agent
EVAL.ts 是该场景的验收测试,全文逻辑浓缩为四条断言:
const previewCalls = getWorkflowCalls('stories-preview');
expectWorkflowCalls(['stories-preview']);
expect(
previewCalls.some((call) =>
workflowCallIncludesStory(call, {
absoluteStoryPath: 'stories/Button.stories.tsx',
exportName: 'Primary',
})
)
).toBe(true);
expect(
previewCalls.some((call) =>
workflowCallIncludesStory(call, {
absoluteStoryPath: 'stories/Button.stories.tsx',
exportName: 'Secondary',
})
)
).toBe(true);
其中三个关键点值得注意:
-
expectWorkflowCalls(['stories-preview'])要求 Agent 的完整工作流恰好是调用stories-preview这一个工作流——不允许额外调用find-story-ids之类的探测工具来回绕,即提示词给的信息已足够,Agent 应直达预览。 -
逐 story 校验选择器形态:
workflowCallIncludesStory断言的期望对象同时携带absoluteStoryPath与exportName,而非storyId。测试里的注释明确说明了原因:Deliberately no storyId: this eval requires the path + export strategy, and workflowCallIncludesStory would accept a storyId match on its own.
也就是说,如果 Agent 偷懒改用 storyId 形态预览,该断言会失败——这正是"by-path"与姊妹评测
915-preview-story-by-id的分工所在。 -
some()语义:Primary 和 Secondary 可以出现在同一次调用的 stories 数组里,也可以分两次调用,只要每个 story 都被 path + export 形态覆盖即通过。
断言所依赖的工具函数定义在 agent-eval/lib/test-utils.ts,其中 StoryInputExpectation 类型精确刻画了两种可选的故事选择形态:
export type StoryInputExpectation = {
absoluteStoryPath?: string;
exportName?: string;
storyId?: string;
};
该文件同时负责从 Agent 的 shell 命令记录或 MCP 调用记录中提取 StorybookWorkflowCall(解析逻辑见 agent-eval/lib/shell-parse.ts),并在 plugin 与 mcp 两种集成方式间做归一化——因此同一份 EVAL.ts 同时约束 MCP 工具和 CLI plugin 两条路径。
工具侧实现:stories-preview 的输入契约
评测断言的"标准答案"来自 MCP 工具本身的 schema。核心定义在 code/core/src/shared/open-service/toolsets/stories/story-input.ts:storyInputSchema 是一个 union,提供两种互斥的故事选择形态。
形态一:路径 + 导出名(本评测考察的形态)
v.object({
exportName: /* The export name of the story from the story file... */,
explicitStoryName: v.optional(v.string()),
absoluteStoryPath: /* Absolute path to the story file... */,
...storyInputProps,
})
各字段的用途(结合 schema 中的描述文本):
absoluteStoryPath:故事文件的绝对路径;描述明确写道"仅当已有 story 文件上下文时与 exportName 一起使用"。exportName:story 在文件中的导出名(本例即Primary/Secondary)。schema 描述特意引导 Agent:只有当你"已经在编辑某个 .stories.* 文件、知道其中的导出名"时才用这种形态,否则应优先 storyId。explicitStoryName(可选):当 story 通过name属性设置了与导出名不同的显示名时使用,否则不设置。props(可选):覆盖 story 默认 args 的自定义 props;globals(可选):预览时的全局参数,如theme('dark'/'light')、locale('en'/'fr')、backgrounds(如{ value: '#000' })等横切关注点。
形态二:storyId
v.object({
storyId: /* for example "button--primary"... */,
...storyInputProps,
})
描述建议"不在具体 story 文件内工作时优先使用此形态",并提示 ID 应从 docs 工具(withStoryIds=true 或 show 操作)中获取。这正是 915-preview-story-by-id 评测对应的路径。
输出契约:预览成功/失败的结构定义在 code/core/src/shared/open-service/toolsets/stories/definition.ts 中。成功项必须返回 title、name 和 previewUrl 三个字段,其中 previewUrl 带有明确的 Agent 行为引导:
Direct URL to open the story preview. Include this URL in the final user-facing response so users can open it directly.
失败项则返回原始 input 与 error 字符串,保证 Agent 能定位是哪个 story 的预览请求失败。
两种选择策略的对照:914 vs 915
914-preview-story-by-path 与 agent-eval/evals/915-preview-story-by-id/PROMPT.md 构成一组对照实验。915 的提示词是:
Show a preview of two existing Button stories: Primary and Secondary.
Do not modify any component or story files.
两者的差异揭示了工具 schema 的设计意图:
| 维度 | 914(by path) | 915(by id) |
|---|---|---|
| 提示词给的信息 | 文件路径 stories/Button.stories.tsx + 导出名 |
仅组件与 story 名,不含文件路径 |
| 期望选择器形态 | absoluteStoryPath + exportName |
storyId |
| 隐含前置动作 | 阅读 stories 文件(顺带回答导入问题) | 通常需先从 story 索引中解析出 storyId |
| 断言策略 | 显式排斥 storyId 匹配 | 期望 storyId 匹配 |
从 schema 描述文本看,官方对二者的使用边界是清晰的:已持有 story 文件上下文时用路径形态,否则优先 storyId。914 评测正是把 Agent 钉在"已有文件上下文"这一分支上,验证它不会退化到先查索引、再按 ID 预览的绕路行为。
如何本地运行该评测
评测套件在仓库根目录以 yarn workspace 方式运行(见 agent-eval/README.md):
# 预览将要运行的内容,不产生 API 调用
yarn workspace agent-eval run eval:dry
# 只调试这一个评测
EVAL_ONLY=914-preview-story-by-path yarn workspace agent-eval run eval
README 同时强调了两条工程约束:一是默认只跑第一个核心评测,需 EVAL_EXTRA_EVALS=1 才跑完整工作流评测线,EVAL_ONLY 则用于单个评测的调试;二是本地运行前需在仓库根目录重新编译沙箱注入的 MCP 构建(yarn nx run-many -t compile --projects mcp,addon-mcp),过期的 dist 会在 preset 加载时崩溃,表面症状是沙箱就绪超时而非构建错误。此外,评测按 Agent(Claude Code / Codex)× 集成方式(mcp / plugin)× 模型与推理档位组合成实验矩阵(见 agent-eval/experiments 下的实验定义文件),EVAL.ts 会依据运行环境自动适配两种集成方式的调用记录解析。
小结
914-preview-story-by-path 场景用一个三步链条完整刻画了"按路径预览 story"的工作流:提示词以文件路径指代 story(PROMPT.md)→ fixture 提供可静态核对的 stories 文件与组件(Button.stories.tsx)→ 断言强制 stories-preview 调用携带 absoluteStoryPath + exportName 而非 storyId(EVAL.ts)。而该链条的最终语义锚点落在 MCP 工具的输入 schema 上(story-input.ts):path 形态要求调用方已持有故事文件上下文,storyId 形态则是无文件上下文时的首选。理解了这组评测,也就理解了 Storybook 为 Agent 设计的"最小惊讶"原则——用户以何种坐标指代 story,Agent 就应使用与之对应的选择器形态。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00