内容
# 设计系统 MCP 服务器
一个 [MCP](https://modelcontextprotocol.io) 服务器,允许大型语言模型查询您的设计系统文档(markdown)。使用 **Elixir** 和 [`anubis_mcp`](https://hex.pm/packages/anubis_mcp) 构建,通过 **Streamable HTTP** (和 **stdio**)提供服务。无需数据库,无需 Phoenix — 文档仅在启动时加载到内存中。
## 工作原理
```
systems/**/*.md ──► 加载器 ──► 索引(ETS) ──► MCP 工具 ──► 大型语言模型
(解析) (搜索/查找) (HTTP/stdio)
```
1. **加载器** 遍历文档根目录(默认为 `systems/github/`),分离可选的 YAML 前置信息和正文,并提取标题、标题、原始 markdown 和纯文本渲染。
2. **索引** 是一个基于 ETS 的 `GenServer`,提供快速查找和排名搜索(标题/标题/标签命中率高于正文命中率)。
3. **服务器** 通过 MCP 工具公开索引。
## MCP 工具
通用工具:
| 工具 | 输入 | 返回 |
| --- | --- | --- |
| `list_categories` | – | 带有文档计数的类别 |
| `list_docs` | `category?`, `layer?`, `token?` | 文档标题 + 斜杠(按类别、层或声明令牌的文档过滤) |
| `search` | `query`, `category?`, `layer?`, `limit?` | 带有片段的排名匹配 |
| `get_doc` | `slug` 或 `title` | 一个文档的完整 markdown |
| `get_section` | `slug`, `heading` | 一个标题下的 markdown |
| `get_related` | `slug` | 从文档的 `related` 前置信息中交叉链接的文档 |
设计系统便捷包装器(通用工具的薄包装器):
| 工具 | 输入 | 返回 |
| --- | --- | --- |
| `list_components` | – | 组件文档 |
| `get_component` | `name` | 完整的组件文档 |
| `list_layouts` | – | 布局文档 |
| `get_layout` | `name` | 完整的布局文档 |
| `get_tokens` | `group?` | 令牌文档或一个令牌组的章节 |
| `get_token` | `name` | 一个令牌的值 + 角色来自清单,以及使用它的文档 |
| `get_typography` | – | 排版文档 |
| `get_color_usage` | – | 颜色使用文档 |
| `get_shape_language` | – | 形状语言文档 |
## 编写文档
将 markdown 文件放在文档根目录(`systems/github/`)下的任意位置。**斜杠** 是相对于文档根目录的路径,不带 `.md`,例如 `systems/github/components/button.md` → `components/button`。
前置信息是可选的,但建议使用以便进行更丰富的查询。所有字段都是可选的;如果字段缺失,加载器会根据路径/内容进行推断。
```markdown
---
title: 按钮 # 否则为第一个 H1,否则为人类化的文件名
category: 组件 # 否则为顶级文件夹(或文档根目录下的 `general`)
layer: 组件 # 概述 | 用法 | 基础 | 布局 | 组件 | 模式 | 元数据
tags: [交互式, 表单]
status: 稳定
slug: 组件/按钮 # 固定一个稳定的标识,如果文件可能会移动
uses_tokens: [--button-primary-bgColor-rest, --fgColor-onEmphasis]
related: [设计, 基础/颜色]
---
# 按钮
...
```
结构化字段驱动更丰富的工具:`layer` 支持 `list_docs`/`search` 的 `layer` 过滤器,`uses_tokens` 支持反向令牌查找(`list_docs token:` 和 `get_token` 的“使用”),`related` 支持 `get_related`。
由于 `uses_tokens` 和 `related` 是正文的机械投影,因此它们是 **生成的** 而不是手动维护的:在反引号中提及一个令牌或兄弟文档,[`mix frontmatter`](#维护文档) 保持列表完整,而 [`mix consistency`](#维护文档) 保持列表诚实。参见下面的 [维护文档](#维护文档)。
当缺少前置信息时,加载器会退回到:
- **标题** → 第一个 `# H1`,否则为人类化的文件名
- **类别** → 顶级文件夹(或文档根目录下的 `general`;可通过 `config :design_system_mcp, root_category:` 配置)
- **斜杠** → 相对于文档根目录的路径,不带 `.md`
## 维护文档
三个 Mix 任务保持文档内部一致并与上游 Primer 包同步。它们是纯粹的(无需运行服务器),失败时会退出并返回非零代码,因此它们控制 CI。完整的 YAML 前置信息合约在 [`systems/github/meta/schema.md`](systems/github/meta/schema.md) 中有记录。
### `mix frontmatter` — 保持 `uses_tokens` / `related` 完整
`uses_tokens` 和 `related` 源自每个文档正文中的反引号引用,因此它们永远不会与散文悄悄分离。生成是 **累加的**:它在正文中提到的引用添加到前置信息中,同时保留手动策划的条目。
```bash
mix frontmatter # 修复到位:追加缺失的引用
mix frontmatter --check # CI 门控:检查文档是否完整(不写入任何内容)
mix frontmatter --prune # 破坏性:重写列表以 *完全* 匹配正文引用
mix frontmatter PATH # 作用域写入文件/目录(仍加载完整的文档集)
```
文档通过 `generate: false` 选择退出 — 例如,独立的清单 `design.md`,其 `uses_tokens` 是一个精选的亮点,而不是其详尽的令牌表。
### `mix consistency` — 验证图
```bash
mix consistency # 验证相关 / 使用令牌 / 层
mix consistency --strict # 还要检查建议警告(孤儿)
mix consistency path/to/docs # 指向不同的文档根目录
```
加载每个文档并标记(并在退出时返回非零):
- 一个 `related:` 斜杠不能解析为现有文档,
- 一个 `uses_tokens:` 条目不存在于令牌清单(`design`)中,即 `get_token` 将无法解析,
- 一个 `layer:` 值在已知集合之外(`overview`、`usage`、`foundation`、`layout`、`component`、`pattern`、`meta`)。
它还会打印建议的 **孤儿** 警告(没有进出 `related` 链接的文档);除非提供 `--strict`,否则这些警告不会导致构建失败。
### `mix tokens.check` — 验证令牌值与 Primer
`design.md` 是独立的,其令牌值内联,因此它们可能会偏离 `@primer/primitives`。此命令将每个记录的值与预购的光模式快照进行比较,如果有任何不匹配,则失败。
```bash
mix tokens.check
```
### 刷新上游快照
两个预购快照支持上述检查。在升级 Primer 时刷新它们(并在 [`meta/dependencies.md`](systems/github/meta/dependencies.md) 中更新固定的版本):
| 脚本 | 生成 | 被消费 |
| --- | --- | --- |
| [`meta/fetch-primitives.sh`](systems/github/meta/fetch-primitives.sh) | `meta/primitives.tokens.json` — 令牌值 | `mix tokens.check` |
| [`meta/fetch-react.sh`](systems/github/meta/fetch-react.sh) | `meta/components.props.json` — 组件属性 | 组件 `## API` 部分 |
```bash
systems/github/meta/fetch-primitives.sh [VERSION] # 默认:固定版本
systems/github/meta/fetch-react.sh [VERSION]
```
`fetch-react.sh` 供应商是 `@primer/react` 的 `generated/components.json` 的紧凑属性投影;组件文档的 `## API` 部分由此生成,因此属性名称、类型和默认值来自包,而不是手动。
## 运行
需要 Elixir(参见 `.tool-versions` / 您的 `mise` 配置)— 此存储库针对 Elixir 1.20 / OTP 29。
```bash
mix deps.get
mix test
# 运行服务器(默认情况下为 http://localhost:9821 的 Streamable HTTP,仅限回环):
mix server
```
`mix server` 是 `mix run --no-halt` 的别名;它启动应用程序并使其保持运行,直到您中断它(Ctrl-C)。
该服务器通过 [Bandit](https://hex.pm/packages/bandit) 在单个端点上提供 Streamable HTTP 传输,该端点根据 HTTP 方法调度:
| 方法和路径 | 目的 |
| --- | --- |
| `GET /mcp` | 打开客户端到服务器消息的 SSE 流 |
| `POST /mcp` | 接收客户端的 JSON-RPC 消息 |
| `DELETE /mcp` | 关闭会话 |
会话与 `mcp-session-id` 响应/请求头相关。
快速烟雾测试:
```bash
# 初始化:服务器在 `mcp-session-id` 头中返回会话 ID。
curl -i -X POST http://localhost:9821/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
# 在后续调用中重用该 ID(例如,列出工具):
curl -X POST http://localhost:9821/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'mcp-session-id: <SID>' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
```
> **注意:** Streamable HTTP 是当前 MCP 传输(规范 2025-03-26)。较旧的 HTTP + SSE 传输(2024-11-05)仍在 `anubis_mcp` 中为旧客户端提供支持,通过 `transport: {:sse, ...}` 和双端点路由器,但已弃用。
文档位置默认为 `systems/github`(参见 `config/config.exs`)。通过 `DESIGN_SYSTEM_DOCS_PATH` 环境变量覆盖它,无需重新编译。
传输、HTTP 端口和已知斜杠的便捷工具解析均可在 `config/config.exs` 中配置:
```elixir
config :design_system_mcp,
transport: :streamable_http, # 或 :stdio 通过 stdin/stdout 讲话
ip: {127, 0, 0, 1}, # 绑定地址;{0,0,0,0} 代表所有接口
port: 9821, # Streamable HTTP 传输的 HTTP 端口
typography_slug: "foundations/typography", # get_typography
color_usage_slug: "foundations/color", # get_color_usage
shape_language_slug: "foundations/shape-language", # get_shape_language
token_manifest_slug: "design", # get_tokens / get_token
root_category: "general" # 文档根目录下的文档类别
```
> 日志路由到 **stderr** (因此当使用 `transport: :stdio` 时,它们永远不会破坏 stdio JSON-RPC 流)。
### 热重载
服务器监视文档目录,并在文件更改时自动重新加载索引,因此作者无需重启即可看到更新。通过 `config :design_system_mcp, watch?: false` 禁用它。
## 注册到 MCP 客户端
将您的客户端(Claude Desktop、Copilot、Cursor 等)指向正在运行的服务器的 MCP URL:
```json
{
"mcpServers": {
"design-system": {
"url": "http://localhost:9821/mcp"
}
}
}
```
更喜欢服务器通过 **stdio** 启动?设置 `config :design_system_mcp, transport: :stdio` 并注册一个命令:
```json
{
"mcpServers": {
"design-system": {
"command": "mix",
"args": ["server"],
"cwd": "/absolute/path/to/design_system_mcp",
"env": { "DESIGN_SYSTEM_DOCS_PATH": "/absolute/path/to/docs" }
}
}
}
```
## 项目布局
```
lib/design_system_mcp/
doc.ex # %Doc{} 结构体
markdown.ex # 标题 / 章节 / 纯文本帮助程序
loader.ex # systems/ -> [%Doc{}]
index.ex # 基于 ETS 的 GenServer(查找 + 排名搜索)
tokens.ex # 准确的单个令牌查找整个清单
watcher.ex # 热重载索引时文档更改
server.ex # Anubis MCP 服务器,注册工具
router.ex # 插件路由器安装 Streamable HTTP 端点
consistency.ex # 静态相关 / 使用令牌 / 层验证 + 孤儿警告
frontmatter.ex # 用于 uses_tokens / related 的附加生成器(mix frontmatter)
token_audit.ex # 将 design.md 令牌值与基元快照进行比较
tools/ # 每个 MCP 工具一个模块
lib/mix/tasks/
consistency.ex # `mix consistency` (包装 DesignSystemMCP.Consistency)
frontmatter.ex # `mix frontmatter` (包装 DesignSystemMCP.Frontmatter)
tokens_check.ex # `mix tokens.check` (包装 DesignSystemMCP.TokenAudit)
systems/github/ # 示例设计系统文档(默认提供)
meta/ # 前置信息合约(schema.md),fetch-*.sh 脚本,预购快照
patterns/ # 任务形配方(布局 + 组件),包括 _template.md
test/ # 单元测试 + 测试/fixtures/docs/ 下的夹具
```
## 许可证
`anubis_mcp` 遵循 **LGPL-v3** 许可;请确认在使用前这对您来说是可以接受的。