MSAW
<div align="center">
<img src="./docs/assets/msaw-banner.svg" width="920" alt="MSAW - Multi SSH Agent Workspace">
</div>
<div align="center">
<h1>MSAW</h1>
<p><strong>面向 AI Agent 的本地多 SSH 控制面与共同工作区。</strong></p>
<p><a href="./README.md">简体中文</a> · <a href="./README.en.md">English</a></p>
</div>
MSAW 让 Codex 等 Agent 通过 MCP 同时操作多台 SSH 主机,并用本地 `.msaw/shared` 在不同 VPS 之间流转文件。主机库存、凭据、隔离初始化和全局权限由本地用户控制,不进入 Agent 的能力范围。
## 安全模型
| 边界 | 默认行为 |
| --- | --- |
| Linux 日常身份 | 可一键创建 `msaw-agent` 系统用户,无密码、无 sudo,只拥有指定工作区 |
| 管理员身份 | root/admin 凭据仅用于隔离初始化和用户授予的全局模式 |
| 全局模式 | 只能通过 Web UI/CLI 开启;开启后永久有效,直到用户手动关闭 |
| 主机库存 | MCP 默认只读;添加和删除只能在 Web UI/CLI 完成 |
| 凭据存储 | Windows DPAPI、macOS Keychain、Linux Secret Service;不可用时回退 RSA-OAEP 加密文件 |
| 私钥口令 | 通过 `SSH_ASKPASS` 传给 OpenSSH,不放进进程 argv |
| Web UI | 默认只绑定 loopback;写操作校验 HttpOnly 会话 cookie 和同源 Origin |
| 远程 Web | 必须同时使用 `--allow-remote` 和 Bearer Token |
| 文件传输 | 自动探测 rsync,支持 `--partial`;不可用时回退 SCP |
| MCP 协议 | 使用官方 Python MCP SDK 处理 stdio、schema、协商和取消 |
隔离用户依赖 Linux 文件权限,不是 chroot 或容器。它会阻止 Agent 读取 `/etc/shadow`、修改系统文件或使用 sudo,但仍能读取系统本来允许普通用户读取的内容。
## 工作方式
```mermaid
flowchart LR
Agent["Codex / Agent"] -->|Official MCP SDK + stdio| MCP["MSAW MCP"]
UI["Web UI / CLI"] --> Control["Local control plane"]
MCP --> Control
Control -->|OpenSSH| A["VPS A"]
Control -->|OpenSSH| B["VPS B"]
A -->|rsync / SCP| Shared[".msaw/shared"]
Shared -->|rsync / SCP| B
Vault["DPAPI / Keychain / Secret Service"] --> Control
```
## 安装
需要 Python 3.10+、OpenSSH 客户端和 Git。rsync 可选。
```powershell
git clone https://github.com/LusiyAvA/msaw.git
cd msaw
python -m venv .venv
.\.venv\Scripts\python -m pip install -e .
.\.venv\Scripts\msaw init
.\.venv\Scripts\msaw ensure
```
打开 `http://127.0.0.1:8765`。Web UI 会实时显示主机、共享文件和操作状态。
Linux/macOS:
```bash
python3 -m venv .venv
.venv/bin/python -m pip install -e .
.venv/bin/msaw init
.venv/bin/msaw ensure
```
## 添加与隔离主机
Web UI 支持系统 OpenSSH、保存私钥和保存密码三种认证方式。点击“保存并测试连接”后会明确显示成功或失败;首次连接 Linux 管理员账号时,可保持“初始化 `msaw-agent` 隔离用户”选中。
CLI 使用系统 SSH 配置:
```powershell
msaw host add prod-a --host example.com --user root --auth-method system
msaw test prod-a
msaw host isolate prod-a
```
保存私钥:
```powershell
msaw host add prod-a --host example.com --user root `
--auth-method key --private-key-file .\id_ed25519
```
命令行密码和口令可能进入 Shell 历史,保存凭据时优先使用 Web UI。
隔离初始化完成后:
- 日常 workspace 操作使用 `msaw-agent` 专用 SSH key。
- 原 root/admin 认证被保留为管理员凭据。
- 新身份会再次执行 SSH 连接测试,成功后才切换主机配置。
- 初始化失败不会切换日常身份,也不会保留无效凭据引用。
- 已存在的同名 Linux 用户不会被复用;请在 Web UI 或 `--user` 中选择新的专用用户名。
- 工作区必须位于隔离用户家目录、`/srv`、`/opt/msaw` 或 `/var/lib/msaw` 下的专用目录。
- 初始化只修改工作区目录本身,不递归修改其中已有文件的所有权。
- 管理员可以是 root,也可以是支持 `sudo -n` 的普通云主机账号。
## 全局权限
全局模式开关位于每台主机卡片上。普通开启永久有效,直到用户手动关闭:
```powershell
msaw host mode prod-a --enable
msaw host mode prod-a --disable
```
临时授权必须显式选择,到期后自动关闭:
```powershell
msaw host mode prod-a --enable --temporary --lease-minutes 30
```
MCP schema 不包含任何修改全局模式的字段或工具。
## 共同工作区
```powershell
msaw pull prod-a releases/app.tar.gz --shared-path releases/app.tar.gz
msaw push prod-b releases/app.tar.gz --remote-path releases/app.tar.gz
msaw transfer prod-a prod-b releases/app.tar.gz
msaw transfer prod-a prod-b releases/app.tar.gz --keep-transfer-copy
```
`transfer` 仍通过本地共享区中转。传输成功后默认删除临时副本;失败时会保留现场,显式使用 `--keep-transfer-copy` 可保留成功传输的副本。双方支持 rsync 时自动使用增量和断点续传,否则自动使用 SCP。
## 接入 Codex
`config.toml` 建议直接指向项目虚拟环境,避免 Codex 启动时依赖已运行的 Web 服务:
```toml
[mcp_servers.msaw]
command = "<path-to-msaw>/.venv/Scripts/python.exe"
args = ["-m", "multi_ssh_workspace.cli", "mcp"]
cwd = "<path-to-msaw>"
startup_timeout_sec = 30
tool_timeout_sec = 180
required = true
[mcp_servers.msaw.env]
MSAW_HOME = "<path-to-msaw>"
PYTHONPATH = "<path-to-msaw>/src"
PYTHONDONTWRITEBYTECODE = "1"
```
Linux/macOS 将 `command` 改为 `<path-to-msaw>/.venv/bin/python`。
MCP 启动时会自动确认并按需启动本地控制服务。Agent 应按以下顺序工作:
```text
hosts_list -> host_test -> host_exec / shared transfer
```
## MCP 工具
默认暴露 9 个工具:
| 分组 | 工具 |
| --- | --- |
| 主机只读 | `hosts_list`, `host_test` |
| 多机执行 | `host_exec` |
| 共同工作区 | `shared_list`, `shared_read`, `shared_write` |
| 文件流转 | `host_pull_to_shared`, `host_push_from_shared` |
| 结果读取 | `result_read` |
`host_exec` 使用统一的多目标接口:
```json
{
"targets": ["prod-a", "prod-b"],
"command": "uname -a",
"timeout": 60,
"concurrency": 4
}
```
单主机也使用长度为 1 的 `targets`,一次最多 16 台。默认每台最多返回最有价值输出流的 10 行、1500 字符,并且整个响应不超过 8000 字符和 120 行;失败主机优先于成功主机保留详情。完整细节保存在当前 MCP 会话。
只有本地用户开启库存修改后,MCP 才额外暴露 `host_upsert` 和 `host_remove`:
```powershell
msaw settings inventory --allow
msaw settings inventory --deny
```
即使启用,这两个工具也不能接收凭据、修改认证方式或开启全局模式。修改设置后需要让 MCP 客户端重新连接。
## 长输出与取消
普通长结果会返回首部、尾部和 `output_ref`。批量执行受每台和全局双层预算约束,未展示内容进入结果缓存。
```json
{
"output_ref": "<output-ref>",
"mode": "incremental",
"max_chars": 8000
}
```
- `incremental` 只返回此前从未交付给 Agent 的内容。
- `full` 返回带明确标识的完整结果,允许重复先前内容。
- 常规工具也可传 `full_output: true` 直接请求全量。
- 缓存仅存在于当前 MCP 进程,最多保留最近 32 条结果。
SSH、SCP 和 rsync 都通过异步子进程执行。MCP cancel 会立即终止同一请求下的全部进程;2 秒内未退出则强制结束。
## Web 访问
本地模式:
```powershell
msaw serve --host 127.0.0.1 --port 8765
```
MSAW 拒绝直接监听非 loopback 地址。远程访问必须通过本机 HTTPS 反向代理,并从环境变量或权限受限的文件提供 Bearer Token:
```powershell
$env:MSAW_BEARER_TOKEN = "<token>"
msaw serve --host 127.0.0.1 --port 8765 --allow-remote
msaw serve --host 127.0.0.1 --port 8765 --allow-remote --bearer-token-file .\.msaw\web-token
```
反向代理必须传递 `X-Forwarded-Proto: https`。远程 Web UI 使用 Bearer Token 换取 `Secure`、SameSite、HttpOnly 会话 cookie;Token 不进入 URL 或进程 argv。不要把 8765 端口直接暴露到公网。
## 运行数据
```text
.msaw/
credentials.json
hosts.json
settings.json
*.lock
known_hosts
keystore/
logs/
runtime/
shared/
```
不要提交 `.msaw/`、真实主机清单、凭据、日志或共享文件。RSA keystore 只在系统凭据服务不可用时作为兼容 fallback。
## 开发
```powershell
python scripts/codegraph.py
python -m unittest discover -s tests -p "test_*.py" -v
```
核心模块:
```text
src/multi_ssh_workspace/
config.py
credentials.py
execution.py
isolation.py
mcp_stdio.py
settings.py
ssh_client.py
web.py
websocket.py
workspace.py
```
## License
[MIT](./LICENSE)
TDQS
Scored across 9 tools
Each tool targets a clearly distinct operation: file reads/writes, host listing, command execution, transfer directions, and cached result retrieval. Overlap is minimal even between similar-sounding tools like shared_read and result_read because their descriptions make the resource type explicit.
Names mostly follow a predictable <scope>_<verb> pattern: shared_read, shared_write, host_exec, result_read. The pluralized hosts_list and longer directional names like host_pull_to_shared and host_push_from_shared are minor deviations but still readable and consistent in style.
Nine tools is well-scoped for a multi-host workspace management server. Each tool covers a necessary operation without redundancy, and the set is neither too thin nor bloated.
The core workflows are covered: shared workspace read/write/list, host discovery/test/execution, bidirectional transfer, and cached result retrieval. Minor gaps exist, such as no explicit shared-delete or host-side file listing tool, but these are workaroundable via host_exec and overwrite semantics.