StyleKit 有 148 个设计风格,每个风格有 token 和组件配方。这些数据本来只存在于我的网站里,用户得打开网页、翻、复制、粘贴。
我给项目写了一个 MCP Server,发布到了 npm,现在 Claude Code 和 Cursor 里可以直接调。这篇讲清楚它到底是怎么工作的——不是抄协议文档,是讲我自己的代码。
一、它解决什么问题
先讲个失败场景。
我用 AI 写一个毛玻璃卡片,跟它说「用 glassmorphism 风格」。它会写出来一个半透明的方块——但它不知道我的规范里写着「玻璃要借光不带色」「顶边要有 inset 高光」。它写的东西看起来像玻璃,但不像我定义的玻璃。
我可以把规范粘贴到对话里,但每次都粘一遍很蠢,而且它记不全。
MCP 就是解决这个的:让你的外部能力变成一个 AI 可以主动调用的工具。
一句话说清 MCP:它是给 AI 应用用的「工具插座标准」。你把能力做成一个进程,任何支持 MCP 的 AI 客户端都能插上,不用为每个客户端单独写插件。
二、不用它会怎样
不用 MCP 的常见做法是:把 StyleKit 的 148 个风格全塞进 system prompt。
然后你会发现:
- 塞不进去。148 个风格每个有 philosophy、doList、dontList、tokens,全量是几十万字。
- 就算塞进去了,模型会开始瞎编。它记住了「neo-brutalist」这个名字,但记不准具体是
border-4还是border-2,于是写一个「差不多」的出来。 - 你的数据一改,prompt 就过期了。用户拿到的还是三个月前的规则。
核心问题是:AI 不需要知道全部,它需要在需要的时候能查到准确的那一条。
这就是 MCP 干的事。
三、代码是怎么做的
三个角色
| 名称 | 作用 | 例子 |
|---|---|---|
| Host | 你在用的 AI 应用 | Claude Code、Cursor、Windsurf |
| Client | Host 内部负责连 Server 的部分 | 内置,不用你写 |
| Server | 暴露工具和数据的进程 | 我写的 stylekit-mcp |
我只写了 Server。它跑在用户自己的电脑上,通过 stdio 和 Host 通信。packages/mcp/src/index.ts 一共 39 行:
const server = new McpServer({ name: "stylekit-mcp-server", version })
registerStyleKitTools(server)
const transport = new StdioServerTransport()
await server.connect(transport)
// stdout 留给协议,日志必须走 stderr
console.error("StyleKit MCP server running via stdio")最后那行注释很关键:stdio 模式下 stdout 被协议占用了,你往里 console.log 一句,协议就崩了。这是我踩过的坑。
六个工具
工具都注册在 packages/mcp/src/tools.ts(531 行)。真实的六个是:
| 工具名 | 干什么 |
|---|---|
stylekit_search_styles | 按关键词/分类搜风格,带分页 |
stylekit_get_style | 拿一个风格的完整档案:理念、配色、do/don't、质量信号 |
stylekit_get_style_tokens | 拿设计 token:边框、阴影、字体、间距、语义色 |
stylekit_get_component_recipe | 拿现成组件代码:className + JSX |
stylekit_get_shadcn_install | 拿一行 shadcn 安装命令 |
stylekit_lint_code | 检查代码是否真的符合风格规则 |
前五个是「给数据」,第六个是「做验证」。
为什么要有 stylekit_lint_code
这是我觉得设计得最有价值的一个。
模型生成 UI 代码之后,默认行为是认为自己写对了,不会回头检查。所以「给规则」本身不够——你给了规则,它不一定遵守,而且不知道自己没遵守。stylekit_lint_code 把这件事变成可验证的:
if (!knownSlug(slug)) return unknownSlug(slug)
if (!hasLintableRules(slug)) {
return errorResult(
`Style "${slug}" has no lint rules registered, so its code cannot be verified.`
)
}
const report = lintStyleCode(slug, code, { checkRequired })它的描述里我明确写了一句给模型看的话:
Use this AFTER generating or editing UI code to verify it matches the style, instead of assuming it does.
「而不是假设它写对了」——这就是整个工具的设计意图。它返回的不是 true/false,而是带原因的违例(rounded-xl (line 12) — 需要硬边直角,Fix: rounded-none),有 reason 有 fix,模型拿到能自己改。
工具的 description 是给模型看的说明书
这一点我一开始完全没意识到。
description 不是写给人看的注释,它是模型判断「什么时候该调这个工具」的唯一依据。模型只看到名字、描述、参数 schema。描述写得含糊,它就不知道什么时候该用。
所以我后来的工具描述都写了这几块:一句话说做什么、Args: 逐条解释参数、Returns JSON: 明确返回结构、Examples: 用自然语言举两三个触发例子。比如搜索工具里:
Examples:
- "find a glassy frosted style" -> query: "glass"
- "next page of retro styles" -> category: "retro", offset: 15这是「用户这么说的时候,你就该这么调」。模型靠这个做映射。
两个工程细节
返回既给人看,也给模型看。 每个工具同时返回 content(人类可读的 Markdown)和 structuredContent(有 schema 的结构化数据)。人看前者,模型拿后者。
错误要带下一步。 找不到风格时不返回空,而是 Unknown style "${slug}". Use stylekit_search_styles to find a valid slug. ——错误信息本身就在教模型怎么纠正。
一个我自己发现的不一致
写这篇时我回头对了一遍代码:tools.ts 里搜索工具的描述写着「146 design styles」,但注册表里实际是 148 个;packages/mcp/README.md 的工具表只列了 5 个,漏了后加的 stylekit_lint_code。
不严重,但说明一个道理:描述文件和代码是两处真相,只改一处就会不一致。
四、面试官会追问什么
问:MCP 和直接写个 CLI 有什么区别?
答:CLI 是给人用的,MCP 是给模型用的。差别在两点:一是描述——MCP 的每个工具要写清楚「什么时候调用、参数是什么」,模型靠这个决定调不调;二是返回结构——MCP 要额外给结构化的
structuredContent,让模型能可靠解析。工具本身的能力可以一模一样。
问:为什么工具描述写得那么长?
答:因为那是我唯一能影响模型调用决策的地方。模型看不到我的实现代码,只看到名字、描述、参数 schema。不写清楚使用场景和例子,它就会漏调或者乱调。
问:为什么专门做一个 lint 工具,模型不能自己判断吗?
答:不能。模型生成代码后的默认状态是「认为自己对」,不会主动回头验证。而且有些规则它记不住——比如某个风格禁用
rounded-xl。lint 把「假设写对了」变成「验证过是对的」,这是从不可靠到可靠的转变。
问:工具数量是不是越多越好?
答:不是。每个工具都占用模型上下文,多了它会选错。我这六个是按「先搜、再看详情、再拿配方、最后验证」这条实际使用链路来的,每个都有不可替代的位置。
五、一句话版本
MCP 是给 AI 用的工具插座标准;工具的描述是写给模型的说明书,而 lint 工具的意义是让模型从「以为写对了」变成「验证过是对的」。
六、自测
- 用自己的话说清 Host / Client / Server 三个角色分别是谁,以及为什么你只需要写 Server。
- 为什么 stdio 模式下不能用
console.log?往 stdout 写日志会发生什么? stylekit_lint_code和stylekit_get_style_tokens都在讲规则,它们的区别是什么?- 如果你要给自己的项目加第七个工具,你会怎么决定它该不该存在?
- 为什么工具返回要同时有
content和structuredContent?