exec-vps-mcp
by yding-git
README.md
# exec-vps-mcp
> 用一个很薄的 Python MCP 服务,把 VPS 的任意 root shell 暴露给 Codex、Claude Code 或其他 MCP 客户端。
[给 AI / Agent 的部署与维护说明 → `AGENTS.md`](AGENTS.md)
[脱敏后的实际使用示例 → `docs/real-world-example.md`](docs/real-world-example.md)
[故障排查手册 → `docs/troubleshooting.md`](docs/troubleshooting.md)
## 先说结论
这个项目最核心的能力只有一个:
```text
exec_vps(command: string, timeout?: number) →
{
exit_code: integer | null,
stdout: string,
stderr: string,
timed_out: boolean,
truncated: boolean
}
```
它收到一段 shell 命令,在 VPS 上以 root 身份执行,再把退出码、stdout、stderr、超时和截断状态作为结构化结果返回给 AI。
```text
Codex / Claude Code / Agent
↓ MCP Streamable HTTP
exec-vps-mcp
↓ subprocess.Popen(..., shell=True)
VPS root shell
```
这很方便,也非常危险。连接到这个 MCP 的 AI 理论上可以读取服务器文件、修改服务、操作 Docker、防火墙和数据库,也可以执行删除或关机命令。只有在你完全控制客户端、网络入口和服务器时才应使用。
本仓库不会把 `8798` 直接开放到公网。默认只监听 `127.0.0.1`,推荐通过 SSH tunnel 使用;同机 Docker 场景可以改为私有 bridge 网络,但仍应由防火墙阻止公网访问。
---
## 1. 它是怎么做出来的
服务端使用官方 MCP Python SDK v2 的 `MCPServer`。返回类型使用 `TypedDict`,SDK 会据此生成并校验结构化 output schema:
```python
class ExecResult(TypedDict):
exit_code: int | None
stdout: str
stderr: str
timed_out: bool
truncated: bool
@mcp.tool()
def exec_vps(command: str, timeout: int = 30) -> ExecResult:
with tempfile.TemporaryFile() as stdout_file, tempfile.TemporaryFile() as stderr_file:
process = subprocess.Popen(
command,
shell=True,
stdout=stdout_file,
stderr=stderr_file,
cwd="/root",
)
process.wait(timeout=min(timeout, 120))
return {"exit_code": process.returncode, ...}
```
`@mcp.tool()` 根据 Python 类型和 docstring 自动生成工具定义;`mcp.run(transport="streamable-http")` 通过 HTTP 提供 MCP endpoint,默认地址为:
```text
http://127.0.0.1:8798/mcp
```
stdout 和 stderr 分别最多返回 1 MiB;任一路超过上限时只返回前 1 MiB,并把 `truncated` 设为 `true`。子进程输出先写入临时文件,避免把无限输出全部保存在 Python 内存或 MCP response 中。
本项目使用 MCP Python SDK v2 稳定线并约束为 `mcp>=2,<3`,使用 `MCPServer` API;HTTP 的 `host` / `port` 参数传给 `run()`。
官方资料:
- [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)
- [MCP Python SDK v2 migration guide](https://py.sdk.modelcontextprotocol.io/migration/)
- [Codex MCP 配置](https://developers.openai.com/codex/mcp)
---
## 2. 你需要什么
```text
一台 Ubuntu / Debian VPS
root 或等价 sudo 权限
Python 3.10+
本地可用的 SSH 公钥登录
支持 Streamable HTTP 的 MCP 客户端
```
不需要 OpenAI API Key。MCP 服务本身不调用模型;它只给已有的 Codex、Claude Code 或其他 Agent 提供工具。
---
## 3. 先由人完成一次 SSH 引导
推荐先让人类完成第一次登录和密钥验证:
```text
人类使用 VPS 初始密码 / 厂商控制台登录
↓
本地生成 SSH 密钥对
↓
只把公钥放入服务器 authorized_keys
↓
确认密钥登录成功
↓
再让 AI / Agent 接手后续部署
```
生成密钥:
```bash
ssh-keygen -t ed25519
```
Linux / macOS:
```bash
ssh-copy-id root@YOUR_SERVER_IP
```
Windows PowerShell:
```powershell
Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub | ssh root@YOUR_SERVER_IP "umask 077; mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys"
```
最后验证:
```bash
ssh root@YOUR_SERVER_IP
```
不要把 SSH 私钥、root 密码或云厂商凭据粘贴到聊天、Issue、日志或公开仓库。在密钥登录验证成功前,不要关闭密码登录。
---
## 4. 安装服务端
以下命令在 VPS 上以 root 执行:
```bash
apt-get update
apt-get install -y git python3 python3-venv
git clone https://github.com/yding-git/exec-vps-mcp.git /opt/exec-vps-mcp
cd /opt/exec-vps-mcp
python3 -m venv .venv
.venv/bin/pip install --upgrade pip
.venv/bin/pip install .
```
先运行测试:
```bash
.venv/bin/python -m unittest -v
```
安装 systemd 服务:
```bash
install -m 0644 deploy/exec-vps-mcp.service /etc/systemd/system/exec-vps-mcp.service
systemctl daemon-reload
systemctl enable --now exec-vps-mcp.service
```
检查状态:
```bash
systemctl status exec-vps-mcp.service --no-pager
ss -lntp 'sport = :8798'
```
默认应该只看到 `127.0.0.1:8798`,而不是公网地址。
---
## 5. 从本地通过 SSH tunnel 连接
在运行 Codex 的本地电脑上保持下面的 SSH 连接:
```bash
ssh -N -L 8798:127.0.0.1:8798 root@YOUR_SERVER_IP
```
含义是:
```text
本地 127.0.0.1:8798
↓ 加密 SSH tunnel
VPS 127.0.0.1:8798
↓
exec-vps-mcp
```
只要这个 SSH 会话保持运行,本地 MCP 客户端就可以连接:
```text
http://127.0.0.1:8798/mcp
```
如果本地 `8798` 已被占用,可以改成本地其他端口,例如:
```bash
ssh -N -L 18798:127.0.0.1:8798 root@YOUR_SERVER_IP
```
然后把客户端 URL 攡成 `http://127.0.0.1:18798/mcp`。
---
## 6. 连接 Agent 客户端
这个服务使用标准 Streamable HTTP MCP。Codex、Claude Code 和 OpenCode 都能原生连接同一个 endpoint;Pi 核心不内置 MCP,需要经过审查的扩展。
协议可以通用,客户端配置文件不能强行通用:Codex 使用 TOML,Claude Code 使用 CLI 或 `.mcp.json`,OpenCode 使用 `opencode.json`。完整换算方法、可复制配置和 Pi 的人工操作说明见:
**[Codex / Claude Code / OpenCode / Pi 客户端配置教程 → `docs/client-setup.md`](docs/client-setup.md)**
### Codex 快速配置
把 [`examples/codex-config.toml`](examples/codex-config.toml) 中的配置加入 `~/.codex/config.toml`:
```toml
[mcp_servers.exec-vps]
url = "http://127.0.0.1:8798/mcp"
enabled = true
required = true
enabled_tools = ["exec_vps"]
tool_timeout_sec = 130
```
然后重新启动 Codex,执行:
```bash
codex mcp list
```
也可以在 Codex 中使用 `/mcp` 查看连接状态。
其他客户端不要照抄这段 TOML;endpoint 仍然是 `http://127.0.0.1:8798/mcp`,只需换成对应客户端的配置外壳。
---
## 7. 测试工具
先只执行无副作用命令:
```text
请使用 exec_vps 执行:id && pwd && uptime
```
预期结构化结果中的 `exit_code` 为 `0`,`timed_out` 与 `truncated` 为 `false`,`stdout` 应包含:
```text
uid=0(root)
/root
...
```
然后可以尝试只读运维任务:
```text
检查服务器磁盘、内存、负载和失败的 systemd 服务,不要修改任何内容。
```
确认链路正常以后,再决定是否让 Agent 修改服务或配置。
---
## 8. 给 AI 使用时怎么说
普通任务可以直接说目标:
```text
检查 nginx 为什么启动失败,先只读诊断并告诉我原因,不要修改配置。
```
如果允许修改,明确范围:
```text
修复 nginx 配置,但不要修改防火墙、Docker、SSH 和其他服务。修改前备份原文件,完成后运行 nginx -t。
```
涉及删除、覆盖、迁移、数据库、凭据、防火墙和 SSH 时,最好要求 Agent 在执行前停下来说明目标和回滚方法。
仓库中的 [`AGENTS.md`](AGENTS.md) 已经把这些协作规则写给 AI。把仓库交给 Codex、Claude Code 或其他 Agent 时,让它先读该文件。
---
## 9. 同机 Docker 私网接入
如果 MCP 客户端也运行在同一台 VPS 的 Docker 容器中,可以把 systemd 单元改为:
```ini
Environment=MCP_HOST=0.0.0.0
```
客户端连接宿主 Docker bridge gateway,例如:
```text
http://YOUR_BRIDGE_GATEWAY:8798/mcp
```
但必须同时满足:
1. 使用专门的 Docker bridge 网络;
2. 防火墙只允许该 bridge / 子网访问 TCP `8798`;
3. 云厂商安全组和公网防火墙不开放 `8798`;
4. 修改防火墙前先确认 SSH 恢复通道;
5. 从公网实际探测确认 `8798` 不可达。
bridge 名称和子网因机器而异,不要照抄其他机器的地址。先用 `docker network inspect` 查看真实网络,再生成精确规则。
---
## 10. 我实际在用的两条链路
这个仓库来自一套真实运行的个人系统,而不是只在本地跑过的示例。公开文档只保留两条经过脱敏的技术链路:
```text
云端:Telegram / gateway → Codex → vps-admin → Docker 私网 → root MCP
本机:Codex Desktop → 云服务器插件 → 远程 MCP 入口 → exec_vps
```
两条链路最终调用的是同一种能力:服务器上的任意 root shell。区别在于 MCP 客户端在哪里运行,以及怎样抵达服务器。
完整的实际案例、设计取舍、权限边界和验收方法见 [`docs/real-world-example.md`](docs/real-world-example.md)。案例中的服务名、网络名和 MCP key 均为通用占位名。
---
## 11. 日常维护
查看日志:
```bash
journalctl -u exec-vps-mcp.service -n 100 --no-pager
```
更新:
```bash
cd /opt/exec-vps-mcp
git pull --ff-only
.venv/bin/pip install .
.venv/bin/python -m unittest -v
systemctl restart exec-vps-mcp.service
systemctl status exec-vps-mcp.service --no-pager
```
停止并禁用:
```bash
systemctl disable --now exec-vps-mcp.service
```
删除前先确认 `/opt/exec-vps-mcp` 里没有你自行加入的配置、备份或数据。本教程不提供自动删除命令,避免 Agent 或人类误删路径。
---
## 12. 常见故障
遇到连接失败、工具不出现、timeout、systemd 启动失败、SSH tunnel 端口冲突或 Docker 私网不可达时,按 [`docs/troubleshooting.md`](docs/troubleshooting.md) 从监听、网络、MCP 初始化、工具调用四层逐步检查。
不要一遇到连接问题就开放公网端口或关闭防火墙。先确认问题发生在哪一层。
---
## 13. 安全边界
公开仓库不要提交或粘贴:
```text
SSH 私钥
root / VPS 密码
云厂商 API Key
Bot Token
OAuth Token
Cookie
生产 MCP URL 中的凭据
真实服务器 IP(如果你不希望公开)
包含秘密的 systemd EnvironmentFile
```
还要知道:
- `shell=True` 会让 shell 解释整段命令;这正是任意 shell 能力的来源。
- systemd 使用 `User=root`,所以工具调用拥有完整宿主权限。
- `timeout` 只限制 MCP 调用等待多久,并在超时后终止直接启动的 shell;它不是 sandbox、资源限制或完整的 process-tree containment。fork、后台运行或 daemonize 的后代进程可能继续存在。长期任务应交给 systemd、tmux 或容器运行时等 supervisor。
- stdout 和 stderr 的 1 MiB 上限只限制返回内容与 Python 内存,不限制命令自身的 CPU、磁盘、网络或进程资源消耗。
- MCP 服务自身没有认证;安全依赖回环地址、SSH tunnel 或严格私网边界。
- Agent 的文字规则不是操作系统权限隔离,也不能替代备份和恢复通道。
- 不要把这个 endpoint 直接暴露到公网。
### 间接提示注入也是 root 风险
即使只有可信用户能访问 Agent,也仍可能出现下面的链路:
```text
可信用户
→ Agent 阅读网页 / Issue / repo / email / log / 文档
→ 内容里夹带恶意指令
→ Agent 被诱导调用 exec_vps
→ root
```
认证只能证明“谁能访问 Agent”,不能证明“Agent 读到的内容都是可信指令”。网页、仓库、Issue、日志、邮件、文档和工具输出中的指令都必须视为 untrusted data;只有人类用户明确授权的任务范围可以触发 root 操作。
---
## 14. 项目结构
```text
exec-vps-mcp/
├── README.md
├── AGENTS.md
├── LICENSE
├── pyproject.toml
├── server.py
├── test_server.py
├── deploy/
│ └── exec-vps-mcp.service
├── examples/
│ ├── codex-config.toml
│ ├── claude-code.mcp.json
│ └── opencode.json
└── docs/
├── client-setup.md
├── real-world-example.md
└── troubleshooting.md
```
## Disclaimer
这个项目用于你自己控制的服务器、个人运维、学习和开发测试。任意 root shell 可能造成数据丢失、服务中断、凭据泄露或服务器被接管。
请遵守所在地区法律法规、云服务器厂商政策和相关服务条款。使用者需要自行承担部署和命令执行风险。
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues