内容
# mcp-retry
MCP(Model Context Protocol)服务器的弹性代理。
它位于MCP客户端(Claude Code、Claude Desktop 或其他MCP客户端)和现有的MCP服务器之间,并透明地添加了弹性网络调用所需的特性,但MCP规范尚未标准化:
- **带有指数退避的重试**,针对瞬时错误。
- **基于幂等键的重复数据删除** — 重试调用(相同的工具 + 参数,或显式幂等键)已经成功或仍在进行中,从不重新执行;返回原始结果。
- **TTL结果缓存**,适用于只读/幂等工具,可全局或按工具配置。
- **断路器** — 在针对给定下游服务器连续出现N次故障后,在冷却窗口期内快速失败,而不是持续请求已关闭的服务器。
- **`mcp-retry check`** — 健康检查模式,ping每个配置的下游服务器,并报告正常/异常状态,设计为Claude Code的`SessionStart`钩子运行。
它适用于**任何**MCP服务器:mcp-retry从不检查或硬编码工具模式。您为其提供与直接在MCP客户端配置中相同的启动命令,它中继JSON-RPC协议,保持不变,除了上述弹性行为。
## 为什么存在
MCP自身的路线图讨论多次提出重试、幂等性和结果过期语义作为开放问题:规范定义了传输和消息格式,但对于客户端或中间人在工具调用超时、间歇性错误或成功但响应丢失时应该执行的操作却只字未提。每个MCP客户端和服务器作者都必须自己解决这个问题,或者更常见的是,没有解决方案 — 今天超时的`tools/call`通常只是向用户显示错误,没有安全的方法来知道底层操作是否实际发生,然后再决定是否可以安全重试。
现有的MCP相关工具(Shield、agentfw、mcp-scan等代理/网关项目)关注**安全** — 沙盒、权限范围、提示注入扫描。它们都没有解决更为平凡的问题,即“MCP服务器间歇性超时或错误,我希望调用能够自动恢复”。mcp-retry是一个小型、专注的工具,专门解决这一问题:它不执行安全策略、沙盒或内容检查 — 仅限于传输级别的弹性。
## 安装
```bash
git clone https://github.com/bentabol/mcp-retry.git
cd mcp-retry
pip install .
```
这安装了`mcp-retry`控制台命令(通过`pip install -e .`进行可编辑/开发安装)。需要Python 3.10+。唯一的运行时依赖是`PyYAML`。
## 配置
编写一个配置文件,描述您要包装的下游服务器。每个条目的`command`/`args`/`env`/`cwd`都与您直接在MCP客户端自己的服务器配置中放置的内容完全相同。YAML或JSON都支持(从文件扩展名中选取)。
```yaml
# mcp-retry.yaml
servers:
filesystem:
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allow"]
call_timeout: 20 # 等待一个下游响应的秒数
retry:
max_retries: 3 # 首次之后额外尝试次数
base_delay: 0.5 # 秒;每次重试时翻倍
max_delay: 20
jitter: 0.2 # +/- 20%随机化以避免雷暴
cache:
default_ttl: 0 # 0 = 除非工具覆盖,否则禁用缓存
tools:
list_directory: 30 # 秒;只读工具可以安全缓存
read_file: 15
circuit_breaker:
failure_threshold: 5 # 连续失败次数,触发断路器
cooldown_seconds: 30 # 在试验调用之前快速失败的秒数
idempotency_ttl: 300 # 秒,保留成功结果,以便
# 准确重试(相同参数或显式键)
# 返回结果而不是重新执行
everything:
command: npx
args: ["-y", "@modelcontextprotocol/server-everything"]
```
一个更完整、注释的示例位于[`examples/config.example.yaml`](examples/config.example.yaml)。
### 发送显式幂等键
如果您的MCP客户端希望将特定的工具调用标记为可以重放(例如“充值此卡”,“创建此记录”),则可以通过两种方式之一提供幂等键 — mcp-retry在向下游发送调用之前会剥离第二种形式,因为包装的工具从未请求它:
- `_meta.idempotencyKey`在`tools/call`请求参数(MCP自己的`_meta`约定,用于请求级元数据),或
- `arguments`内的 `_idempotency_key` 条目。
共享键的两个调用被视为相同的操作,无论其其他参数如何,在第一个调用完成后的`idempotency_ttl`秒内。
## 使用
### 作为MCP客户端配置中的直接包装
将您的客户端指向`mcp-retry`而不是真正的服务器,并传递配置文件和要使用的服务器条目:
```json
{
"mcpServers": {
"filesystem": {
"command": "mcp-retry",
"args": ["run", "--config", "/absolute/path/to/mcp-retry.yaml", "--server", "filesystem"]
}
}
}
```
mcp-retry将真正的服务器作为子进程启动,并透明地代理标准输入/输出:`initialize`、`tools/list`、`resources/*`、`prompts/*`和服务器发起的请求(例如采样)都无修改地传递。只有`tools/call`才会进行重试/缓存/重复数据删除/断路器处理,因为只有这种消息类型在现实世界中有重执行的影响。
如果配置中只定义了一个服务器,则可以省略`--server`。
### 健康检查模式
```bash
mcp-retry check --config /absolute/path/to/mcp-retry.yaml
```
```
[UP ] filesystem 180.4ms ok
[DOWN] everything - 无响应,超时10.0s
```
ping每个配置的服务器,发送一个简单的`initialize`请求,并报告正常/异常状态和延迟。如果所有服务器都响应,则退出状态为`0`,否则为`1`。添加 `--json` 以获取机器可读的输出,并使用 `--timeout <seconds>` 修改每个服务器的等待时间(默认为10秒)。
### 将`check`绑定为Claude Code的`SessionStart`钩子
这是日常使用`check`的预期方式:在Claude Code会话启动时运行一次,以便在任务中途发现神秘的工具故障之前,将已死亡或配置错误的下游MCP服务器作为上下文呈现。
在 `.claude/settings.json`(项目级)或 `~/.claude/settings.json`(用户级)中添加:
```json
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "mcp-retry check --config /absolute/path/to/mcp-retry.yaml ; exit 0",
"timeout": 15
}
]
}
]
}
}
```
有关为什么这样编写的说明:
- `matcher: "startup"`仅在全新会话时运行钩子;使用`"resume"`或完全省略`matcher`也可以在恢复/清除/压缩会话时运行。
- **`; exit 0` 是故意的。** Claude Code 仅在 `SessionStart` 钩子退出状态为 `0` 时,将钩子的标准输出注入到会话上下文中;如果退出状态为非零,则仅显示钩子错误通知(stderr 的第一行),而不是完整的结果表。由于 `mcp-retry check` 在任何服务器异常时退出状态为 `1` — 您实际上希望看到这种情况 — 因此强制钩子退出状态为 `0`,确保完整的结果表始终作为上下文到达会话,无论每个服务器是否健康或异常。`SessionStart` 钩子永远不会阻塞会话启动,无论退出状态如何。
- 使用绝对路径 `--config`(或 `${CLAUDE_PROJECT_DIR}` 获取项目相对路径)— 钩子不具备与交互式 shell 相同的当前工作目录假设。
- `timeout` 以秒为单位,应用于整个钩子进程;将其保持在 `check` 自己的 `--timeout` (默认为 10 秒)之上,以防慢速服务器在 `check` 将其报告为异常之前被钩子框架终止。
## 它如何决定什么是可重试的
仅将分类为**瞬时**的故障进行重试:
- 任何传输级故障(超时、断开的管道、子进程退出)始终是瞬时的。
- 如果 JSON-RPC 错误响应为瞬时,则其 `code` 属于通用“服务器错误”范围(`-32000`..`-32003`)或 `-32603`(内部错误)。其他情况 — 无效参数(`-32602`)、工具自己的报告应用程序错误等 — 被视为真实的、可重复的故障,并立即返回给客户端,不进行重试。
这故意是保守的:重试因错误输入而失败的调用只会浪费时间重现相同的故障。
## 开发
```bash
pip install -e ".[dev]"
python -m pytest -v
```
测试套件启动了一个小型可配置的模拟MCP服务器(`tests/mock_server.py`,一个真正的子进程,使用相同的行分隔JSON-RPC标准输入/输出协议),并在真正的`mcp_retry.proxy.Proxy`后面驱动诸如“失败两次然后成功”,“始终失败”和“响应缓慢”等场景,以测试重试恢复、断路器跳闸/半开/恢复、TTL缓存和幂等键重复数据删除与真正的异步I/O,而不是mcp-retry内部的模拟。`tests/test_cli_end_to_end.py`更进一步,通过真正的`python -m mcp_retry run`子进程驱动其真正的标准输入/输出。
## 这不是什么
- 不是安全工具。它不执行沙盒、权限范围或内容检查 — 如果您需要这些,请将其与mcp-scan等工具配对。
- 不是通用MCP网关/多路复用器。一个`mcp-retry run`进程包装一个下游服务器;如果您想扇出到多个服务器,请为每个服务器运行一个`mcp-retry`实例(如下面的配置示例),并将您的客户端指向每个实例。
- 重试和缓存仅适用于`tools/call`。其他所有内容都无修改地通过。
## 许可证
MIT — 请参阅 [LICENSE](LICENSE)。