mcpField
S
sift

sift

本地服务
github.com开发工具07 周前更新

关于 sift

sift 是面向开发者的 MCP 服务和命令行工具,可运行命令并筛选日志、构建与测试输出。它保留完整原始内容,只展示模型选中的行,并可随时查看遗漏部分。

主要功能

  1. 01运行命令并保存原始输出
  2. 02筛选重要行并标记省略内容
  3. 03查看捕获内容的任意范围
  4. 04摘要日志、JSON 和源代码结构
  5. 05后台运行并分段跟进输出
  6. 06脱敏后请求模型选择行号

适用场景

  1. 01从冗长测试输出中定位失败
  2. 02检查保存的 CI 日志或崩溃转储
  3. 03逐步查看持续运行的构建输出
  4. 04浏览源文件声明而不显示函数体

MCP 相关

作为 MCP 服务,sift 可接收客户端命令并处理其输出;其 hook 也可在客户端执行 shell 命令前进行路由。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"slymnysr-sift": {
"command": "uvx",
"args": [
"sift-cli"
],
"env": {
"NVIDIA_API_KEY": "<NVIDIA_API_KEY>"
}
}
}
}
启动命令uvx sift-cli
需要配置的环境变量
NVIDIA_API_KEY密钥A free NVIDIA NIM key. Optional: without one every tool still runs the command, still keeps every byte, and still returns the exit code -- what is lost is the model's choice of lines, not the output. Also read from ~/.config/nvidia/api_key.

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypisift-cli1.1.0

工具

7

收录的工具列表 · 更新于 3 周前

参数类型必填说明
cwdany否
keepany否
budgetany否
commandstring是
timeoutany否
backgroundboolean否

详细介绍

sift

运行你的命令,然后只把重要的行交给模型。

测试套件输出了 4,000 行,其中 11 行是失败信息。构建过程输出的进度条重绘了 900 次。安装过程列出了它处理过的每个软件包。所有内容都会进入上下文窗口,而且——这才是代价所在——之后的每一轮都会完整重新发送这些内容。

sift 是一个 MCP 服务和命令行工具,用来解决这个问题。它自行运行命令,将每个字节都保存在磁盘上,并向模型提供一份视图:失败信息、摘要,以及读者真正会停下来看的行。其余内容会被标记,而不是删除。

两个数字,都经过实测,也都能在此仓库中复现。运行本项目自带的测试套件会输出 652 行——按模型自己的分词器计算,共 21,392 个 token。最终返回的是 8 行、208 个 token:减少 99%(python test/kazanc.py)。去掉几乎所有内容只是容易的那一半。在一个包含 22 组真实构建和测试输出的语料中,默认预算保留了读者不可或缺的 140 行中的 138 行(python test/budget.py)。

$ sift run -- pytest
FAILED test/test_auth.py::test_expired_token - assert 401 == 200
  ...
─ 3,914 lines not shown · sift peek 9f2c41ab for any of them ─
=== 1 failed, 212 passed in 18.4s ===

对于你没有生成的文件——日志、已保存的 CI 记录或崩溃转储——无需运行,也能进行同样的处理:

$ sift digest ci-run-8812.log
2026-09-02T04:11:07 Building 214 targets
─ 38,904 lines not shown · sift peek ci-run-8812.log for any of them ─
ERROR: //src/parser:parse_test failed in 4.1s
─ 1,022 lines not shown · sift peek ci-run-8812.log for any of them ─
FAILED: 1 of 214 targets

列表不是按行读取的。为机器编写的 JSON 数组通常完全没有换行符,而且其中某一行也没有值得挑选的内容,因此会以记录为单位,并遵循同样的规则:

$ sift digest export.json
{"id": 3, "status": "failed", "error": "connection refused"}
─ 412 records not shown · sift peek export.json for the text they came from ─
{"id": 416, "status": "ok"}

如果把同样的问题用在源代码上,得到的就是目录:

$ sift outline src/parser.rs
pub struct Parser {
pub fn parse(input: &str) -> Result<Ast, Error> {
─ 34 lines not shown · sift peek src/parser.rs for any of them ─
impl Iterator for Tokens {

对于不会结束的命令——开发服务器、日志跟踪,或你想让它持续运行的构建——则先启动它,再一次读取一段:

$ sift run --background -- cargo build --release
9f2c41ab
$ sift follow 9f2c41ab
   warning: unused import: `std::fmt`
─ 212 lines not shown · sift peek 9f2c41ab for any of them ─
   error[E0308]: mismatched types
sift 9f2c41ab · running · new lines 1-247 · 2 shown · nemotron
$ sift follow 9f2c41ab          # only what has arrived since
$ sift follow --all --wait 30   # every run at once, holding for something new
$ sift stop 9f2c41ab            # ends it, and everything it started

内容不会重复显示,行号也取自这次运行:只要捕获内容还在,sift peek 中的第 247 行就始终是第 247 行。

由谁决定

由免费模型决定。sift 会将带编号的行发送给它,并只问一个问题:哪些编号重要? 它只返回编号,其他回复一概不用——你在上面看到的文本,都是从本地捕获文件中逐字节打印出来的。

这就是全部诀窍,也正因如此,它不是摘要器。摘要器可能会误述某一行的内容;sift 不会,因为它从不编写任何行,只负责挑选。

这也意味着,语言覆盖范围不必列成清单。模型能读懂土耳其语、日语、阿拉伯语和印地语;也能读懂 Rust、COBOL、Mojo,甚至上周刚发布的语言。这里没有逐一列举它们,因此也不会漏掉任何一种。

三条规则

显示的内容绝不凭空捏造。 判断器返回行号。文本始终来自本地文件。

内容绝不丢弃。 sift peek <handle> 会返回未经更改的原始捕获内容。视图中的每个间隔都会标明其中包含多少行。

命令绝不会因此出故障。 无 API 密钥、无网络、端点过载、回复毫无意义、提炼器出现错误,或因决定不让任何内容离开这台机器而无法使用——以上任何一种情况出现时,都会回退到不依赖这些条件的规则。命令照常运行;你仍能获取其输出和退出代码。

离开本机的内容

只有一样:问题文本。发送之前,所有看起来像凭据的内容都会被替换——带有已知前缀的令牌、JWT、授权标头、连接字符串中的密码,以及 PEM 块的正文。

你可以放心阅读这些内容。模型只会被要求提供行号,而这些行会从你自己的文件中打印出来,因此,即使某一行在发出时被屏蔽,你仍能看到它的完整内容。

有三个开关:

SIFT_MAX_CAPTURE=0  # keep everything a command writes, however much that is
SIFT_BASE_URL=... # ask your own endpoint instead, and no key is wanted
SIFT_MODELS=a,b   # which models to ask there, best first
SIFT_NO_MODEL=1   # never send anything; use the deterministic view
SIFT_MASK=0       # send unmasked
SIFT_CACHE=0      # ask again, even about text already answered
SIFT_EFFORT=low   # let the model think less, and lose some of what matters
SIFT_PATIENCE=0   # one quick pass only; do not wait out a busy hour

屏蔽并不完整,也不声称能做到完整:看起来不像任何特定内容的裸密钥仍可能漏过。只有 SIFT_NO_MODEL 能提供保证。

第四项是一种经过衡量的取舍,默认关闭。要判断一份 404 行的构建日志中哪些行重要,模型会写出约 900 个令牌的推理内容,才得出一个 12 个令牌的答案,而你要等 15 秒。设置为 SIFT_EFFORT=low 时,同一个问题只需 2.4 秒——但在整个语料中,会漏掉 7.8% 读者离不开的行,恰好出现在这个工具存在的原因所在:大型机作业的返回码、崩溃循环的诊断信息。可以选择速度,但它不是默认选项,代价也已明示。

第三项是为了避免重复付费。对于完全相同的字节和完全相同的问题,已经给出的答案会被再次使用,而不是再次购买——保存下来的只有数字,不会保存视图,因此文本仍会从你自己的文件中呈现,间隔标记仍会指向你自己的捕获内容。不需要请求的视图会显示 (remembered),而不是模型名称。

捕获的字节永远不会离开 $SIFT_HOME(默认值为 ~/.cache/sift)。不会上传任何内容,也不会在其他地方记录任何内容;删除捕获目录,就会删除其中曾保存的所有内容。

每次捕获最多保留 1 GB。这远超任何实际构建日志,以及命令陷入循环时几秒钟产生的内容——而后者正是此工具存在的原因:内容不会被丢弃,也不会占满他人需要的磁盘。读取永远不会停止——如果管道中的内容无人读取,命令就会停止,而这是本工具绝不会做的事——因此命令会正常完成,退出码由命令自身决定,页脚会显示 kept the first 1,073,741,824 bytes of it,而不是让你误以为拿到了完整运行记录。对于宁愿占用磁盘也要保留全部内容的人,可以设置 SIFT_MAX_CAPTURE=0 关闭上限。

它们也不会自行消失。本工具不会在后台清理、过期或整理内容:只有 sift gc [DAYS] 会删除捕获内容,而且只会在你输入命令时删除,不会提前删除。删除后会为每个句柄留下一行记录——删除时间和大小,而不是命令内容——这样,两周后查看间隔标记时,会看到 "removed on the 8th",而不是看到它对一个虚构句柄给出的答案。

命令

sift run [--timeout SECONDS] [--shell] [--background] [--cwd DIR]
         [--budget LINES] [--keep PATTERN] [--] COMMAND...
sift follow [HANDLE] [--all] [--wait N]
                          what a background run has said since you last looked
sift stop [HANDLE]        end it, and everything it started
sift outline PATH|-       what a file declares, without its bodies
sift digest PATH...|-     what is in files somebody else produced
sift peek HANDLE|PATH [FIRST] [LAST]
sift hook                 answer one shell-command event on stdin
sift mcp                  speak the protocol on stdin, for a client
sift tools                which dense tools this machine has
sift tool NAME [ARGS...]  run one of them, distilled
sift memory [TERM]        what has been run here before, and how it went
sift list [COUNT]         what is running, and what has been run
sift stats [COUNT]        what the shortening cost, and what it saved
sift gc [DAYS]            remove captures older than that, and say what went

将 - 作为路径会读取标准输入,这是接收他人输出的另一种方式:

$ journalctl -u nginx --since yesterday | sift digest -
Sep 08 04:11:07 nginx[2114]: worker process 2119 exited on signal 11
─ 8,204 lines not shown · sift peek 7c1a04e9 for any of them ─
Sep 09 01:02:55 nginx[2114]: signal 15 (SIGTERM) received, exiting

管道传来的内容没有可供他人再次输入的路径,因此会单独保存为一份捕获内容,间隙标记也会指向它。第二条规则正是原因:如果视图省略了若干行,却指向一个临时文件,那么等别人读取时,这个文件早已不复存在。

同时查询传给 digest 的多个路径,而 --all 会一次性跟进所有正在运行的命令。这两者表达的是同一个想法:等待是成本,所以只等一次。--wait N 会一直等到某次运行确实有了结果,而不是回答说目前还没有发生任何事。

sift hook 是捕获客户端自行运行的 Shell 命令的方式,无需代理:将客户端的工具前置钩子指向它,每条 Shell 命令都会先经过这里。所有内容都会被路由——这里不会猜哪些命令值得捕获,因为这样的命令清单无异于一份工具清单,而且命令会输出多少内容,在运行前无法得知。路由所有内容没有成本:十二行视图就是十二行。它会在出错时放行,因此其中的 Bug 不会改变你的 Shell 原样,而 SIFT_HOOK=0 可以将其关闭。

这是最有用的部分,也是唯一需要你启用的部分,因此它会主动提供自身,而不是等着被发现:

sift hook --install      # says what it gives and what it costs, then asks
sift hook --uninstall    # and takes it back out

它会在客户端的设置中写入一行,保留原文件副本,并且不触碰其中的其他内容。sift run 只会提到它一次,然后就不再提了。

sift tool 会运行三个程序之一,它们可以在不打开文件的情况下回答问题——sg(ast-grep)用于结构化搜索,diff(difftastic)用于区分重新缩进与内容更改的差异比较,loc(scc)用于统计代码树的大小。它们的输出天生就很大,这正是它们适合放在这里的原因。此软件包不附带任何二进制文件:sift tools 会说明这台机器上有哪些工具及其名称,是否安装则由你决定。

sift memory 完全不调用模型。它要回答的是计数问题——出现了多少次、结果如何、哪个命令在这里一次也没成功过——而让模型计数速度更慢、需要一次请求,而且有时还会出错。模型只负责判断无法计算的内容,除此之外一概不管。

sift stats 会说明缩短内容节省了什么、付出了什么代价,并将两者分开,因为它们不是同一类数字。比例是此工具根据其持有的字节自行计算的,因此是精确值。成本则是端点对其自身 Token 数量的统计,因此是测量值。旁边现在还会显示真正需要关注的数字——输出在发送时由同一分词器计算出的 Token 数量:

$ sift stats
handle    captured    shown  part  asks  weighed    cost  command
9f2c41ab  62,003 B    450 B  0.7%     1   21,392   5,120  pytest -v

1 run · 62,003 B captured · 450 B shown · 0.7% of it
cost 5,120 tokens, as the endpoint counted them
the output put to a model weighed 21,392 tokens -- numbering and question included

最后这一行比单纯的捕获内容多了一点,因为这些行发出时带有编号和前置问题,它会如实说明这一点,而不是减去一个猜测值。它也会让报告不那么好看:对于一个小型命令,提问的成本可能超过输出本身,这时你就会看到这一点。端点未统计的运行会以短横线显示,并从两个总数中排除,而不是用字节数除以四来填补。

--keep PATTERN 会显示所有匹配的行,不受其他选择和预算限制。模式由你提供,而不是由此工具猜测——这是这里唯一由模式决定任何内容的地方,而且只有在你输入后才会起作用。--budget LINES 是单个视图的上限。

在 Agent 中

相同的答案也可通过 MCP 获取,而这正是它们最能发挥价值的地方:工具 结果会在之后的每一轮中重新发送,因此,不放进对话记录的构建日志 会始终留在记录之外。

uv tool install "sift-cli[mcp]"   # or pipx; see Installing
claude mcp add --scope user sift -- sift-mcp

密钥从 ~/.config/nvidia/api_key 读取,因此无需将其放在 环境变量中,也无需粘贴到任何地方。--scope user 会将服务配置到 所有项目中,而不是只配置到你当前所在的项目。

然后重启客户端。MCP 服务会在会话开始时连接,因此你运行该命令的会话 看不到这个服务。

没有密钥时,服务仍会启动,而所有需要模型的工具都会说明原因并拒绝执行, 因此 Agent 会改用自己的 shell,而不是得到一个它无法分辨好坏的更差答案。

任何支持 stdio 的客户端都可以使用。命令是 sift-mcp,而 sift mcp 会启动同一个服务——这是为更愿意按软件包自身名称运行它的客户端准备的 命令(uvx --from "sift-cli[mcp]" sift-cli mcp,MCP 注册表条目中列出的就是这个命令)。它提供 run、follow、outline、digest、digest_many、tool 和 peek。list 和 stats 则刻意不予提供:它们会把这台机器上最近运行的所有命令交给模型, 包括模型从未询问过的命令;而在终端前的人本来就有权访问这些信息,连接到套接字的模型却没有。

由于客户端看不到 stderr,每个结果的最后一行都会说明你看到的内容:句柄、 命令如何结束,以及模型是否选出了行,或是没有找到可用的模型。

安装

uv tool install sift-cli            # the command line, no dependencies at all
uv tool install "sift-cli[mcp]"     # and the MCP server

pipx install 也能完成同样的操作。两种方式都会将 sift 和 sift-mcp 放入各自独立的环境,并添加到 PATH 中,这正是命令行工具所需要的:这里的任何内容都不应放进你用于构建的 Python 环境。

在已激活的虚拟环境中,直接运行 pip install sift-cli 也可以。 但它不能用于 Debian、Ubuntu 或 WSL 上的系统 Python——这些系统带有 EXTERNALLY-MANAGED 标记,因此 pip 会按设计拒绝安装:

error: externally-managed-environment
× This environment is externally managed

这种拒绝是正确的,解决办法不是 --break-system-packages。请使用 uv tool 或 pipx。

软件包名是 sift-cli,命令是 sift 和 sift-mcp。名称不同是因为 PyPI 上的 sift-mcp 属于其他项目——一个与此无关、用于授权 Agent 操作的 MCP 服务。这里的内容不属于他们, 那边的内容也不是这里的内容。

需要 Python 3.12 或更新版本,命令行工具没有依赖项。

你需要一个模型——密钥,或自己的模型

sift 会询问模型哪些行很重要。有两种方式可以提供模型,你只需要其中一种。

免费密钥

**不随软件提供,也不可共享。**密钥必须属于你。它是免费的,获取只需一分钟:

  1. 在 https://build.nvidia.com 获取密钥
  2. 将其放在 sift 会查找的位置:
export SIFT_API_KEY=nvapi-...            # or NVIDIA_API_KEY
# or, once and for good:
mkdir -p ~/.config/nvidia && echo 'nvapi-...' > ~/.config/nvidia/api_key
或者使用自己的模型,完全不需要密钥

指定 sift 使用某个地址,它就会向该地址发起请求。问题本身不会改变;请求会按常规的 OpenAI 格式发送到端点,而没有密钥可填时,也不会发送 Authorization 标头。

export SIFT_BASE_URL=http://localhost:11434/v1   # Ollama's own port
export SIFT_MODELS=the-model-you-pulled          # whatever that server calls it

同样的两行配置适用于 llama.cpp(--api)、vLLM、LM Studio、LocalAI、公司网关,或任何支持 POST /v1/chat/completions 的其他端点。SIFT_MODELS 接受以逗号分隔的模型列表,并按顺序尝试。

你输入的地址会被视为一个决定:如果缺少密钥,不会收到任何警告,MCP 服务也不会拒绝请求。该端点需要什么凭据,由你和它自行决定。

上面故意没有列出模型名称。这里测试的是这种模式,而不是模型列表:测试套件证明会向你自己的端点发起请求,而且 不带密钥。这里尚未衡量哪个本地模型回答得好,贸然推荐某个模型也名不副实——你可以用 test/budget.py 中的语料库,针对自己实际运行的输出进行评估,得出结论。

两者都没有时

我们有意对两种调用方作出不同的回应:

  • 在终端中,一切照常运行——命令、字节、退出码、第三条规则——同时会显示醒目的横幅,说明这些内容并非由模型挑选,并提醒你看到的是输出的两端。
  • 通过 MCP,工具会拒绝调用并说明原因,还会告诉 Agent 改用自己的 shell。人可以看到降级后的视图并自行判断;模型拿到的则是一段短文本,附带的页脚没有理由让它怀疑,而悄然变差正是这套机制绝不会对无法核查的读者做的事。

如果你想在没有模型的情况下运行,请设置 SIFT_NO_MODEL=1。这是主动选择,而非疏忽;一切都能正常工作,也不会有任何说教。

贡献与问题报告

CONTRIBUTING.md 是指南:有意采用两种语言、三条不可妥协的规则,以及针对每条规则的变异测试。SECURITY.md 说明了哪些内容会运行、什么会离开这台机器,以及如何私下提交安全问题。

构建过程

共二十四个阶段,每个阶段都在下一个阶段开始前完成;notlar/ 中的说明记录了每个阶段的决策及其代价。notlar/00-PLAN.md 概述了整个过程,包括那些我们有意不构建的内容及其原因。

测试位于 test/。旁边还有 test/mutations.py,它会逐一破坏代码所遵循的每条规则——共 162 条——并检查测试套件是否能发现问题。测试套件全绿,只能说明测试没有反对这个版本的代码,并不代表它们能发现更糟糕的版本。

文档还说明了测试电池中断时会发生什么,因为它确实中断过:磁盘上遗留的一处断点躲过了所有常规测试运行,在有人查看之前让机器瘫痪了六次。test/conftest.py 现在会修复其中一个问题;而它破坏的规则也有双重保障,因此任何单次编辑都无法把 sift stop 变成向你拥有的一切发送信号的命令。详见 notlar/19。

许可证

MIT。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。