Read OSS

AI 优先的组件发现机制:MCP Server 与编程式 Registry API

中级

前置知识

  • 第 1 篇:架构概览
  • 第 2 篇:registry 系统与依赖解析
  • 对 Model Context Protocol(MCP)概念有基本了解

AI 优先的组件发现机制:MCP Server 与编程式 Registry API

shadcn/ui CLI 从一开始就不只是为人类用户设计的工具。正如第 1 篇所介绍的,这个包通过 7 个子路径导出,服务于三类不同的使用者。本系列的最后一篇文章,将重点聚焦于其中最具前瞻性的接入面:允许 AI 编程助手发现、检查和安装组件的 MCP server,以及可供库作者直接导入使用的编程式 registry API。

MCP Server 架构

位于 packages/shadcn/src/mcp/index.ts 的 MCP server 基于 @modelcontextprotocol/sdk 构建。它创建了一个具备 tools 能力的 Server 实例,并注册了 7 个工具:

graph TD
    MCP["MCP Server (shadcn)"]
    MCP --> T1["get_project_registries"]
    MCP --> T2["list_items_in_registries"]
    MCP --> T3["search_items_in_registries"]
    MCP --> T4["view_items_in_registries"]
    MCP --> T5["get_item_examples_from_registries"]
    MCP --> T6["get_add_command_for_items"]
    MCP --> T7["get_audit_checklist"]

每个工具都有一个 Zod 输入 schema,并通过 zod-to-json-schema 转换为 MCP 协议所需的 JSON Schema 格式。Server 以 stdio 进程的方式运行——执行 shadcn mcp 时,它会通过 StdioServerTransport 建立连接并等待工具调用。

CallToolRequestSchema 处理器中的错误处理逻辑(第 413–462 行)展示了第 2 篇中介绍的 registry 错误系统与 MCP 响应之间的桥接方式。RegistryError 实例会携带错误码、消息、建议和上下文一并格式化输出,为 AI 助手提供足够的信息来诊断问题并给出修复建议。

7 个 MCP 工具:发现 → 检查 → 安装

这 7 个工具构成了一套自然递进的工作流:

1. get_project_registries — 读取 components.json 并返回已配置的 registry 名称。这通常是 AI 了解可用资源时发起的第一个调用。

2. list_items_in_registries — 分页列出指定 registry 中的所有条目。接受 registries: string[]limitoffset 参数。

3. search_items_in_registries — 跨 registry 进行模糊搜索。其实现位于第 197–240 行,委托给内部使用 fuzzysort 库的 searchRegistries 函数处理。

4. view_items_in_registries — 获取条目的完整详情,包括文件内容。接受带 registry 前缀的条目名称(例如 ["@shadcn/button"])。

5. get_item_examples_from_registries — 搜索 demo/示例条目并返回完整代码。该工具专为 accordion-demobutton exampleexample-hero 等模式设计。

6. get_add_command_for_items — 返回安装条目所需的 CLI 命令(例如 npx shadcn@latest add @shadcn/button)。

7. get_audit_checklist — 返回安装后的检查清单,涵盖常见问题:import 正确性、next.config.js 图片配置、依赖安装、代码检查、TypeScript 错误等。

flowchart LR
    A["1. get_project_registries"] --> B["2. list/search items"]
    B --> C["3. view item details"]
    C --> D["4. get examples"]
    D --> E["5. get add command"]
    E --> F["6. User runs command"]
    F --> G["7. audit checklist"]

提示: MCP 工具有意不直接执行 shadcn add,而是返回命令字符串供用户手动运行。这是一种安全设计理念:AI 助手负责建议,人类负责审批。

各 IDE 的客户端配置

mcp 命令 提供了一个 init 子命令,可为五种 AI 编程环境生成对应配置。每种环境的配置格式各不相同:

客户端 配置路径 格式
Claude Code .mcp.json { mcpServers: { shadcn: { command, args } } }
Cursor .cursor/mcp.json { mcpServers: { shadcn: { command, args } } }
VS Code .vscode/mcp.json { servers: { shadcn: { command, args } } }
Codex .codex/config.toml TOML 格式([mcp_servers.shadcn]
OpenCode opencode.json { mcp: { shadcn: { type, command, enabled } } }

第 22–86 行的 CLIENTS 数组定义了每个客户端的配置路径和结构。runMcpInit 函数读取现有配置,深度合并 shadcn server 配置后写回文件,原有的 MCP server 配置不会被覆盖。

Codex 需要特殊处理,因为它的配置文件位于 ~/.codex/config.toml(全局路径),而非项目目录下。对于这种情况,命令会打印手动配置说明,而不是自动写入文件。

编程式 Registry API

packages/shadcn/src/registry/index.tsshadcn/registry 导出重新暴露了公共 API:

export { getRegistries, getRegistryItems, resolveRegistryItems, 
         getRegistry, getRegistriesIndex } from "./api"
export { searchRegistries } from "./search"
export { RegistryError, RegistryNotFoundError, /* ... */ } from "./errors"

这套 API 与 MCP server 内部使用的完全一致。库作者可以直接导入它来构建自定义工具:

import { searchRegistries, getRegistryItems } from "shadcn/registry"

const results = await searchRegistries(["@shadcn"], {
  query: "button",
  limit: 10,
})

api.ts 模块在内部组合了 builder、fetcher 和 resolver。getRegistryItems 同时处理带命名空间的条目(如 @acme/button)和 URL 形式的条目,为请求设置包含认证 header 的 registry 上下文,并用 Zod schema 对响应进行校验。

搜索子系统

searchRegistries 函数实现了跨多个 registry 的搜索,流程如下:

  1. 对每个 registry,通过 getRegistry 获取完整的 registry 数据
  2. 将条目映射为可搜索的格式,附带 addCommandArgument(例如 @shadcn/button
  3. 如果提供了查询词,则通过 fuzzysortnamedescription 字段进行模糊匹配
  4. 使用 offsetlimit 进行分页处理
  5. searchResultsSchema 对结果进行校验
flowchart TD
    A["searchRegistries(['@shadcn', '@acme'], {query: 'button'})"]
    A --> B["Fetch @shadcn registry"]
    A --> C["Fetch @acme registry"]
    B --> D["Map to searchable items"]
    C --> E["Map to searchable items"]
    D --> F["Concatenate all items"]
    E --> F
    F --> G["fuzzysort.go(query, items)"]
    G --> H["Apply pagination"]
    H --> I["Return SearchResults"]

fuzzysort 库提供容错式模糊匹配,阈值可配置(默认为 -10000,非常宽松)。返回结果中包含 addCommandArgument 字段,即传给 shadcn add 的精确字符串。

三层接入面设计:CLI、库与 AI

纵观整体架构,shadcn/ui 被有意设计为一个三层接入面系统:

graph TD
    subgraph "Shared Internals"
        Parser["parser.ts"]
        Builder["builder.ts"]
        Fetcher["fetcher.ts"]
        Resolver["resolver.ts"]
        Schema["schema.ts"]
        Context["context.ts"]
        Search["search.ts"]
        API["api.ts"]
    end

    subgraph "Surface 1: CLI"
        Init["init command"]
        Add["add command"]
        Build["build command"]
    end

    subgraph "Surface 2: Library"
        RegExport["shadcn/registry"]
        SchemaExport["shadcn/schema"]
    end

    subgraph "Surface 3: AI"
        MCP["shadcn/mcp"]
        Tools["7 MCP tools"]
    end

    Init --> API
    Add --> API
    Build --> Schema
    RegExport --> API
    SchemaExport --> Schema
    MCP --> API
    MCP --> Search
    Tools --> MCP

    API --> Parser
    API --> Builder
    API --> Fetcher
    API --> Resolver
    API --> Context
    Search --> API

这一架构的核心原则在于:三层接入面共享同一套内部模块。当 resolver 新增功能(比如第 2 篇中介绍的拓扑排序),CLI 用户、编程调用方和 AI 助手可以立即同步受益——没有额外的适配层,也没有独立实现,只是同一套代码的不同入口。

package.json 的子路径导出划定了清晰的边界。导入 shadcn/registry 不会引入 CLI 的 Commander 依赖;导入 shadcn/schema 只会获取 Zod schema,不包含任何运行时代码。tsup.config.ts 的 tree-shaking 确保每个入口点只打包自身所需的内容。

这并非偶然。CLI 入口 index.ts 最后一行 export * from "./registry/api" 是一个刻意为之的选择,目的是让 CLI 模块同时也是一个编程式库。用最小的 API 接入面,覆盖最广泛的使用场景。

提示: 如果你正在围绕 shadcn/ui 构建工具(VS Code 插件、CI 检查、文档生成器等),请从 shadcn/registry 而非根路径导入。这样可以获得更小的打包体积,避免引入 CLI 专属的依赖。

系列总结

在这六篇文章中,我们完整梳理了 shadcn/ui 的整体架构:

  1. 架构概览:一个具备多接入面 CLI 包与 registry 协议的 monorepo
  2. Registry 协议:命名空间解析、URL 构建、HTTP 请求与拓扑排序
  3. Transformer:基于 ts-morph 的 AST 级代码改写与 PostCSS 样式映射
  4. 构建流水线:bases × styles 的笛卡尔积,生成静态 JSON 文件
  5. 初始化流程:框架检测、模板、preset 与 monorepo 路由
  6. AI 集成:MCP server、编程式 API 与三层接入面设计

贯穿全系列的核心架构洞察,在于 cn-* 抽象层的设计。通过将组件逻辑与视觉样式解耦——以抽象 CSS 类为媒介——shadcn/ui 实现了一件难得的事:单一组件实现,兼容多种 headless 库、多种视觉风格、多种图标库和多种项目配置,所有适配在安装时完成,而非运行时。最终落入你项目中的代码,没有任何抽象开销——就是组件本身,以你选择的方式精确渲染。