内容
# resilient-read
[](https://pypi.org/project/resilient-read/)
MCP 服务器,允许编码代理安全地以**小、可续读的块**读取非常大的文件。
## 为什么存在
当上下文窗口很小时,朴素的全文读取很脆弱且昂贵。`resilient-read` 提供基于游标的确定性分页和偏移检测。
## 工具
- `rr.stat` - 获取文件元数据(`size`,`mtime_ns`,可选 `sha256`)
- `rr.read_bytes` - 读取带有 `offset`、`max_bytes`、`next_offset`、`eof` 的字节窗口
- `rr.read_lines` - 读取用于代码/日志分析的行号切片
- `rr.read_tail` - 读取追加文件中的最新行
- `rr.search_then_page` - 搜索带有上下文摘录和 `next_from_line`
- `rr.make_cursor` - 铸造可续读的游标令牌
- `rr.read_next` - 从游标读取下一个块(如果文件更改则失败)
## 安装
```bash
uv sync
```
PyPI(最新版):[https://pypi.org/project/resilient-read/](https://pypi.org/project/resilient-read/)
从 PyPI 安装:
```bash
pip install resilient-read
```
使用标准输入输出运行(默认):
```bash
uv run resilient-read
```
使用 SSE 运行:
```bash
uv run resilient-read --transport sse --host 127.0.0.1 --port 8000
```
使用可流式 HTTP 运行:
```bash
uv run resilient-read --transport http --host 127.0.0.1 --port 8000
```
## 路径解析:CWD、工作区、状态
三个独立的概念:
| 环境变量/源 | 目的 | 默认值 |
|---|---|---|
| **CWD**(进程工作目录) | 代理提供的相对路径的默认锚点 | 从启动器继承 |
| `RR_WORKSPACE` | *访问边界* — 路径必须解析到这些根目录之一 | CWD |
| `RR_STATE_DIR` | 将来的 resilient-read 状态存储位置 | CWD |
### 解析规则
- **相对路径** 首先在 **CWD** 中锚定。如果文件不存在,则按顺序尝试每个工作区根目录(第一个匹配项获胜)。找不到文件的回退是 CWD 相对候选路径,因此错误消息一致。
- **绝对路径** 仅当它们位于 *某些* 工作区根目录内时才被接受。读取所有已配置工作区外部的内容都会被 `permission_denied` 拒绝。
- **CWD 自动添加到** 工作区列表中,如果它已经不在一个已配置的根目录中,因此纯相对路径始终在访问边界内。
- **状态与工作区解耦**。设置 `RR_STATE_DIR` 以将状态放在特定位置;否则,它存储在 CWD 中。
`RR_WORKSPACE` 接受:
- **纯字符串**:`"/Users/jay/my-project"`
- **`os.pathsep` 列表**:`"/Users/jay/a:/Volumes/Lux/dev/b"`
- **JSON 数组**:`["/Users/jay/a", "/Volumes/Lux/dev/b"]`
### 命令行界面
```bash
# 单个工作区
resilient-read -w /Users/jay/proj-a
# 多个工作区(可重复)
resilient-read -w /Users/jay/proj-a -w /Volumes/Lux/dev/proj-b
```
命令行参数优先于 `$RR_WORKSPACE`。
## MCP 配置(标准输入输出)
```json
{
"mcpServers": {
"resilient-read": {
"command": "uvx",
"args": ["resilient-read"],
"env": {
"RR_WORKSPACE": "[\"/Users/jay/proj-a\", \"/Volumes/Lux/dev/proj-b\"]"
}
}
}
}
```
### 从源代码运行:使用 `--project` 而不是 `--directory`
通过 `uv run` 从本地源代码检出运行,使用 **`--project`** 而不是 `--directory`:
```json
{
"command": "uv",
"args": ["run", "--project", "/path/to/resilient-read-src", "resilient-read"],
"env": { "RR_WORKSPACE": "[\"/Users/jay\"]" }
}
```
`uv run --directory X` **更改目录** 到 X 然后运行,这将服务器的 CWD 固定在其自己的源代码树中 - 来自代理的相对路径然后在那里解析,而不是在代理的项目中。`--project X` 定位包而不更改 CWD,因此服务器继承启动器的 CWD(您的活动项目)。对于任何解析用户提供的文件路径的 MCP 服务器,请优先使用 `--project`。
`RR_STATE_DIR` 是可选的 - 当未设置时,状态存储在 CWD 中。resilient-read 目前是无状态的;`RR_STATE_DIR` 存在以支持向前兼容。
## MCP 配置(SSE)
```json
{
"mcpServers": {
"resilient-read": {
"type": "sse",
"url": "http://127.0.0.1:8000/sse"
}
}
}
```
## MCP 配置(HTTP)
```json
{
"mcpServers": {
"resilient-read": {
"type": "http",
"url": "http://127.0.0.1:8000/mcp"
}
}
}
```
## 示例工作流
### 基于游标的文件分页
1. 调用 `rr.stat(path="large.log")`
2. 调用 `rr.make_cursor(path="large.log", offset=0, max_bytes=65536)`
3. 循环调用 `rr.read_next(cursor=...)` 直到 `eof=true`
### 目标搜索分页
1. 调用 `rr.search_then_page(path="server.log", query="timeout", max_matches=3)`
2. 随后调用 `rr.search_then_page(..., from_line=<next_from_line>)`
每个响应都很小且可组合,因此您可以在大型文件中处理大量数据,同时保持在小模型上下文内。
## Claude Code: “文件尚未读取” 门控
Claude Code 内置的 `Edit` 和 `Write` 工具强制实施 “必须先读取文件” 的前提条件,该条件仅由内置的 `Read` 工具满足 - 服务的 MCP 读取(如 `rr.read_lines`)不注册。因此,在 `rr.*` 读取后立即进行 `Edit` 操作会因 `File has not been read yet` 而失败,Claude 必须重试。在 PreToolUse 钩子触发之前会检查前提条件,因此 PreToolUse 钩子无法绕过它。
可行的桥梁是 `mcp__resilient-read__*` 上的 **PostToolUse 钩子**,它会注入一个系统提醒,告诉 Claude 确切的 `Read(file_path=...)` 调用,以便在编辑同一路径时发出。如果它打算编辑同一路径,则需要额外的廉价读取操作,而不是失败的编辑然后读取重试循环。
一个现成的脚本随存储库一起提供: [`examples/claude-code-hooks/resilient_read_nudge.py`](examples/claude-code-hooks/resilient_read_nudge.py).
添加到 `~/.claude/settings.json` 中:
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "mcp__resilient-read__.*",
"hooks": [
{
"type": "command",
"command": "python3 /path/to/resilient-read/examples/claude-code-hooks/resilient_read_nudge.py"
}
]
}
]
}
}
```
每次 `rr.*` 读取后,Claude 都会看到带有确切 `Read(file_path="<abs>")` 调用 的系统提醒,以便在任何编辑/写入之前发出。
## 项目维护
- 版本说明和历史记录:`CHANGELOG.md`
- 手动 semver 标签流程:`docs/RELEASE.md`
- JSON-RPC 示例:`examples/`