Caveman MCP Server 深度解析:stdio JSON-RPC 适配器的五个压缩工具、不可杀传输与 Anti-Storm 恢复机制
Caveman 的 MCP 服务(mcp/)是一个“薄层 stdio JSON-RPC 适配器”:它把压缩引擎以五个 MCP 工具暴露给任意 MCP 宿主(Claude Code、Cursor 等),自己只负责 MCP 协议封装(framing),所有压缩能力来自进程内直连的 Caveman Engine。读完本文,你将掌握这套服务端的完整目录结构、五个工具(caveman_compress、caveman_retrieve、caveman_stats、caveman_toon_encode、caveman_toon_decode)的精确语义与失败行为、共享 CCR 恢复存储的打开方式、工具注册带来的前缀 token 成本与 execute.mcp 开关、以及“不可杀传输”与 retrieve 反风暴两条核心工程不变量。
一、定位:本地-only、inferred-only 的协议薄层
mcp/CLAUDE.md 对该模块的定义非常明确:
- 只拥有 MCP framing:
initialize、tools/list、tools/call、通知(notification)的封装与分发,属于适配器的职责; - 压缩全部属于引擎,且是 in-process 链接(无子进程、无行为漂移);
- 本地-only:服务端不打开任何网络连接;
- inferred-only:它报告的一切数据都是
inferred(推断值),从不出现verified(已验证)。
这一“薄层”定位在 mcp/README.md 中同样被强调:五个工具、stdio 传输、任意 MCP 宿主可用、进程内链接引擎。这种分层让协议层(server.go)与压缩逻辑(engine/)可以独立演进和测试——Server 接收一个可注入的 Engine 接口,测试可以注入 mock,无需真实压缩器即可验证协议行为(见 mcp/engine_tools.go#L13-L23 的 Engine 接口定义)。
二、模块布局:四个部分各管一段
文档给出的布局(Layout)如下,每一项都可以直接对应到仓库中的真实文件:
| 路径 | 职责 |
|---|---|
| mcp/server.go | Server 本体:JSON-RPC 循环、分发(dispatch)、五个工具处理器。接收可注入的 Engine 接口,使协议层可脱离真实压缩器测试 |
| mcp/protocol.go | JSON-RPC 与 MCP tool-result 类型、toolText/toolError 辅助函数、tools/list 的精确工具定义 |
| mcp/cmd/caveman-mcp/ | 二进制入口:打开共享文件 CCR store(CAVEMAN_CCR_DB,否则 CAVEMAN_HOME/ccr.db / ~/.caveman/ccr.db),然后服务 stdin↔stdout |
bin/caveman-mcp.mjs + mcp/package.json |
npx caveman-mcp 启动器,exec 预编译好的 Go 二进制 |
2.1 二进制入口:先打开共享恢复存储,再进入协议循环
mcp/cmd/caveman-mcp/main.go 的 main() 流程是:处理 version --json 参数 → 用 slog 建一个写 stderr 的 logger → 打开恢复存储 → engine.New(store, nil) → mcp.NewServerVersion("caveman", version, mcp.EngineTools(eng, logger), logger) → srv.Serve(os.Stdin, os.Stdout)。
存储打开逻辑(openRecoveryStore(),mcp/cmd/caveman-mcp/main.go#L70-L87):
CAVEMAN_MCP_EPHEMERAL=1→ 内存存储(ccr.OpenMemory()),不落盘,供测试与不需要跨进程恢复的会话使用;- 否则取
CAVEMAN_CCR_DB作为库路径; - 否则取
CAVEMAN_HOME(默认~/.caveman)下的ccr.db; - 若连主目录都取不到,回退为内存存储。
默认走共享文件存储的原因在源码注释中写得很直白:这与 Caveman 网关(proxy)写的是同一个 store,因此这里 caveman_retrieve 能解析出 proxy 披露过的 handle——这正是“被包装的 agent 能在流式请求中恢复 proxy 删减掉的细节”的机制基础。
version --json 输出(handleArgs)是一个带 schema: "caveman.mcp.version.v1" 的 JSON,capabilities 包含 mcp_recovery 与 build_stamped_version。使用 NewServerVersion 而非 NewServer 的动机是:initialize 响应中的版本必须与构建时戳入的版本一致,避免兼容性信号漂移(见 mcp/server.go#L78-L81 的注释)。
2.2 npm 启动器与许可边界
npm 包 mcp/package.json(caveman-mcp v1.0.0,MIT,type: module,bin 指向 bin/caveman-mcp.mjs)是纯启动器:首次运行时下载与平台匹配的 BSL-1.1 二进制,校验密钥签名的 checksum 清单与制品 SHA-256,缓存到 ~/.caveman/bin。无需 Go 工具链,也无需全局安装 Caveman。若要使用已审查的本地二进制,可用 CAVEMAN_MCP_BIN=/path/to/caveman-mcp npx caveman-mcp。MIT 只适用于 npm 启动器部分;下载的二进制遵循 mcp/BINARY_LICENSE.md 中声明的 BSL-1.1 条款——这也是标题里“commercial Go core + MIT launcher”的含义。
三、五个工具:精确名称、精确语义、精确失败行为
工具名大小写敏感,恰好这五个(见 mcp/engine_tools.go#L25-L33 的常量定义):
| 工具 | 输入 | 返回 |
|---|---|---|
caveman_compress |
input(string) |
压缩文本、推断 ratio、recovery_handle(直通时为 null) |
caveman_retrieve |
recovery_handle(string) |
字节级精确的原始内容;未知 handle 报错 |
caveman_stats |
— | 会话总量:前后 token、ratio、basis:"inferred"、scope:"session" |
caveman_toon_encode |
input(JSON 字符串) |
显式 JSON→TOON 结果及两侧尺寸;不可编码时直通加说明 |
caveman_toon_decode |
input(TOON 字符串) |
解码出的 JSON;非法 TOON 报错 |
3.1 caveman_compress:fail-open 的有损压缩
有损(S4)但可恢复;不可压缩、格式错误或压缩后不更小的输入原样返回,ratio:0、recovery_handle:null——永不报错。
处理器实现(compressTool,mcp/engine_tools.go#L118-L143)值得注意的细节:
- 入参支持
content_type与type两个字段(互为别名),缺省则交由引擎自行探测类型; - 引擎返回 error 时并不直接失败:若引擎已返回“完整记账的字节精确直通结果”,则保留该结果;只有当第三方
Engine实现返回了不安全/空结果时才以cave_compress_failed大声失败,避免“非空内容看起来零成本”的假象。
返回结构 compressPayload(mcp/engine_tools.go#L94-L104)包含 compressed、ratio、tokens_before、tokens_after、basis、content_type、recovery_handle(可空指针,直通时序列化为 null)、method 与可选的 lossless_to_model。
3.2 caveman_retrieve:最后手段,不是分页 API
工具描述本身(mcp/engine_tools.go#L57)是一段写给模型的“行为引导”:删除标记(elision marker)陈述的是从被替换的精确单元计算出的事实(如 “… 340 rows elided (caveman): all state=charged; range amount=5.00..199.99 …”),且每个被丢单元都与仍可见的单元相似——因此计数、求和、按字段提问通常可以直接从可见视图加这些不变量回答,根本不需要调用。只有拿不到无法推导的精确字节时,才对每个 handle 做一次宽 query 调用。
实现侧(retrieveTool,mcp/engine_tools.go#L168-L206):
- handle 归一化(
normalizeRecoveryHandle,mcp/engine_tools.go#L208-L233)接受该栈历史上向 agent 展示过的所有形态:裸ccr_…、嵌入压缩内容的标记<<ccr:ccr_…>>、半剥掉的ccr:ccr_…、以及 native runtime 工具输出掩码ccr://…。源码注释指出,缺了ccr://这一形态曾导致某些基准任务无法回答——agent 看到了恢复工具拒绝解析的引用; - 空 query 是字节精确的全量恢复,非空 query 经 BM25 收窄到相关段落,且只返回完整记录、从不抽行,并在跳过的位置打印 “… [caveman: non-adjacent] …” 标记;
- 未知 handle 返回 fail-closed 工具错误(
cave_unknown_handle)并携带cave_snake_code,绝不伪造 payload。
3.3 caveman_stats:字符串 "verified" 永不出现
statsTool(mcp/engine_tools.go#L291-L306)固定填 Basis: engine.BasisInferred、Scope: "session",对应文档不变量:basis:"inferred"、scope:"session",字符串 verified 永不出现。会话不可用时返回 cave_stats_unavailable 工具错误。
3.4 TOON 双向:显式请求才编码,失败要大声
caveman_toon_encode(toonEncodeTool,mcp/engine_tools.go#L248-L272):显式 JSON→TOON 重编码,返回output、encoded、input_bytes、output_bytes。注意它不是 proxy 的 best-of 门:即使编码结果不更小也照样返回(两侧尺寸都给出,由 agent 自己决定)。无法编码的输入原样返回并带note: "not encoded: …"——从不静默 no-op,也从不编造编码;caveman_toon_decode(toonDecodeTool,mcp/engine_tools.go#L277-L289):TOON→JSON;非法 TOON 返回cave_invalid_toon工具错误,绝不把原始输入当作 JSON 吐出(与 CLI decode 动词同一不变量)。
工具级错误的载体是 ToolError(mcp/protocol.go#L83-L88):isError: true 加一段 {"error": <cave_snake_code>, "message": …} 文本,保证宿主不会把它误认为成功 payload。
四、前缀成本:这五个工具是“按调用收费”的,并且被度量过
文档专门用一节量化了工具注册的代价:注册这个 server 会把五个工具的 schema 放进被包装 agent 每次调用的 prefix 里——agent 基准测试中为 11,060 tokens/call(其 201 次调用累计约 2.22M token)。因此 caveman wrap 把注入放在 execute.mcp 这个 surface 开关之后(取值 auto | marker-only | true | false,见 packages/cli/src/index.ts):
- 非 auto 的 surface 下,wrap 抑制它的两处注入点——
mcp install写入,以及 openclaw 等配置文件型 agent 的 profilemcp.servers.caveman覆盖层; - 但绝不卸载用户自己安装的 server,所以已存在的安装会持续付出前缀成本,直到
caveman tools mcp uninstall <agent>; - 该开关不触碰 wrap 门上的恢复诚实性:
CAVEMAN_RECOVERY依据“本次启动是否真的给了 agentcaveman_retrieve”这一证据回答,proxy 永远不会被告知名下并无的检索工具存在; - 在此新增第六个工具,就会抬高每个被包装 agent 的每次调用税。
五、构建与运行约定
- 构建/测试:
make product-build PRODUCT=mcp/make product-test PRODUCT=mcp; - stdout 是协议通道——日志只能写 stderr,且有一条专门测试守护这一点(
NewServer的注释明确要求 log 写 stderr、为 nil 时丢弃;入口 main.go 也确实把 logger 建在os.Stderr上)。
六、诚实性不变量(Gotchas)与源码印证
文档列出的“Gotchas (honesty invariants)”每一条都能在源码中找到对应实现:
6.1 不可杀的传输(un-killable transport,issue #139)
stdio server 除 EOF 外什么都杀不死。具体机制(mcp/server.go):
- 逐行 framing:
readLine(L146-L162)按\n分消息且有界读取——超过maxInboundBytes的行停止缓冲但继续排空到下一个换行,报tooLong后以cave_payload_too_large应答并重新同步到下一行(永不return退出循环); - 畸形行:
handleLine(L167-L181)解析失败应答-32700(parse error),流继续; - 处理器 panic:
invokeHandler(L348-L356)用recover()把 panic 收敛为cave_tool_panicked工具错误;分发层的 panic(dispatch 内部、工具之外)由serveOne的顶层recover(L218-L230)收敛为cave_internal_error; - JSON-RPC 批量:以
[开头的行走handleBatch(L187-L213),逐成员分发、收集非通知响应、按规范写回一个数组;全通知批次不回复; - 无 id /
"id":null请求是通知,永不回复(isNotification,mcp/protocol.go#L30-L35,对应 #139 的子问题 #4); - 双向尺寸上限:入站行与生成的工具输出(compress/toon)都受
cave_payload_too_large约束,maxInboundBytes/maxResultBytes默认各 16 MiB(mcp/server.go#L29-L32)——但caveman_retrieve豁免(Tool.ExemptResultCap,mcp/server.go#L41-L48):恢复必须返回字节级精确的原始内容,且共享网关存储没有对应的尺寸上限,原始内容可以合法超过 cap,此时 fail-closed 会让被删减的内容永远无法恢复。“死掉的 server 比慢的 server 更糟——proxy 会继续删减已经没有caveman_retrieve可展开的内容”。
6.2 fail-open 与 fail-closed 的分工
- fail-open:引擎错误或畸形输入 → 字节一致的直通,永不产生协议错误(见
compressTool的回退路径); - fail-closed:未知工具/handle →
isError+cave_snake_code(handleToolCall对未知工具返回cave_unknown_tool工具错误,mcp/server.go#L319-L344);未知 JSON-RPC method →-32601(dispatch的 default 分支,mcp/server.go#L264-L285); - 完整错误码表定义在 mcp/protocol.go#L9-L15:
-32700parse、-32600invalid request、-32601method not found、-32602invalid params、-32603internal。
6.3 零出站(zero-egress)
适配器不导入 net / net/http / os/exec,且有一条测试直接解析源码来强制这一点——TestZeroEgressNoNetworkImports(mcp/server_test.go#L530-L535)检查源码中不含这些导入,这是 PRD §11.6 出站保证的可执行形式。
6.4 版本边界
v1 仅 stdio、仅字符串 payload(类型由引擎探测);HTTP 传输 + caveman mcp 子命令属于 v2 规划。
6.5 协议协商永不报错
适配器实现 2024-11-05 契约并回显该版本;客户端索要更新版本时,得到 initialize 结果里的 2024-11-05,由客户端自行决定(符合 MCP 生命周期)。handleInitialize(mcp/server.go#L287-L317)的注释完整记录了事故链:旧版对不支持的版本应答 -32602: unsupported protocol version,导致 Claude Code 等所有现行客户端直接丢弃 server——而 caveman wrap 是从安装期标记(而非活着的 agent)读取恢复可用性,proxy 于是继续压缩已无 caveman_retrieve 可展开的内容。“回显未实现的版本不对,拒绝开口更不对”:新代码保留保守内核(从不声称实现了并未实现的语义——它拒绝回显,而不是拒绝交谈),supportedProtocolVersions 显式只含 2024-11-05(mcp/server.go#L50-L57)。
七、Retrieve 反风暴(anti-storm):每进程一份恢复台账
7.1 问题:每次 retrieve 花费一个完整的 agent 回合
caveman_retrieve 的代价是整个 agent turn:模型要重读整个会话前缀,且此前任何一次 retrieve 返回的内容从此都是该前缀的一部分——N 次 retrieve 花的是 N 个回合,而每次的回放脚本(transcript)都比上一次更长。
文档引用了 2026-08-10 一次对 229 个本地 CaveBench stdout 文件的只读扫描:34 个恢复会话、534 次恢复工具调用;按调用数分桶 1 / 2–5 / >5 分别含 3 / 16 / 15 个会话,p95 为 118,max 为 143。15 个 >5 次调用的会话中 8 个仍通过了精确任务 grader;534 次归一化 (handle, trimmed query) 对中仅 3 对完全重复,许多调用使用新 handle 或指针链。文档同时明确划出证据边界:批次混合了不同 arm、任务与重复,这是描述性的调用形态证据,不是同任务反事实、不是托管网关最终结果总体、也不构成对任何通用阈值的验证。
7.2 两条规则:任何一条都不得扣住会话尚未拿到的内容
实现位于 mcp/engine_tools.go#L313-L397(retrieveSession),由 EngineTools 为每个进程(=每个会话)创建一份台账:
- 重复指针:完全相同的
(handle, query)返回一行指针,指向会话转录中已有的逐字答案,而不是再发一遍字节(repeatNote,L350)。key 的比较对 query 做与RetrieveQuery相同的 trim,所以" "和""算同一请求(retrieveKey,L368-L370); - 全量支付:超过
retrieveStormThreshold(5,L348)个不同的 retrieve 后,下一次调用返回该 handle 的完整存储原文(而非 query 收窄视图)并明示(fullPayoutNote,L352)。文档强调这是保留的历史策略,意图是避免后续更窄的分页;没有任何成对实验验证过该阈值、token 效果或任务结果效果。retrieveStormThreshold的注释进一步说明:更大的扫描取代了先前不完整的 11/25/46 数据,“描述性计数既不授权收紧也不授权移除”——改阈值需要先做那个实验。
*retrieveSession 为 nil 是安全的且同时禁用两条规则,因此任何没有会话概念的调用方保留旧语义(alreadyServed/shouldPayOutInFull/record 对 nil receiver 均短路返回,L372-L397)。
八、延伸阅读
- 模块入口文档:mcp/CLAUDE.md(本文依据的源文档)
- 用户向说明与安装配置:mcp/README.md
- 引擎侧说明:engine/CLAUDE.md、根目录 CLAUDE.md(含“recovery 原样返回原文”的规则 #2)
- 测试证据:mcp/server_test.go(stdout 纯度、零出站、恢复豁免 cap 回归)、mcp/engine_tools_antistorm_test.go、mcp/retrieve_integration_test.go、mcp/tests/launcher.test.mjs
- wrap 门开关实现:packages/cli/src/index.ts
适用前提:以上描述以当前仓库 v1 实现为准——stdio-only、字符串 payload、2024-11-05 协议版本、16 MiB 双向默认 cap、retrieveStormThreshold=5 为历史保留值。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00