Repository navigation
重点:MCP 会话不再过期(修复 transport session expired / tools 为空)
根因:fastmcp 4.1.0 起,Streamable HTTP 会话默认空闲 1800 秒就销毁(4.0.x 永不过期)。
由于依赖此前只做范围锁定,同一个 commit 在不同时间构建会装到不同 fastmcp 版本(4.0.3 / 4.1.4)——代码没改、行为变了。
长连接客户端(Hermes 等)空闲后会收到 404 Session not found,表现为 transport session expired、tools 列表为空、反复重连。
修复
- MCP 会话永不过期:新增
core/mcp_session.py,默认http_session_idle_timeout = None;可用MCP_SESSION_IDLE_TIMEOUT=<秒数|none>覆盖(想回收孤儿会话可设86400)。 - 依赖锁到传递依赖:
requirements.txt精确 pin + 新增requirements.lock(71 包全锁),Docker 构建只从 lock 安装。 - 基础镜像按 digest 固定:
FROM python:3.11-alpine@sha256:79440cc1...(manifest list,含 amd64 + arm64),上游重发 tag 不再影响构建结果。 - 容器日志
get_container_logs:tail之前完全失效(docker-py7.x 要求int,传字符串会被静默降级为全量);超大日志现在按最终响应体积截断(保留末尾约 350KB + 提示),不再触发客户端 1MiB SSE 上限报错。 - 关于页「复制」按钮:内网 HTTP(非安全上下文)下
navigator.clipboard不存在导致按钮无反应,改为能力检测 +execCommand兜底。 - 静态文件路由路径穿越(安全):
/ui/static/{path}未归一化,编码后的..%2f可越界读取仓库文件(如secrets/auth.yaml);现在越界一律 404。 INITIAL_API_KEYS幂等更新:已存在的同名 key 现在会按环境变量覆盖,而不是静默跳过。- 移除 MCP 请求级超时中间件:避免请求级超时打断长连接(SSE)会话。
升级须知
- 容器重启会让所有 MCP 会话失效:会话表在内存中,重启后客户端会收到一次
404 Session not found(服务端日志Rejected request with unknown or expired session ID),随后自动重新initialize恢复。属预期行为,不是故障。 Missing session ID(400)是 MCP 规范行为,需客户端保存并回传mcp-session-id。- 读容器日志请用
get_container_logs(container_id, tail, since),不要exec cat /proc/1/fd/1(会阻塞并拖死 SSE 流)。
镜像
ghcr.io/sopyk/docker-mcpilots:v2.1.3 (latest)
sopyk/docker-mcpilots:v2.1.3 (latest)
digest: sha256:fbe2926a5b75d1a41b2cd1892380d8afd69813bff575dddcf89f9795117cafc7
校验版本标签(避免拉到旧镜像):
docker image inspect ghcr.io/sopyk/docker-mcpilots:v2.1.3 \
--format '{{index .Config.Labels "org.opencontainers.image.version"}}'
# 期望输出 2.1.3验证
pytest tests/205 passed;scripts/audit.shE2E 7 passed- 默认容器空闲 65 秒后
tools/list仍 200(24 个工具) MCP_SESSION_IDLE_TIMEOUT=2的容器 5 秒后 404(超时路径可用)tail=50在 4 万行日志容器上恰好返回 50 行- 2.8MB 日志不传
tail:成功返回末尾约 350KB + 截断提示,响应 715KB(低于 1MiB 上限) - 标准 MCP SDK 客户端:initialize / 24 tools /
list_containers/get_container_logs全部成功 - 实景(NAS + Hermes):enable → 24 tools → 调用成功;空闲 30 分钟后再调用,无
transport session expired