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>
This commit is contained in:
jocay
2026-10-18 10:23:15 +08:00
parent 63d48ec951
commit 84653bec61
9 changed files with 3083 additions and 752 deletions
+8 -2
View File
@@ -7,12 +7,18 @@
# 认证令牌(必填,若监听 0.0.0.0 对外暴露)
#BOLT_TOKEN=请改成强随机串
# 命令白名单(非空时仅允许列出的命令,用逗号分隔)
# 命令白名单(按程序名精确匹配,逗号分隔;非空时 shell 模式禁止管道/重定向/命令串联等元字符)
#BOLT_ALLOW=git,docker,curl
# 命令黑名单(前缀匹配)
# 命令黑名单(按程序名精确匹配命令中每一段;尽力而为,不能替代白名单)
#BOLT_DENY=rm,mkfs
# 文件工具与 /files 允许访问的目录(逗号分隔;设置了 BOLT_ALLOW 时必须配置,否则文件功能禁用)
#BOLT_FILE_ROOT=/srv/data,/tmp
# PUT /files 单次上传上限(支持 K/M/G,默认 2G)
#BOLT_MAX_UPLOAD=2G
# 默认命令超时毫秒(0 = 无限)
#BOLT_TIMEOUT_MS=30000
+14
View File
@@ -14,6 +14,8 @@
# --allow <a,b> 命令白名单
# --deny <a,b> 命令黑名单
# --timeout-ms <n> 命令默认超时(默认 30000)
# --file-root <d1,d2> 文件工具与 /files 只能访问这些目录(配置 --allow 时必须设置,否则文件功能禁用)
# --max-upload <n> PUT /files 单次上传上限,支持 K/M/G(默认 2G)
# --binary <src> 二进制来源:本地路径 或 http(s) URL(默认从 Gitea Release 下载)
# --no-start 只安装不启动
# --no-enable 不设置开机自启
@@ -24,6 +26,8 @@ TOKEN="${BOLT_TOKEN:-}"
ALLOW=""
DENY=""
TIMEOUT_MS="${BOLT_TIMEOUT_MS:-30000}"
FILE_ROOT=""
MAX_UPLOAD=""
BINARY_SRC=""
NO_START=0
NO_ENABLE=0
@@ -35,6 +39,8 @@ while [ $# -gt 0 ]; do
--allow) ALLOW="${2:-}"; shift 2 ;;
--deny) DENY="${2:-}"; shift 2 ;;
--timeout-ms) TIMEOUT_MS="${2:-}"; shift 2 ;;
--file-root) FILE_ROOT="${2:-}"; shift 2 ;;
--max-upload) MAX_UPLOAD="${2:-}"; shift 2 ;;
--binary) BINARY_SRC="${2:-}"; shift 2 ;;
--no-start) NO_START=1; shift ;;
--no-enable) NO_ENABLE=1; shift ;;
@@ -150,6 +156,8 @@ if [ "$OS" = Darwin ]; then
<key>BOLT_ALLOW</key><string>$(xml_escape "$ALLOW")</string>
<key>BOLT_DENY</key><string>$(xml_escape "$DENY")</string>
<key>BOLT_TIMEOUT_MS</key><string>$(xml_escape "$TIMEOUT_MS")</string>
<key>BOLT_FILE_ROOT</key><string>$(xml_escape "$FILE_ROOT")</string>
<key>BOLT_MAX_UPLOAD</key><string>$(xml_escape "$MAX_UPLOAD")</string>
</dict>
<key>WorkingDirectory</key><string>/var/root</string>
<key>RunAtLoad</key><true/>
@@ -200,6 +208,12 @@ fi
if [ -n "$DENY" ]; then
printf 'BOLT_DENY=%s\n' "$DENY" >> "$ENV_FILE"
fi
if [ -n "$FILE_ROOT" ]; then
printf 'BOLT_FILE_ROOT=%s\n' "$FILE_ROOT" >> "$ENV_FILE"
fi
if [ -n "$MAX_UPLOAD" ]; then
printf 'BOLT_MAX_UPLOAD=%s\n' "$MAX_UPLOAD" >> "$ENV_FILE"
fi
[ -z "$TOKEN" ] && [ -z "$ALLOW" ] && [ -z "$DENY" ] && \
printf '# 默认无认证无白名单;监听公网时请设置 BOLT_TOKEN 与 BOLT_ALLOW\n' >> "$ENV_FILE"