Skip to main content
Glama
losophy

skynet-mcp

by losophy
README.md
# skynet-mcp

把 skynet 游戏服务器框架的 [DebugConsole](https://github.com/cloudwu/skynet/wiki/DebugConsole)
调试命令封装成 MCP(Model Context Protocol)工具,让 coding agent(如 opencode)
通过**自然语言**直接驱动 skynet 调试控制台——不用再记 `list` / `mem` / `call` / `inject` 这些命令。

```
用户: "看看现在 skynet 里跑了哪些服务"
AI:   → 调用 list 工具
用户: "帮我把 watchdog 服务的卡住的任务栈打出来"
AI:   → 调用 task 工具(地址来自 list 输出)
```

## 功能

- **32 个 MCP 工具**,覆盖 debug console 全部命令(见下方工具清单)
- `raw_command` 兜底工具:原样透传任意命令行,兼容未来新增的命令
- 2 个资源:`skynet://services`(实时服务列表)、`skynet://help`(命令帮助)
- 1 个提示词模板:`skynet_troubleshoot`(按"只读 → 危险"顺序生成排查步骤)
- 有副作用的命令(kill/exit/inject/call/signal/...)**绝不自动重试**;只读命令传输失败时自动重试一次

## 通信原理

skynet debug console 支持 HTTP 通道(`POST / HTTP/1.0`,body 即命令行,响应为裸文本 +
`<CMD OK>` / `<CMD Error>` 标记后断开)。本项目用标准库 socket 手工构造该请求:

- 为什么不用 http.client/requests:skynet 的响应**没有 HTTP 状态行**(curl 需 `--http0.9`),
  标准 HTTP 客户端无法解析
- 为什么用 POST 而不是 GET:POST 的 body 被服务端 `docmd(body)` 原样当命令行执行,
  `call 3 "foo", 1, "bar"` / `inject 3 /home/x/patch.lua` 里的引号、逗号、斜杠路径都不会被 URL 编码破坏

## 项目结构

```
skynet-mcp/
├── skynet_mcp/
│   ├── main.py          # FastMCP 入口(工具注册 + 资源 + 提示词)
│   ├── config.py        # host/port/timeout(env + 命令行参数)
│   ├── backend.py       # 裸 socket HTTP POST 通信层
│   ├── parser.py        # 裸文本响应解析(去 Welcome/CMD 标记)
│   └── tools.py         # 32 个工具定义
├── tests/               # mock console + 单元测试
├── examples/            # opencode 集成示例
└── scripts/smoke_test.py
```

## 安装(Linux,与 skynet 同机)

```bash
# 1. 获取代码(git clone,或拷贝已有目录到 ~/skynet-mcp)
mkdir -p ~/skynet-mcp && cp -r <代码路径>/* ~/skynet-mcp/

# 2. 创建 venv 并安装依赖(python3 需 >= 3.10)
cd ~/skynet-mcp
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/pip install -e .

# 3. 验证
.venv/bin/python scripts/smoke_test.py --port 8000
```

## 启动

MCP 服务器以 **streamable-http** 方式**独立启动**(手动,或交给 systemd / supervisor 等
进程管理器托管),监听固定端口,由 opencode 等客户端通过 HTTP 远程连接——不再由客户端
自动拉起子进程。

```bash
# WSL 内启动,默认监听 127.0.0.1:8765(Windows 侧经 WSL2 localhost 转发访问)
.venv/bin/python -m skynet_mcp.main
# 自定义 HTTP 监听端口
.venv/bin/python -m skynet_mcp.main --http-port 8765
```

HTTP 监听参数:

| 参数 | 默认值 | 说明 |
|---|---|---|
| `--http-host` | `127.0.0.1` | HTTP 监听地址 |
| `--http-port` | `8765` | HTTP 监听端口(与 skynet console 端口区分) |

skynet debug console 连接参数:

| 参数 | 环境变量 | 默认值 |
|---|---|---|
| `--host` | `SKYNET_CONSOLE_HOST` | `127.0.0.1` |
| `--port` | `SKYNET_CONSOLE_PORT` | `8000` |
| `--timeout` | `SKYNET_CONSOLE_TIMEOUT` | `30`(秒) |

- 端点 URL:`http://127.0.0.1:8765/mcp`(MCP streamable-http 协议),
  opencode / skynet-mcp-client 等客户端均通过该端点连接
- 安全:默认绑定 `127.0.0.1` 并开启 DNS rebinding 保护;若需跨机访问,
  用 `--http-host 0.0.0.0` 并确保网络可信(或走 SSH 隧道),勿暴露公网

## 接入 opencode

先按上文**独立启动 MCP 服务器**,再用 `type: "remote"` 连接。**配置要写在 opencode 运行
的那一侧**——opencode 只读自己进程侧的全局配置 `~/.config/opencode/opencode.json` +
当前目录项目级 `opencode.json`;Windows 侧启动的 opencode 看不到 WSL 里的配置
(表现为 `opencode mcp list` 显示 `No MCP servers configured`)。

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "skynet": {
      "type": "remote",
      "url": "http://127.0.0.1:8765/mcp",
      "enabled": true
    }
  }
}
```

- **WSL 内启动 opencode**:写 WSL 的项目根 `opencode.json`(本仓库已带)或全局
  `~/.config/opencode/opencode.json`
- **Windows 侧启动 opencode**(PowerShell / Desktop):写 Windows 全局
  `C:\Users\Admin\.config\opencode\opencode.json`(已有内容如 `instructions` 时合并保留)
  或启动目录的项目级 `opencode.json`。`url` 仍用 `http://127.0.0.1:8765/mcp`——WSL2
  localhost 转发会把 Windows 侧 `127.0.0.1:8765` 直通到 WSL 里监听的 MCP 进程,
  **无需改 MCP 监听地址**
- **MCP 服务器需独立启动**(手动或进程管理器托管),opencode 不再自动拉起子进程;
  服务器未启动时 opencode 会显示连接失败
- 局域网/公网访问需把 MCP 重启为 `--http-host 0.0.0.0`——但服务器**无鉴权**且有
  `kill` / `inject` / `raw_command` 等危险命令,只建议走 SSH 隧道
  (`ssh -L 8765:127.0.0.1:8765 user@remote`)或加 Bearer Token 鉴权,勿直接暴露公网

改完重启 opencode,在对话里输入 `/mcp` 确认 `skynet` 已连接,然后让它
"用 skynet 工具列出当前所有服务"即可端到端验证。完整测试与排障步骤见
`examples/opencode-mcp.md`;覆盖全部 32 个工具的测试提示词见 `examples/mcp-test-prompts.md`。

## 部署方式

1. **WSL/Linux 内直连(推荐)**:opencode、MCP 进程与 skynet 都在 WSL 内,
   直连 `127.0.0.1:<port>`,零转发
2. **SSH 隧道(远端生产机)**:MCP 进程与 skynet 不同机时,
   `ssh -L 8000:127.0.0.1:8000 user@remote`,opencode 连本地 8000 即可。
   **切勿把 debug console 端口直接暴露到公网**

## 工具清单(32 个)

| 工具 | 底层命令 | 说明 |
|---|---|---|
| `help` | `help` | 全部命令帮助 |
| `list` | `list` | 列出所有服务及地址 |
| `service` | `service` | 列出唯一服务与挂起请求 |
| `stat [ti]` | `stat` | 消息队列/挂起请求/消息总数 |
| `mem [ti]` | `mem` | 各服务 lua 内存 |
| `gc [ti]` | `gc` | 全服强制 GC + 内存报告 |
| `netstat` | `netstat` | 网络连接概况 |
| `cmem` / `jmem` | `cmem` / `jmem` | C 层 / jemalloc 内存 |
| `dumpheap` / `profactive` | `dumpheap` / `profactive` | 堆分析 |
| `start` / `log` / `snax` | 同名 | 启动新服务(⚠) |
| `kill` / `exit` | 同名 | 中止服务(【危险】) |
| `signal` | `signal` | 打断死循环拿到调用栈(【危险】) |
| `task` / `uniqtask` | 同名 | 挂起请求调用栈 |
| `killtask` | `killtask` | 终止线程(⚠) |
| `info` | `info` | 服务内部信息 |
| `inject` | `inject` | 注入补丁脚本(【危险】,路径为 skynet 视角) |
| `dbgcmd` | `dbgcmd` | 任意 debug 协议命令(⚠) |
| `ping` | `ping` | 往返耗时 |
| `trace` | `trace` | 协议跟踪 |
| `logon` / `logoff` | 同名 | 记录服务输入消息 |
| `call` | `call` | 调用服务 lua 接口(【危险】) |
| `getenv` / `setenv` | 同名 | 环境变量读写 |
| `raw_command` | 透传 | 任意命令兜底(【危险】) |

地址写法:`:01000001`(八位 hex)、`1`(简写)、`.名字`(本地服务名)。

## 安全注意事项

- skynet debug console **无鉴权**,且只监听 `127.0.0.1`——远程使用请走 SSH 隧道,不要暴露端口
- 【危险】命令(kill/exit/signal/inject/call/raw_command)会影响运行中的服务,
  工具描述中已标注;coding agent 调用前应让用户确认
- `debug` 交互式命令需要持久终端会话,HTTP 通道不支持,已显式拒绝(请用 telnet/nc 手动连接)
- `inject` 的脚本路径是 **skynet 服务器视角**(MCP 与 skynet 可能在不同文件系统)

## 开发与测试

> 通过 opencode 端到端测试全部 32 个 `skynet_*` 工具的可复制提示词见
> `examples/mcp-test-prompts.md`;下面是开发者侧的单元/冒烟测试。

```bash
# 单元测试
python -m pytest tests/ -v

# 冒烟测试(先起 mock console)
python -m tests.mock_console          # 打印 mock 端口
python scripts/smoke_test.py --port <mock端口>

# 或对真实 skynet 冒烟
python scripts/smoke_test.py --port 8000

# 手工验证(nc 直连真实 console)
printf 'POST / HTTP/1.0\r\nContent-Length: 4\r\n\r\nlist' | nc 127.0.0.1 8000
```