caveman-shrink:为 MCP 工具目录做「文字瘦身」的 stdio 代理,少烧 Token 且不改变工具语义
【免费下载链接】caveman🪨 why use many token when few token do trick — Claude Code skill that cuts 65% of tokens by talking like caveman项目地址: https://gitcode.com/GitHub_Trending/caveman1/caveman
caveman-shrink 是 caveman 项目中的一个 Model Context Protocol(MCP)中间件:它以 stdio 代理的形式站在 MCP 客户端(如 Claude Code)与任意上游 MCP 服务器之间,只对工具目录中的描述性文本(description等 prose 字段)做压缩,保留代码、URL、路径与标识符原样不动。读完本文,你将了解它的安装与接入方式、两个环境变量配置项、它明确「不碰」的安全边界,以及从源码层面验证其压缩规则、保护机制与进程管理细节的方法。
它解决什么问题
MCP 服务器在tools/list、prompts/list、resources/list等响应中会返回大量自然语言描述。模型每次读取工具目录都要为这些文字消耗上下文 Token,而其中相当一部分是冠词、填充词、客套话和犹豫式表达,对调用工具本身没有信息量。
caveman-shrink 的定位(见 README)可以概括为一句:
MCP middleware. Wrap any MCP server. Cut the prose. Keep the substance.
它是一个 stdio 代理:压缩的是「模型要读」的那一侧文本,目标是让工具目录被读得更省 Token,且工具的语义不发生任何变化。压缩规则与 caveman 主 skill 使用的边界一致——代码、URL、路径、标识符一律保留,只剥离冠词、填充词、模糊措辞与客套话。
安装与接入
安装
npm install -g caveman-shrink # 或者直接用 npx 运行 npx caveman-shrink <upstream-command> [...args]在 MCP 客户端中包装任意上游服务器
以文件系统 MCP 服务器为例,在 Claude Code(或其他 MCP 客户端)的配置中把caveman-shrink作为第一个进程、上游服务器作为其参数:
{ "mcpServers": { "fs-shrunk": { "command": "npx", "args": [ "caveman-shrink", "npx", "@modelcontextprotocol/server-filesystem", "/path/to/dir" ] } } }代理会以后台子进程方式启动上游服务器,拦截tools/list、prompts/list、resources/list三类响应,并重写其中的description字段(以及你在CAVEMAN_SHRINK_FIELDS中列出的其他字段)。
从 package.json 可以确认包的入口结构:bin指向 index.js(CLI 可执行入口),main指向 compress.js(纯 Node 压缩库,可被require复用),当前版本为0.1.1,MIT 协议。
它明确「不碰」什么
README 强调 v1 是保守设计,以下三类内容一律不改:
- 发往上游的请求体:客户端 → 上游方向原样透传。
tools/call的调用结果:不压缩上游返回给模型的数据,避免悄悄改变数据内容而破坏下游解析。- prose 中的代码外观 token:标识符、URL、路径、类代码片段在任意文本中都精确保留,边界与父级 caveman skill 相同。
这一保守策略在 index.js 中可以直接印证:Client → us → upstream. Pass through unchanged for v1.,客户端输入只被forwardInput原字节转发,不经过任何JSON.parse或改写。
配置项
| 环境变量 | 默认值 | 作用 |
|---|---|---|
CAVEMAN_SHRINK_FIELDS | description | 需要压缩的字段名,逗号分隔 |
CAVEMAN_SHRINK_DEBUG | 0 | 设为1时向 stderr 输出每个字段的压缩前后长度 |
两个变量在 index.js 中解析:
const debug = process.env.CAVEMAN_SHRINK_DEBUG === '1'; const fields = (process.env.CAVEMAN_SHRINK_FIELDS || 'description') .split(',').map(s => s.trim()).filter(Boolean);即字段列表按逗号切分、去空白、去空项,只接受非空字符串字段名。开启调试后,代理会为每一处实际发生变化的字段打印一条形如[caveman-shrink] tools.<tool名>.description: 142→97 bytes的日志(见 index.js),便于核对压缩是否如预期生效。
代理主流程:源码级走读
双向行缓冲的 JSON-RPC 透传
MCP 的 stdio 传输按行分隔 JSON-RPC 消息。index.js 中的makeLineBuffer用StringDecoder做 UTF-8 安全的行缓冲(避免多字节字符被 chunk 边界截断),两个方向各建一个:
- 上游 → 客户端:每行尝试
JSON.parse,解析失败则原样透传(见 index.js);解析成功则交给transformResponse。 - 客户端 → 上游:直接透传,不做解析。
transformResponse的匹配策略值得注意(index.js):它不依赖请求-响应对应关系,而是按响应形状识别——只要msg.result中存在tools、prompts、resources、resourceTemplates数组之一,就对数组内每项的指定字段做压缩。这意味着即使上游在响应里携带了请求方法之外的额外列表字段,也能被覆盖到。
压缩分两层:
- 顶层字段:遍历数组项,对
fields列表中的字符串字段调用compress; - 嵌套 schema:对每项的
inputSchema调用compressDescriptionsInPlace,递归遍历对象/数组,压缩所有同名字符串字段——这覆盖了工具参数级(JSON Schema 内嵌套的)description,是顶层压缩管不到的区域。
背压与退出码
代理对背压做了显式处理:写客户端方向,若process.stdout.write返回 false 就暂停上游 stdout,等drain再恢复(index.js);客户端 → 上游方向同理(index.js)。上游进程close时先暂停 stdin、摘除监听器,让已转换的字节全部排空后按退出码/信号自然退出(index.js),信号退出时还原为128 + signal_number的 shell 惯例。
压缩器:保护优先的文本规则
compress.js 是纯 Node 实现(文件头注释说明它是对 caveman-compress 工具边界的 Node 重实现,使代理保持单运行时)。API 为compress(text) → { compressed, before, after }。
永远不动的 token(保护模式)
PROTECTED_PATTERNS(compress.js)列出了即使在 prose 内部也绝不触碰的 8 类模式:
| 模式 | 正则 | 覆盖 |
|---|---|---|
| 围栏代码块 | ```…``` | 整块代码 |
| 行内代码 | `…` | 反引号片段 |
| URL | https?://\S+ | 链接 |
| 路径 | 含/或\的词 | 文件系统路径 |
| CONST_CASE | API_KEY_VALUE式 | 常量标识符 |
| 点分调用 | pkg.fn()式 | 模块/方法引用 |
| 函数调用 | name(...)式 | 代码外观 token |
| 版本号 | 1.2.3式 | 语义化版本 |
实际删除的内容
compressProse(compress.js)对剩余文本依次应用五组正则:
- LEADERS:句首的
I'll/I will/you can/we will/let me等(多行模式); - PLEASANTRIES:
please、kindly、thank you、sure、certainly、of course、happy to等客套; - HEDGES:
perhaps、maybe、might、could potentially、i think、it seems等模糊措辞; - FILLERS:
just、really、basically、actually、simply、quite、very、essentially、literally; - ARTICLES:
a/an/the(仅当后接小写字母,避免误伤缩略词)。
随后折叠多余空白、清理标点前的空格、压缩三连以上换行,并把可能被误伤成小写的句首重新大写。
哨兵替换与嵌套恢复
保护机制的实现是「哨兵替换」:先把每个受保护片段替换为N占位符,只压缩剩余文本,再迭代把占位符还原回原文(withProtectedSegments,compress.js)。这里的坑在于模式嵌套——路径规则先吞下STARTER/BUSINESS,随后函数调用规则又可能把还原产物里的哨兵一起匹配进新的哨兵。因此恢复是迭代的,上限MAX_RESTORE_PASSES = 8次;注释明确说明:轮数随嵌套深度增长而不随输入长度增长,8 次已远超真实深度,同时为病态输入兜底。
这个机制有对应的回归测试(tests/test_mcp_shrink.js,标注为 #444):对plan type (STARTER/BUSINESS)、user role (ADMIN/MEMBER/GUEST)等输入断言枚举值完整保留、且输出中不残留N形式的哨兵。
上游进程启动:跨平台与 Windows shim 安全
spawn-options.js 单独抽出了「如何安全启动上游进程」的逻辑,文件头注释解释了动机:Windows 上 npm 工具以.cmdshim 形式存在,必须直接把参数数组交给目标 Node 脚本,绝不能把上游参数拼接进 cmd.exe 字符串。
从源码结构看其处理链:
- 非
win32平台直接透传{ command, args }; - Windows 上按
PATHEXT语义解析无扩展名命令(resolveWindowsCommand); - 目标是
.cmd/.bat时,限制 shim 文件 ≤256KB,解析出其中引用的.js/.cjs/.mjs目标脚本,最终改写为process.execPath(当前 Node 可执行文件)直接启动该脚本; - 非 Node 的 Windows shim 直接抛错拒绝启动,避免「猜」。
getSpawnOptions在所有平台都保持shell未开启、stdio: ['pipe', 'pipe', 'inherit']、windowsHide: true(spawn-options.js),测试 tests/test_mcp_shrink.js 分别对 win32/linux/darwin 断言了「shell 关闭 + 三管道 stdio」。
测试如何验证它
压缩单元行为的回归测试集中在 tests/test_mcp_shrink.js,每条断言都对应 README 宣称的一条边界:
- 删冠词、删填充词/客套、删模糊措辞与
I will句首(compress直接测); - 围栏代码、行内代码、URL、路径、
CONST_CASE、点分调用逐条断言原样保留; - 真实 MCP 风格描述(天气工具示例)断言压缩率超过 15% 下限,且
weather/Fahrenheit/city name等实质内容保留; compressDescriptionsInPlace对嵌套tools数组的遍历、对非字符串字段跳过不抛错。
代理本体的运行时测试在 tests/installer/mcp-shrink.runtime.test.mjs:
- 上游一次性吐出 1.5MB 的无行尾最终响应,断言代理能完整排空、payload 长度不丢失(对应
responses.end()的 flush 逻辑); - 一个
🙂emoji 的 UTF-8 字节被切到两个 chunk,断言StringDecoder行缓冲不会产生乱码。
此外 tests/test_mcp_shrink.js 中还有一个打包回归(#597):静态遍历bin与main入口可达的全部相对require,断言每个模块都列在package.json的files中——因为files漏项会导致发布包在启动时MODULE_NOT_FOUND(历史上spawn-options.js就出过这种事故)。
当前状态与适用范围
README 的 Status 一节明确:caveman-shrink 处于Pre-1.0,压缩规则与字段集合可能变化;它是 caveman 生态(caveman、cavemem、cavekit、cavecrew、caveman-stats、caveman-init等 skill 套件)的组成部分,许可为 MIT。
适用前提与限制小结:
- 仅作用于 MCPstdio 传输、按行分隔的 JSON-RPC 消息;非行分隔或多消息粘连的传输不在当前实现覆盖范围内;
- v1 只压缩
tools/list/prompts/list/resources/list(含resourceTemplates)响应中的描述性字符串字段,tools/call结果与请求体一律原样透传; CAVEMAN_SHRINK_FIELDS只能列出字符串字段名,对象/数组值会被跳过(测试有明确断言)。
对想进一步了解的读者,可直接阅读 压缩核心、代理主入口 与 启动选项 三个文件,配合 README 与上文引用的测试文件,即可完整重建并验证这一中间件的行为边界。
【免费下载链接】caveman🪨 why use many token when few token do trick — Claude Code skill that cuts 65% of tokens by talking like caveman项目地址: https://gitcode.com/GitHub_Trending/caveman1/caveman
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考