Files
bolt/CLAUDE.md
T
jocay 84653bec61 feat: v0.2.0 文件传输(MCP 工具 + HTTP 直传)与安全/正确性修复
文件功能:
- 新增 file_read / file_write / file_stat 工具(文本/base64/图片,分块读取,原子替换写入,SHA-256)
- 新增 file_link 工具与 GET/PUT /files 端点:HMAC 签名限时链接或 Bearer 认证,
  支持 Range、HEAD、chunked、Expect: 100-continue,大文件不经过模型上下文
- 新增 --file-root / --max-upload;配置白名单但未限定目录时文件功能自动禁用

安全:
- 白名单改为程序名精确匹配,shell 模式禁止元字符,shell_write 输入逐行受检;黑名单检查每一段
- HTTP 签发 Mcp-Session-Id,按会话隔离 shell 会话与取消,支持 DELETE 终止会话
- HTTP 客户端不能再用 notifications/exit 关停服务;token 常量时间比较

正确性:
- 后台进程占用输出管道时 run_command 不再挂起(detached=true)
- shell_read 改用绝对偏移,修复缓冲裁剪后错位;被信号杀死的会话能报告退出;阻塞读可取消
- 输出与分块读取不再切断 UTF-8 多字节字符
- shell_* 结果改为标准 content + structuredContent
- POST + SSE 的 tools/call 在同一响应流中推送进度与结果
- /mcp 支持 Expect: 100-continue(curl 发送大请求时不再白等 1 秒)
- CLI 参数优先于环境变量;SIGTERM 在 stdio 模式下也能清理退出;Windows 支持 Ctrl+C 清理与优雅退出

资源:并发工具调用 / HTTP 连接 / shell 会话数量上限,空闲会话自动回收

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:23:15 +08:00

7.5 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

项目

bolt 是一个单文件 MCP 服务器(src/main.rs),让 MCP 客户端在主机上执行 shell 命令、读写和传输文件。它手写实现了 JSON-RPC 2.0 + MCP(协议版本 2024-11-05 / 2025-03-26 / 2025-06-18),支持两种传输:stdio(默认)和 Streamable HTTP(--http <addr>)。依赖只有 serde_json,Unix 下再加 libc;base64、SHA-256、HMAC、百分号编码、随机数(/dev/urandom / BCryptGenRandom)都是手写的。请保持这一点:不引入新 crate,不引入 async runtime,也不引入 HTTP/MCP 框架。代码注释、日志信息和文档都用中文,新增内容请保持一致。

常用命令

cargo build --release                 # 产物 target/release/bolt(release 配置:lto=fat、codegen-units=1、strip)
cargo test --release                  # 单元测试(位于 src/main.rs 末尾的 mod tests)
cargo test --release test_policy      # 按名称运行单个单元测试
cargo clippy --release --all-targets  # 当前无警告,请保持
cargo check --release --all-targets --target x86_64-pc-windows-gnu   # 在 Linux 上检查 Windows 分支能否编译
./target/release/bolt --help          # 查看 CLI 参数;每个参数都可用对应的 BOLT_* 环境变量作为回退(CLI 优先)
./target/release/bolt --http 127.0.0.1:8080 --token t --allow "git,ls" --file-root /tmp
  • test_run_command_basic 和 test_shell_session 带 #[cfg(unix)](使用 sh -c、printf、sleep),Windows 上会跳过。Windows 下服务器使用 cmd /C。
  • 端到端测试在 docs/bolt_stdio_test.py(stdio)和 docs/bolt_http_test.py(HTTP,会额外起一个带 --token 和 --file-root 的实例)。它们对管道使用 select,需要 Unix 主机;二进制路径默认是 target/release/bolt,可用 BOLT_BIN 覆盖,例如 BOLT_BIN=/path/bolt python3 docs/bolt_http_test.py。HTTP 测试占用端口 18080 和 18081。

架构(src/main.rs,自上而下)

文件用 // ---- 横幅注释分段:常量 → 配置 → 出站通道 → 应用状态 → JSON-RPC 辅助 → 命令策略 → 编码/摘要/随机数 → run_command 执行引擎 → shell 会话 → 文件 → 工具列表 → MCP 分发 → 客户端作用域 → stdio 传输 → HTTP 传输 → 信号与清理 → main → 测试。

  • App / Shared = Arc<App>:全局状态。包含 Config、按配置生成的 tools、inflight 表(作用域\n请求id → Arc<Killable>)、sessions(shell 会话,带 owner 作用域)、mcp_sessions(Mcp-Session-Id → 最近活动时间)、并发/连接计数,以及 file_link 的签名密钥(启动时随机生成)。
  • Ctx(作用域 + 出口):每个请求都带一个 Ctx { scope, sink, base_url }。scope 用来隔离取消和 shell 会话:stdio 下是 "stdio";HTTP 下是 Mcp-Session-Id,不带该头的请求共用匿名作用域 "-"。sink 决定通知和响应发往哪里:Sink::Stdio、Sink::Hub(hub, scope)(只推给该作用域的 GET SSE 流)、Sink::Chan(当前 POST 的 SSE 响应流)。base_url 由 Host / X-Forwarded-* 推出,只有 file_link 用到。
  • process_message(v, app, ctx, async_exec):两种传输共用的唯一入口。通知返回 None。tools/call 先通过 CallSlot 占用一个并发名额(超限时返回 isError 结果),再注册 Killable。async_exec 为 true(stdio)时在 bolt-tool 线程中执行,完成后经 ctx.sink 发送响应,所以 stdio 响应可能乱序;为 false(HTTP)时同步执行。notifications/exit 只在 stdio 作用域生效。
  • exec_tool_call:按工具名分发。工具执行失败返回 Ok(tool_error(..))(isError: true),成功结果用 tool_ok(v) 包成 content + structuredContent。参数错误或未知工具返回 Err((code, msg)),转成 JSON-RPC 错误。参数用 req_str / opt_u64 等辅助函数读取。新增工具时请保持这一区分,并在 build_tools(cfg) 里补上 schema。文件工具只有在 files_enabled 时才会出现在列表中,file_link 只在 HTTP 模式下出现。
  • 进程生命周期:Unix 下子进程放进独立进程组(pre_exec + setpgid(0,0)),kill_tree 对 -pid 发 SIGKILL;Windows 下用 taskkill /T /F。Killable 处理"pid 注册前就收到取消"的情况。主进程被收割后会把 pid 清零,防止 pid 复用时误杀。主进程退出后,读取线程最多再等 PIPE_DRAIN_GRACE;如果后台进程还占着管道,就标记 detached 直接返回,读取线程转入后台继续排空但不再保存。
  • Shell 会话:SessBuf 里的偏移都是绝对字节偏移(base / read),裁剪缓冲前部不会让读取位置错位。退出信息也放在同一把锁里,Condvar 不会丢唤醒。shell_read 会避免切断 UTF-8 字符,每 200ms 检查一次是否被取消。janitor_loop 每 30 秒回收空闲会话和过期的 MCP 会话。
  • 文件:resolve_path 是所有文件访问(工具和 /files)唯一的路径入口。已存在的路径用 canonicalize 解析符号链接;不存在的路径先规范化最近的已存在祖先,再拼上剩余部分(剩余部分不允许 . / ..),最后检查是否在 --file-root 之内。write_file 的 overwrite 模式写同目录临时文件后 rename。错误类型是 FErr = (HTTP 状态码, 信息),工具和 HTTP 共用。
  • HTTP 传输:手写 HTTP/1.1,每个连接一个线程(conn_loop,受 MAX_HTTP_CONNS 限制)。检查顺序:OPTIONS(CORS)→ Origin 白名单 → Bearer token(ct_eq 常量时间比较;带 sig 的 /files 请求改为在 handle_files 里校验 HMAC 签名)→ 路由到 handle_mcp 或 handle_files。读取 body 之前出错一律关闭连接。请求体统一经 BodyReader 读取,支持 Content-Length 和 chunked。handle_* 对读写端是泛型的,单元测试直接喂内存缓冲。
  • 退出:Unix 下屏蔽 SIGINT 和 SIGTERM,由 sigwait 线程接收信号,执行 shutdown_cleanup 后直接 process::exit(stdio 主线程可能阻塞在读 stdin 上)。Windows 下用 SetConsoleCtrlHandler 做同样的事。
  • 命令策略(policy_check(cfg, command, shell)):allow/deny 都按程序名精确匹配(会去掉路径、引号、转义和 .exe 等扩展名)。配置了 allow 时,shell 模式禁止元字符;deny 会检查按元字符切分后每一段的程序名。作用范围:run_command、shell_open 的 shell 名称、shell_write 写入的每一行。配置了 allow 但没有 --file-root 时文件功能禁用。

各种上限和默认值是文件顶部的 const(MAX_HTTP_BODY、MAX_CONCURRENT_CALLS、SESSION_IDLE、DEFAULT_READ_LEN、DEFAULT_MAX_UPLOAD 等)。

部署(deploy/)

  • install.sh 是需要 root 的 POSIX sh 安装脚本。自动识别 Linux(systemd:/usr/local/bin/bolt、/etc/bolt.env、bolt.service)或 macOS(launchd 标签 io.jocay.bolt,plist 放在 /Library/Application Support/bolt/,启用开机自启时复制到 /Library/LaunchDaemons/)。默认从 Gitea Release 下载 bolt-<linux|darwin>-<amd64|arm64>(版本由 BOLT_VERSION 指定,默认 v0.1.0;可用 BOLT_DL_URL 或 --binary 覆盖)。BOLT_DL_TOKEN(下载认证)与 --token(服务器的 Bearer 令牌)相互独立。
  • bolt.service 和 bolt.env.example 用于手动 systemd 部署。监听地址写在 unit 文件的 ExecStart 里,其余配置来自 /etc/bolt.env 中的 BOLT_* 变量。
  • 修改 CLI 参数时,需同步更新 parse_args/print_help、install.sh(参数解析及其生成的 env/plist)、bolt.env.example 和 README 中的参数表。