# Streamable HTTP 部署指南
English • Português • 中文
Winx 通过经过身份验证的 **Streamable HTTP** 端点向 ChatGPT、云端智能体、远程自动化流程以及无法直接启动本地 stdio 进程的客户端公开可配置的 MCP 工具集。端点统一为 `/mcp`,默认监听在 `127.0.0.1:8000`,默认工具配置仍为 `full`。
由于该端点提供了真实的 Shell 和文件系统控制能力,Winx 采取了**安全失败(Fail-Closed)**的设计策略:强制要求强凭证、除非显式声明否则拒绝非回环绑定、对请求资源消耗设置严格上限,并在不同主体之间进行严格的安全隔离。
## 配置概览
| 属性 | 默认值 |
| :--- | :--- |
| MCP 端点 | `/mcp` |
| MCP 协议版本 | `2026-07-28` |
| 监听地址 | `127.0.0.1:8000` |
| 认证机制 | `Authorization: Bearer ` |
| 最小 Token 长度 | 32 字节 |
| 远程会话亲和性 | `workspace`(工作区亲和) |
| 请求体上限 | 64 MiB |
| 请求超时时间 | 120 秒 |
| 全局并发上限 | 32 个并发请求 |
| 单来源 IP 限流 | 120 请求/分钟 |
| 认证失败延迟响应 | 100 毫秒 |
| 未使用 Guardian TTL | 1,800 秒(30 分钟) |
| 已使用 Guardian TTL | 86,400 秒(24 小时) |
| 存活 Guardian 配额 | 32 |
Winx 同时支持现代无状态 MCP 调用与传统的 HTTP 会话初始化流程。通过 HTTP 与 stdio 可调用完全相同的工具、Prompt、资源及可选的 MCP Tasks。
## 架构设计
```text
远程 MCP 客户端
│ HTTPS + Bearer Token
▼
私有隧道 / VPN / 认证 HTTPS 反向代理
│ 回环 HTTP
▼
127.0.0.1:8000/mcp
│
├─ Host 头部与请求体校验
├─ 超时、限流与并发控制
├─ 主体身份验证
├─ 会话亲和性解析
└─ thread_id / MCP Task 隔离
│
▼
共享 WinxService
│
▼
winxd (控制守护进程)
│
└─ 每个逻辑会话专属 winx-guardian
│
└─ 真实 PTY / Bash 或 zsh / 前台与后台任务
```
在 Linux、macOS 和 WSL2 上,`winx-code-agent` 仅作为 MCP 适配层。`winxd` 管理控制平面,每个 `winx-guardian` 独立控制一个 PTY。断开 HTTP 连接或重启适配层不会终止后台 PTY。
原生 Windows 环境使用内置运行时(Embedded),会话生命周期与服务器进程相同。需要持久化远程会话时推荐使用 WSL2。
## 快速上手
安装 Unix 三件套二进制文件:
```bash
cargo install winx-code-agent
```
生成仅当前用户可读的强 Token 文件:
```bash
mkdir -p ~/.config
install -m 600 /dev/null ~/.config/winx-http-token
openssl rand -hex 32 > ~/.config/winx-http-token
```
在回环接口上启动 Winx:
```bash
winx-code-agent serve --http \
--bind 127.0.0.1:8000 \
--token-file ~/.config/winx-http-token
```
配置客户端:
```text
URL: http://127.0.0.1:8000/mcp
Authorization: Bearer <~/.config/winx-http-token 的内容>
```
云端客户端需要公网可达的 HTTPS 接口。请保持 Winx 监听在回环地址,并在其前方配合私有隧道、VPN 或认证反向代理。
强烈推荐使用 `--token-file` 代替 `--token`,避免敏感凭证出现在进程列表(`ps`)、Shell 历史及日志中。`WINX_HTTP_TOKEN` 环境变量可作为单主体部署的环境变量备选项。
## 工作区会话一致性
远程 `Initialize` 返回两个共同构成会话绑定的值:
```text
thread_id + workspace_root
```
后续每次有状态的 Winx 调用都必须原样携带这两个值。在选择 PTY 或执行任何工具操作前,Winx 会验证线程亲和性、提交的规范化根目录与已初始化会话是否一致。绑定缺失或混用时,服务器返回结构化的 `needs_initialize`/`conflict` 工具结果,且不会触达 Shell 或文件系统。
此检查**不会**把工具目标限制在 `workspace_root` 内。该根目录只标识拥有终端、cwd、读取历史和编辑状态的项目上下文;路径权限由独立策略控制。例如设置 `WINX_ALLOW_PATHS=/` 后,只要当前模式允许,一致的会话仍可读取或编辑项目外的辅助路径。这样既支持真实的单体仓库和跨目录工作,也防止一个对话静默继承其他项目的终端。
切换到其他项目时,请使用新路径调用 `Initialize(type="first_call")`,之后改用新返回的绑定。远程 `user_asked_change_workspace` 会安全失败,避免把持久会话键原地重定向到其他项目。
## 会话亲和性 (Session Affinity)
### 工作区亲和性 (默认)
默认模式为:
```bash
--session-affinity workspace
```
针对远程 `Initialize(first_call)` 调用,Winx 根据以下维度推导逻辑会话:
```text
(认证主体, 规范化工作区绝对路径)
```
客户端首次调用传入的 `thread_id` 不作为持久化键值。模型生成的临时变体(如 `release_02333` 与 `release_0_2_333`)在指向同一主体和工作区时将解析至同一个 Guardian 守护进程。Winx 会返回稳定的外部 ID(如 `ws_project_`),后续工具调用需使用该返回的 ID。
特性与影响:
- 无状态重连会自动附加到已有会话,而不是重复创建 Guardian;
- 重复的首调请求会保留 PTY 进程、工作目录、输出日志及正在执行的命令;
- 不同主体之间严格隔离命名空间;
- **同一主体在同一工作区内的并行对话共享同一个 Shell 会话**与前台执行锁;第二个前台请求会快速返回结构化
`command_already_running`,不会进入隐藏队列或稍后意外执行,同时重新附加和其他项目的会话仍保持响应;
- 未指定工作区的调用按主体共享临时会话;
- 任务恢复根据保存的 Task ID 进行关联。
### 对话亲和性 (`conversation`)
使用:
```bash
--session-affinity conversation
```
适用于同一主体在同一仓库中进行多路并行对话且不希望共享 Shell 的场景。会话键由以下组合生成:
```text
(认证主体, 对话标识, 规范化工作区)
```
标识解析优先级:
1. `Mcp-Session-Id`(具备稳定会话传输时);
2. 网关注入的 `X-Winx-Conversation-Id`;
3. 首次调用提供的 `thread_id`;
4. 若均无则降级为工作区亲和。
### 线程亲和性 (`thread`)
使用:
```bash
--session-affinity thread
```
当客户端具备管理稳定唯一 ID 的能力,并显式负责会话的创建与生命周期清理时使用此模式。
## 附加或创建 (Attach-or-create)
协议 `1.3+` Guardian 支持 `FirstCall` 的自动附加或创建:
1. 逻辑会话不存在时新建 PTY;
2. 逻辑会话存在时返回其权威状态快照;
3. 适配层根据快照同步本地状态;
4. Guardian 保留原 PTY 进程、工作目录、安全模式、输出历史及运行中的命令。
需要替换 Shell 时仍应显式使用重置;模式切换也必须显式请求。远程切换项目时应创建新的 `FirstCall` 绑定,而不是修改原工作区身份。重复调用 `FirstCall` 不会隐式重置会话。
## 多主体认证配置
为每个客户端或自动化分配独立凭证:
```bash
mkdir -p ~/.config
install -m 600 /dev/null ~/.config/winx-chatgpt-token
install -m 600 /dev/null ~/.config/winx-automation-token
openssl rand -hex 32 > ~/.config/winx-chatgpt-token
openssl rand -hex 32 > ~/.config/winx-automation-token
```
创建 TOML 配置文件:
```toml
# ~/.config/winx-principals.toml
[[principals]]
name = "chatgpt"
token_file = "/home/alice/.config/winx-chatgpt-token"
tool_profile = "coding"
[[principals]]
name = "automation"
token_file = "/home/alice/.config/winx-automation-token"
tool_profile = "terminal"
[[principals]]
name = "ci"
token_env = "WINX_CI_MCP_TOKEN"
allowed_tools = ["Initialize", "BashCommand", "ReadFiles"]
```
启动服务:
```bash
chmod 600 ~/.config/winx-principals.toml
winx-code-agent serve --http \
--principal-config ~/.config/winx-principals.toml
```
主体规则要求:
- 名称仅包含 ASCII 字母、数字、`_` 和 `-`;
- 每个条目精确配置 `token_file` 或 `token_env` 之一;
- 主体名称和 Token 必须全局唯一;
- Token 文件必须为常规文件且权限为 `0600`;
- Token 长度至少为 32 字节;
- `tool_profile` 默认为 `full`;若提供 `allowed_tools`,它将完全替代该配置且不得为空;
- 工具名称区分大小写,未知名称会阻止服务启动。
### 工具目录配置
配置可为不需要全部能力的客户端缩小 `tools/list` Schema 载荷。Winx 在分发前也会执行同一策略。旧编辑别名仅在已有等价变更权限时保持可调用,使缓存会话继续工作而不扩大权限。
| 配置 | 公开的工具 |
| :--- | :--- |
| `full` | 七个工具:精简代码目录加 `ContextSave` 与 `ReadImage`(默认值) |
| `coding` | `Initialize`、`BashCommand`、`ReadFiles`、`CodeMap` 与 `EditFiles` |
| `read-only` | `Initialize`、`ReadFiles`、`ReadImage` 和 `CodeMap` |
| `terminal` | `Initialize` 和 `BashCommand` |
单主体服务可通过命令行选择配置:
```bash
winx-code-agent serve --http --token-file ~/.config/winx-http-token \
--tool-profile coding
```
也可以重复使用 `--allow-tool` 构造精确目录;显式名称会替代 `--tool-profile`:
```bash
winx-code-agent serve --http --token-file ~/.config/winx-http-token \
--allow-tool Initialize --allow-tool BashCommand --allow-tool ReadFiles
```
工具目录策略不是 Shell 沙箱。任何包含 `BashCommand` 的配置仍拥有已初始化 Winx 模式和操作系统用户所允许的命令能力。
## LLM 编排规范 (Orchestration Contract)
MCP 握手协议定义了确定性的调用序列约束:仅初始化一次、保留返回的 `thread_id`、优先使用 `CodeMap` 获取概览、使用 `ReadFiles` 批量读取、编辑前必须先读取、用 `&&` 合并相关的快速失败检查,并且绝不原样重复已被拒绝的调用。`Initialize` 还会返回一个受限的 `/.winx/tmp/session-…/` 目录,供确有独立用途的派生辅助文件使用。这些文件不是权威源,必须保留原始源码路径和行号来源,并复用稳定文件名;不得仅为了调用 `CodeMap` 而把源码或命令输出转换为载体。辅助文件映射只接受一个已存在的文件,单次响应上限为 12 KiB,每个活动会话最多映射 24 个不同文件、调用 64 次;规范源码映射不受此聚合配额限制。临时存储每个会话限制为 64 MiB / 128 个文件,每个工作区限制为 256 MiB,闲置会话会在 24 小时后清理。活动会话达到 96 个文件或 48 MiB 时,Winx 只会按从旧到新的顺序回收已连续闲置满 24 小时的辅助文件,目标恢复到 64 个文件 / 32 MiB;它不会跟随符号链接,也不会自动删除新鲜文件。禁止把内容编码进文件名或目录名,也禁止用 `.winx-*`/`.winx_tmp` 文件污染项目根目录。每个前台或后台 PTY 都会把这个精确目录导出为 `WINX_TEMP_DIR`;若 Shell 写入的静态目标绕过该目录,调用会以 `temporary_artifact_policy` 被拒绝。Winx 还会在每次 Bash 操作后审计实际用量,包括静态分析无法预测的动态写入;结果会报告字节数、文件数和旧文件回收计数。若清理后仍超出配额,普通命令将被阻止,直到代理显式检查并删除剩余的过时辅助文件。只有在 `BashCommand` 返回类型明确的 Shell 运行时故障后才应请求 `reset_shell`;五分钟内若无新的故障证据而重复重置,Winx 会返回 `reset_skipped_healthy` 并保留现有 PTY。若 `Initialize` 返回 `initialize_workspace_already_bound` 或 `workspace_change_requires_new_session`,则当前对话不得重试该调用:访问策略允许的绝对路径时继续使用现有绑定,真正切换项目时应开启新的对话。`EditFiles` 可通过 `verify_command` 在同一次调用中执行有限的编辑后检查,从而节省一次网络和模型往返。
`ReadFiles` 批次中的文件会在受限并行池中处理(`WINX_READ_PARALLELISM`,默认 `4`,最大 `32`),但响应内容和读取保护范围始终严格按请求顺序发布。
每个工具均声明了 `outputSchema` 并返回统一的 `structuredContent` 封装:
```json
{
"status": "needs_read",
"tool": "EditFiles",
"message": "EditFiles failed: ...",
"errorCode": "read_required",
"retryable": true,
"retrySameCall": false,
"nextAction": {
"tool": "ReadFiles",
"instruction": "Perform every required read before retrying the edit.",
"arguments": {
"file_paths": ["/workspace/README.md:231-301"],
"thread_id": "ws_project_hash"
}
},
"requiredReads": [
{ "path": "/workspace/README.md", "ranges": ["231-301"] }
]
}
```
可恢复的执行失败在 HTTP/JSON-RPC 层面返回成功,而在 MCP 层面设置 `isError: true`,并附带明确的下一步修复动作(`nextAction`)。
`BashCommand.wait_policy=until_complete` 仅适用于有限的前台命令。若用于后台、状态、输入、屏幕或等待操作,
Winx 会返回可恢复结果(`errorCode: wait_policy_incompatible_with_action`),并在
`nextAction.arguments` 中把策略修正为 `return_early`。
Task 取消同样绑定到精确执行代次。若取消与启动并发,Winx 会等待启动发布准确执行令牌,或确认没有进程
启动。若有界握手无法完成,系统会先终止受影响的 Shell 再确认取消,因此过期中断不会命中下一条命令。
`ReadImage` 会根据内容验证 JPEG、PNG、GIF 或 WebP,而不是信任文件扩展名。源文件上限为 50 MiB,并另设
解码尺寸和内存分配限制;交付内容限制为 2 MiB、长边 2560 px,较大的输入会自动缩放和重新编码。每个会话
都有受限的内容指纹缓存,未变化的重复读取会返回紧凑的结构化引用;只有确实需要再次传输字节时才使用
`force=true`。
`EditFiles` 是强类型变更引擎唯一的公共入口。它的每文件显式模式为 `replace`、`search_replace`、`line_patch`
和 `undo`;一次编辑调用可接收 1 到 100 个唯一目标,并在写入前验证整个批次。旧名称保留为隐藏兼容
别名,不能扩大等价 `EditFiles` 模式已授予的权限。
对于现有文件,默认的确定性流程是先调用 `ReadFiles`,再使用绑定该修订版本的 `line_patch`。
`search_replace` 仅保留给有意使用的精确文本锚定。发生 SEARCH 冲突后,结构化恢复要求执行其指定的
`ReadFiles` 操作,并将修正后的重试切换为 `line_patch`;普通源码修改不应退回到 shell、`sed` 或 Python。
`EditFiles` 接受可选的 `verify_command` 与 `verify_wait_for_seconds`(默认 `15`,最大
`60`)。验证仅在提交成功后运行,并以前台命令形式遵循与 `BashCommand` 相同的模式白名单。退出码为零时组合结果成功;非零退出码保留 `isError: false`,并返回 `status: completed_with_issues`、`errorCode: verification_failed` 和
`data.edit_applied: true`。回执绑定的 `BashCommand` 下一步动作只会在修复后重新执行检查,不会重复编辑。若检查仍在运行,结果会提供标准的 `BashCommand` `status_check` 动作。验证同时需要 `EditFiles` 与 `BashCommand` 权限。
已提交的编辑会保留 30 分钟的紧凑持久化变更回执。完全相同的调用采用 single-flight,并且仅在目标哈希仍匹配时无副作用重放;若目标后来发生变化,Winx 会返回 `mutation_postcondition_changed`,不会覆盖新状态。同一会话和目标连续三次 SEARCH 冲突会升级为 `recovery_exhausted`,停止自动重试并要求代理改变策略。
## Guardian 生命周期管理
`winxd` 统一管理连接到控制套接字的所有 Guardian:
- **权威活动时钟:** 协议 1.3+ Guardian 记录创建时间、终端实际活动时间、最近指令时间以及是否曾执行过指令。
- **分层 TTL 回收机制:**
- `WINX_UNUSED_SESSION_IDLE_TTL_SECS=1800`(30 分钟,从未执行过命令的会话);
- `WINX_SESSION_IDLE_TTL_SECS=86400`(24 小时,已执行过命令的会话)。
- 正在执行前台或后台任务的会话不会被自动回收。
- **配额控制与释放:** 配额满时,`winxd` 会优先回收已无活动且从未执行过命令的会话。
## 运维控制命令
```bash
# 查看会话列表
winx-code-agent list
# 实时跟踪会话终端输出
winx-code-agent attach --follow
# 触发分层默认清理
winx-code-agent prune
# 强制清理所有空闲会话 (保留活跃命令)
winx-code-agent prune --idle-seconds 0
# 明确销毁特定会话或全部会话
winx-code-agent kill
winx-code-agent kill --all
# 热重启控制平面守护进程 (保持 Guardian 和 PTY 存活)
winx-code-agent restart-daemon
# 输出脱敏后的系统与环境诊断报告
winx-code-agent doctor
```
## 遥测与使用日志
配置异步非阻塞的 `winx::usage` JSONL 写入路径:
```bash
WINX_USAGE_LOG="$HOME/.local/state/winx/usage.jsonl" \
WINX_USAGE_LOG_ROTATION=daily \
WINX_USAGE_LOG_KEEP_DAYS=7 \
winx-code-agent serve --http --token-file ~/.config/winx-http-token
```
日志文件权限严格限定为 `0600`。命令文本、文件内容、Token 及原始对话标识绝不会写入遥测日志中;日志仅记录时延、结果状态、响应大小、批次项数、worker 上限和协议元数据。可通过 `request_id` 关联 `tool_call` 与 `http_request`,从而在不暴露负载的情况下区分工具耗时和传输开销。
可使用以下命令快速查看各工具的延迟分布:
```bash
jq -s '
def pct($p): sort | .[((length - 1) * $p | floor)];
[.[] | select(.fields.event == "tool_call") | .fields]
| group_by(.tool)
| map(. as $calls | {
tool: $calls[0].tool,
calls: ($calls | length),
p50_ms: ([$calls[].duration_ms] | pct(0.50)),
p95_ms: ([$calls[].duration_ms] | pct(0.95))
})
' ~/.local/state/winx/usage.jsonl*
```
## 网络暴露建议
### 推荐方案
保持监听在本地回环(`127.0.0.1:8000`),并在前方配合:
- 私有 VPN(如 WireGuard、Tailscale);
- 仅出站(Outbound-only)MCP 安全隧道;
- 经过认证的 HTTPS 反向代理。
当代理转发公网 `Host` 头部时,通过 `--allowed-host` 进行白名单授权:
```bash
winx-code-agent serve --http \
--token-file ~/.config/winx-http-token \
--allowed-host mcp.example.com
```
### 直接非回环监听
若经评估后确实需要在局域网接口监听:
```bash
winx-code-agent serve --http \
--bind 192.168.1.20:8000 \
--allow-non-loopback \
--token-file ~/.config/winx-http-token
```
## 资源限制与响应码
| 异常条件 | HTTP 响应 | 备注 |
| :--- | :--- | :--- |
| Token 缺失或无效 | `401 Unauthorized` | 延迟 100ms 响应 |
| 单 IP 请求频次超限 | `429 Too Many Requests` | 包含 `Retry-After: 1` 头部 |
| 全局并发配额耗尽 | `503 Service Unavailable` | 包含 `Retry-After: 1` 头部 |
| 请求执行超过 120 秒 | `408 Request Timeout` | 中止该次请求 |
| 请求体大于 64 MiB | `413 Payload Too Large` | 在 MCP 分发前直接拒绝 |
## CLI 选项速查
| 选项 | 说明 |
| :--- | :--- |
| `serve --http` | 启用 Streamable HTTP 模式 |
| `--bind ` | 监听地址(默认 `127.0.0.1:8000`) |
| `--token-file ` | 单主体 Token 文件路径 |
| `--principal-config ` | 多主体 TOML 配置文件路径 |
| `--tool-profile ` | 单主体目录:`full`、`coding`、`read-only` 或 `terminal` |
| `--allow-tool ` | 构造精确的单主体目录;可重复使用 |
| `--session-affinity ` | 亲和性模式:`workspace`、`conversation` 或 `thread` |
| `--allow-weak-token` | 允许少于 32 字节的弱 Token(仅限测试) |
| `--allow-non-loopback` | 允许绑定非回环网络地址 |
| `--allowed-host ` | 显式允许的 Host 域名白名单 |
| `--allow-query-token` | 允许通过 URL 参数 `?token=...` 传递凭证 |
| `--max-concurrency ` | 全局最大并发请求数(默认 32) |
| `--requests-per-minute ` | 单 IP 每分钟最大请求数(默认 120) |
## 安全边界说明
经过身份验证的主体拥有以启动 Winx 的操作系统用户身份执行命令和读写文件的权限。
- `wcgw` 模式拥有完全访问权限;
- `architect` 模式限制为只读探索;
- `code_writer` 模式限制为命令和通配符白名单;
- 敏感信息脱敏默认始终开启;
- `WINX_SANDBOX=1` 可在 Linux 上启用内核级 Landlock 文件沙箱。
详情请参阅 [SECURITY.md](../SECURITY.md)。