@tabula-css/mcp
Tabula 的只读 MCP 服务器:面向生成的 .tabula/ 产物的智能体接口(agent surface)。
安装
npm install --save-dev @tabula-css/mcp开发依赖——它是你的编码智能体的 MCP 客户端所启动的一个本地工具,而不是你应用的运行时依赖。它打包了 @tabula-css/cli、@tabula-css/core、@tabula-css/eslint-plugin、@tabula-css/merge 和 @tabula-css/registry。@typescript-eslint/parser 作为常规依赖发布;eslint 是一个可选的对等依赖,只有 validate_source 工具才需要它。
概览
llms.txt/llms-full.txt 给智能体的是一张关于该词汇表的静态地图,而 MCP 服务器给它的则是对同一份 .tabula/ 产物的实时、结构化访问:解析一个类字符串实际会渲染出什么、在写入之前预览一次 cn() 合并、按意图而非凭记忆去反查一个类名,以及在一次拟议的编辑落地之前依据真实的注册表进行校验。每一个工具都是只读的——这里没有任何东西会写入文件;两个提议类工具会返回一个补丁,交给智能体自己的编辑流程去应用,因此最终由人来复查这个 diff。
运行
npx tabula-mcp [--cwd <dir>]这个二进制文件是 tabula-mcp(来自 bin.ts)。它通过 stdio(StdioServerTransport)来传输 MCP——stdout 上只承载协议本身,不承载其他任何东西;每一条诊断都会发往 stderr,否则它会被误解析为一个协议帧。--cwd 让它指向当前目录之外的某个项目根目录。如果 .tabula/ 缺失或格式错误,它会拒绝启动(退出码 2),而不是去服务一个它从未读取过的配置档案。
将你的 MCP 客户端直接指向它,例如在某个 MCP 兼容客户端的配置中:
{
"mcpServers": {
"tabula": { "command": "npx", "args": ["tabula-mcp"] }
}
}每一次响应都携带一个过期性信封
{ "profileVersion": "…", "sourceHash": "…", "stale": false }过期性会在每一次调用时重新计算——服务器会重新对 .tabula/ 和令牌源做 stat,一旦它们发生变化就重新加载,因此“编辑然后重建”这个循环从不需要重启。每一个工具在遇到过期读取时都会拒绝响应,返回:
{
"error": "STALE_REGISTRY",
"message": "The .tabula/ artifacts do not match the current inputs. …",
"command": ["tabula", "build"],
"reasons": ["…"],
"profileVersion": "…", "sourceHash": "…", "stale": true
}有两个工具豁免于这种拒绝(尽管在适用时它们仍会报告 stale: true):doctor,它的全部职责就是报告过期性;以及 explain,它只读取 @tabula-css/core 冻结的目录,不触碰任何产物。当源码漂移检查无法运行时(一个无法解析的包版本,或无法读取的项目源码),这个信封还会额外携带 "staleCheck": "artifacts-only"——参见过期性与仅产物检查。
工具
resolve_classes
“这个元素实际上长什么样?”一个类字符串完整的局部样式模型:基础声明、条件带、环境属性、原子/未知类、已声明的组依赖。
| 参数 | 类型 | 是否必需 |
|---|---|---|
classes | string | string[] | 是 |
axes | Record<string, string>(例如 { theme: "dark" }) | 否 |
// → resolve_classes({ classes: "bg-surface p-md" })
{ "profileVersion": "…", "sourceHash": "…", "stale": false, /* declarations, bands, … */ }preview_merge
在你写下 cn(...) 之前,精确预测它将产出什么:合并后的字符串,加上每一个被丢弃的类、是什么遮蔽了它,以及在哪个 CSS 属性上发生的。
| 参数 | 类型 | 是否必需 |
|---|---|---|
fragments | string[] —— 按顺序;消费方的 className 放在最后 | 是 |
find_class_for
按意图进行反查——这套工具里价值最高的一个。只匹配真实的文本(类名、族、已声明的属性/值、令牌描述),每次命中都会报告 matchedOn;没有同义词表,也没有模糊评分。没有命中就意味着不存在这样的类,而不是“去猜一个任意值”。
| 参数 | 类型 | 是否必需 |
|---|---|---|
intent | string | 否 |
property | string,例如 "padding-inline" | 否 |
value | string,例如 "#ffffff" | 否 |
family | string,例如 "bg" | 否 |
limit | number(默认 25) | 否 |
// → find_class_for({ intent: "raised card background" })
{ "matches": [{ "class": "bg-surface-raised", "family": "bg", "matchedOn": ["intent"], "rank": 30091300002, … }], "total": 1 }get_tokens
每一个令牌(或某个命名空间/子串切片),直接来自 tokens.resolved.json——在选择一个值之前先读这个。
| 参数 | 类型 | 是否必需 |
|---|---|---|
namespace | string,例如 "color" | 否 |
query | string,对路径 + 描述做子串匹配 | 否 |
get_vocabulary
完整的类列表,分页返回——从不会被静默截断;响应中会报告 total/pages/hasMore。
| 参数 | 类型 | 是否必需 |
|---|---|---|
family | string | 否 |
prefix | string | 否 |
page | number,从零开始 | 否 |
pageSize | number(默认 100,最大 500) | 否 |
explain_ban
为什么一个类被封禁或未注册,并给出替代方案——绝不是一句干巴巴的“未找到”。在写下一个你不确定的类(space-x-4、dark:bg-black、pl-4)之前调用它。
| 参数 | 类型 | 是否必需 |
|---|---|---|
class | string,例如 "space-x-4" | 是 |
resolve_element
通过 { file, line, col } 指向一个 JSX 元素,返回其已解析的类、它从同一文件内祖先元素继承来的文本上下文,以及它所引用的每一个 group/peer 在该文件中是否存在标记。仅限同一文件内分析——在一个组件边界处,它会返回 status: "unknown" 以及下一个应该打开的文件,而不是去猜测该组件会渲染出什么。它还会省略 axisValues,因为一个元素在哪个轴组合下渲染,是关于正在运行的文档的事实,而不是源码本身的事实。
| 参数 | 类型 | 是否必需 |
|---|---|---|
file | string,相对于项目根目录 | 是 |
line | number,从 1 开始 | 是 |
col | number,从 1 开始 | 是 |
validate_source
用真实的 strict ESLint 配置对一段源码字符串进行 lint,依据你的注册表——能捕获 check_classes 在结构层面无法捕获的问题,因为它看到的是 AST(运行时类拼装、className 顺序、group/peer 结构、继承边界、例外范围)。内联的 eslint-disable 注释会被忽略,因此一段代码片段无法靠注释伪装出一个干净的结论。需要安装 eslint 和 @typescript-eslint/parser;若未安装,会返回 LINTER_UNAVAILABLE 以及安装命令。
| 参数 | 类型 | 是否必需 |
|---|---|---|
code | string —— 要 lint 的源码文本 | 是 |
filename | string,例如 "src/ui/card.tsx" —— 从不会从磁盘读取;用于选择规则与例外范围 | 是 |
propose_exception
引入一个没有对应令牌的值的唯一合法方式。返回一个补丁——不写入任何内容。 通过真实的令牌校验器进行校验,当存在一个相差约 5% 以内的既有令牌时会给出提示,并返回该补丁将铸造出的类名。
| 参数 | 类型 | 是否必需 |
|---|---|---|
name | string,kebab-case | 是 |
$type | string(DTCG 类型) | 是 |
value | string,例如 "347px" | 是 |
families | string[],例如 ["w"] | 是 |
reason | string,≥40 字符 | 是 |
owner | string,例如 "@design-systems" | 是 |
expires | string,YYYY-MM-DD,≤12 个月(若为 literal 则 ≤90 天) | 是 |
allowedIn | string[](glob) | 是 |
description | string | 否 |
ticket | string | 否 |
literal | boolean | 否 |
propose_token
铸造一个新的设计令牌(design token)——优先使用它而非 propose_exception,因为一个令牌是永久性的词汇。返回一个补丁——不写入任何内容。 通过拼接进真实的令牌文档来校验。
| 参数 | 类型 | 是否必需 |
|---|---|---|
name | string,"<namespace>.<name>" | 是 |
$type | string(DTCG 类型) | 是 |
value | string —— 单一字面量;与 cases 互斥 | 否 |
cases | object —— 一个轴映射,例如 { $axis: "theme", light: "#fff", dark: "#0b0b0b" };与 value 互斥 | 否 |
description | string | 否 |
find_group_marker
某个命名的 group/<name> 标记声明在何处,使得一次跨元素依赖成为一次查找,而不是一次遍历树。能够区分“不存在这样的标记”与“这次构建没有生成任何标记索引”。
| 参数 | 类型 | 是否必需 |
|---|---|---|
name | string,例如 "card" | 是 |
check_classes
预检——在写下一个 class 属性之前调用。报告未知的类(附带“你是不是想输入……?”)、被封禁的机制,以及同一字符串内的槽位冲突,并返回规范化的合并后字符串。
| 参数 | 类型 | 是否必需 |
|---|---|---|
classes | string[] | 是 |
doctor
健康检查:过期性(附带原因与修复命令)、手工编辑过的产物、即将到期的例外,以及逃生预算。即使处于过期状态也会作答——如果因为过期而拒绝这个过期性报告器本身,就永远说不出到底是什么发生了漂移。
无参数。
// → doctor()
{
"stale": false, "reasons": [], "checkedSource": true,
"drift": [], "expiringExceptions": [], "escapeBudget": { "used": 3, "max": 25 },
"profileVersion": "…", "sourceHash": "…", "manifestInputsHash": "…"
}explain
一个 TAB-Exxx/TAB-Wxxx 代码的起因与修复方法,来自 @tabula-css/core 冻结的目录。
| 参数 | 类型 | 是否必需 |
|---|---|---|
code | string,例如 "TAB-E113" | 是 |
参见
@tabula-css/registry— 每一个工具所读取的产物。@tabula-css/tokens—propose_token/propose_exception据以校验的令牌文档。@tabula-css/eslint-plugin—validate_source所运行的strict配置。- 智能体接口 —
llms.txt、故障可见保证,以及完整的过期性模型。 @tabula-css/cli—tabula doctor和tabula explain,这个服务器在 CLI 中的对应命令。