Files
bolt/CLAUDE.md
T
jocay 76f6fe32f5 deploy: 一键更新 / 卸载脚本,安装脚本自动选择 IPv4/IPv6 站点与最新版本
- 新增 deploy/update.sh:只替换二进制并重启服务,保留配置;已是最新时跳过、不自动降级;
  重启由独立进程执行(通过 bolt 自身运行也安全),新版本起不来时自动回滚
- 新增 deploy/uninstall.sh:删除服务、二进制与备份,默认同时删除配置,--keep-config 保留
- install.sh:先试 IPv4 站点(git.jocayn.qzz.io)再试 IPv6 站点(git.draws.qzz.io),
  通过 Release API 获取最新版本,支持 SHA256SUMS 校验与 --version;
  已安装时拒绝覆盖配置(--reinstall 强制);macOS 缺少签名时补 ad-hoc 签名
- .gitattributes 强制 *.sh 使用 LF
- README / CLAUDE.md 更新部署说明与发布流程

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

8.6 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/)

  • 三个独立的 POSIX sh 脚本(需要 root,设计为 curl … | sudo sh 直接运行,所以每个脚本都自带下载/探测函数,不能互相引用):
    • install.sh:首次安装。自动识别 Linux(systemd:/usr/local/bin/bolt、/etc/bolt.env、bolt.service)或 macOS(launchd 标签 io.jocay.bolt,plist 放在 /Library/Application Support/bolt/,启用开机自启时复制到 /Library/LaunchDaemons/)。已安装时拒绝执行,除非加 --reinstall,因为它会按本次参数重写配置。
    • update.sh:只替换二进制,保留配置。重启和健康检查交给独立进程执行(Linux 用 systemd-run,macOS 用 (trap '' HUP; sh … &);macOS 上 nohup … & 启动的进程会在调用返回后消失),失败时从 /usr/local/bin/bolt.bak 回滚。
    • uninstall.sh:先删文件,最后才停服务,因为通过 bolt 自身执行时,停服务会结束脚本本身。
  • 下载站点:IPv4 用 https://git.jocayn.qzz.io,IPv6 用 https://git.draws.qzz.io(同一个 Gitea,仓库 jocay/bolt 已公开)。脚本先用 -4 试 IPv4 站点,再用 -6 试 IPv6 站点;最新版本通过 /api/v1/repos/jocay/bolt/releases/latest 获取(这个 Gitea 不支持 releases/latest/download/ 直链),附件命名为 bolt-<linux|darwin>-<amd64|arm64>,可选附带 SHA256SUMS。BOLT_GIT_HOST、BOLT_VERSION、BOLT_DL_URL、--binary 可以覆盖;BOLT_DL_TOKEN(下载认证)与 --token(服务器的 Bearer 令牌)相互独立。
  • 发布用的二进制用 cargo-zigbuild 编译:x86_64-unknown-linux-gnu.2.28(普通 cargo build 产物依赖构建机的 glibc 版本,较老的系统上跑不了)和 aarch64-apple-darwin。
  • 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 中的参数表。