Skip to content

@tabula-css/mcp

Tabula 的只读 MCP 服务器:面向生成的 .tabula/ 产物的智能体接口(agent surface)。

安装

bash
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。

运行

bash
npx tabula-mcp [--cwd <dir>]

这个二进制文件是 tabula-mcp(来自 bin.ts)。它通过 stdioStdioServerTransport)来传输 MCP——stdout 上只承载协议本身,不承载其他任何东西;每一条诊断都会发往 stderr,否则它会被误解析为一个协议帧。--cwd 让它指向当前目录之外的某个项目根目录。如果 .tabula/ 缺失或格式错误,它会拒绝启动(退出码 2),而不是去服务一个它从未读取过的配置档案。

将你的 MCP 客户端直接指向它,例如在某个 MCP 兼容客户端的配置中:

json
{
  "mcpServers": {
    "tabula": { "command": "npx", "args": ["tabula-mcp"] }
  }
}

每一次响应都携带一个过期性信封

json
{ "profileVersion": "…", "sourceHash": "…", "stale": false }

过期性会在每一次调用时重新计算——服务器会重新对 .tabula/ 和令牌源做 stat,一旦它们发生变化就重新加载,因此“编辑然后重建”这个循环从不需要重启。每一个工具在遇到过期读取时都会拒绝响应,返回:

json
{
  "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

“这个元素实际上长什么样?”一个类字符串完整的局部样式模型:基础声明、条件带、环境属性、原子/未知类、已声明的组依赖。

参数类型是否必需
classesstring | string[]
axesRecord<string, string>(例如 { theme: "dark" }
jsonc
// → resolve_classes({ classes: "bg-surface p-md" })
{ "profileVersion": "…", "sourceHash": "…", "stale": false, /* declarations, bands, … */ }

preview_merge

在你写下 cn(...) 之前,精确预测它将产出什么:合并后的字符串,加上每一个被丢弃的类、是什么遮蔽了它,以及在哪个 CSS 属性上发生的。

参数类型是否必需
fragmentsstring[] —— 按顺序;消费方的 className 放在最后

find_class_for

按意图进行反查——这套工具里价值最高的一个。只匹配真实的文本(类名、族、已声明的属性/值、令牌描述),每次命中都会报告 matchedOn;没有同义词表,也没有模糊评分。没有命中就意味着不存在这样的类,而不是“去猜一个任意值”。

参数类型是否必需
intentstring
propertystring,例如 "padding-inline"
valuestring,例如 "#ffffff"
familystring,例如 "bg"
limitnumber(默认 25)
jsonc
// → 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——在选择一个值之前先读这个。

参数类型是否必需
namespacestring,例如 "color"
querystring,对路径 + 描述做子串匹配

get_vocabulary

完整的类列表,分页返回——从不会被静默截断;响应中会报告 total/pages/hasMore

参数类型是否必需
familystring
prefixstring
pagenumber,从零开始
pageSizenumber(默认 100,最大 500)

explain_ban

为什么一个类被封禁或未注册,并给出替代方案——绝不是一句干巴巴的“未找到”。在写下一个你不确定的类(space-x-4dark:bg-blackpl-4)之前调用它。

参数类型是否必需
classstring,例如 "space-x-4"

resolve_element

通过 { file, line, col } 指向一个 JSX 元素,返回其已解析的类、它从同一文件内祖先元素继承来的文本上下文,以及它所引用的每一个 group/peer 在该文件中是否存在标记。仅限同一文件内分析——在一个组件边界处,它会返回 status: "unknown" 以及下一个应该打开的文件,而不是去猜测该组件会渲染出什么。它还会省略 axisValues,因为一个元素在哪个轴组合下渲染,是关于正在运行的文档的事实,而不是源码本身的事实。

参数类型是否必需
filestring,相对于项目根目录
linenumber,从 1 开始
colnumber,从 1 开始

validate_source

用真实的 strict ESLint 配置对一段源码字符串进行 lint,依据你的注册表——能捕获 check_classes 在结构层面无法捕获的问题,因为它看到的是 AST(运行时类拼装、className 顺序、group/peer 结构、继承边界、例外范围)。内联的 eslint-disable 注释会被忽略,因此一段代码片段无法靠注释伪装出一个干净的结论。需要安装 eslint@typescript-eslint/parser;若未安装,会返回 LINTER_UNAVAILABLE 以及安装命令。

参数类型是否必需
codestring —— 要 lint 的源码文本
filenamestring,例如 "src/ui/card.tsx" —— 从不会从磁盘读取;用于选择规则与例外范围

propose_exception

引入一个没有对应令牌的值的唯一合法方式。返回一个补丁——不写入任何内容。 通过真实的令牌校验器进行校验,当存在一个相差约 5% 以内的既有令牌时会给出提示,并返回该补丁将铸造出的类名。

参数类型是否必需
namestring,kebab-case
$typestring(DTCG 类型)
valuestring,例如 "347px"
familiesstring[],例如 ["w"]
reasonstring,≥40 字符
ownerstring,例如 "@design-systems"
expiresstringYYYY-MM-DD,≤12 个月(若为 literal 则 ≤90 天)
allowedInstring[](glob)
descriptionstring
ticketstring
literalboolean

propose_token

铸造一个新的设计令牌(design token)——优先使用它而非 propose_exception,因为一个令牌是永久性的词汇。返回一个补丁——不写入任何内容。 通过拼接进真实的令牌文档来校验。

参数类型是否必需
namestring"<namespace>.<name>"
$typestring(DTCG 类型)
valuestring —— 单一字面量;与 cases 互斥
casesobject —— 一个轴映射,例如 { $axis: "theme", light: "#fff", dark: "#0b0b0b" };与 value 互斥
descriptionstring

find_group_marker

某个命名的 group/<name> 标记声明在何处,使得一次跨元素依赖成为一次查找,而不是一次遍历树。能够区分“不存在这样的标记”与“这次构建没有生成任何标记索引”。

参数类型是否必需
namestring,例如 "card"

check_classes

预检——在写下一个 class 属性之前调用。报告未知的类(附带“你是不是想输入……?”)、被封禁的机制,以及同一字符串内的槽位冲突,并返回规范化的合并后字符串。

参数类型是否必需
classesstring[]

doctor

健康检查:过期性(附带原因与修复命令)、手工编辑过的产物、即将到期的例外,以及逃生预算。即使处于过期状态也会作答——如果因为过期而拒绝这个过期性报告器本身,就永远说不出到底是什么发生了漂移。

无参数。

jsonc
// → doctor()
{
  "stale": false, "reasons": [], "checkedSource": true,
  "drift": [], "expiringExceptions": [], "escapeBudget": { "used": 3, "max": 25 },
  "profileVersion": "…", "sourceHash": "…", "manifestInputsHash": "…"
}

explain

一个 TAB-Exxx/TAB-Wxxx 代码的起因与修复方法,来自 @tabula-css/core 冻结的目录。

参数类型是否必需
codestring,例如 "TAB-E113"

参见

Released under the MIT License.