内容
# offsec-mcp-server
用于管理您自己的授权OffSec Proving Grounds(PG Play / Practice)实验室的MCP服务器:列出和搜索目录,查看机器详情,启动/停止/重置机器,并将机器的通关指南拉取到您的会话中进行个人学习。
> 仅在OffSec的条款允许下,用于您自己的账户。不要重新分发通关内容 — OffSec的社区规则禁止分享完整的解决方案/通关指南。
## 文档
- **[安装与设置](docs/INSTALLATION.md)** — 逐步安装和每个客户端的配置(Claude Desktop,Claude Code,Cursor,Windsurf,VS Code,通用stdio/HTTP),令牌检索,故障排除。
- **[使用与工具参考](docs/USAGE.md)** — 每个工具的参数,示例提示和样本输出。
- 本README — 快速入门,架构和实时门户的连接方式。
## 快速入门
```bash
git clone https://github.com/cbdmaul/offsec-labs-mcp.git
cd offsec-labs-mcp
npm install
npm run build
echo "$(pwd)/dist/index.js" # 您提供给MCP客户端的路径
```
然后将服务器添加到您的客户端(完整的平台特定步骤在 **[docs/INSTALLATION.md](docs/INSTALLATION.md)** 中):
| 客户端 | 配置位置 |
|--------|--------------------|
| **Claude Desktop** | `claude_desktop_config.json` → `mcpServers` |
| **Claude Code** | `claude mcp add offsec --env OFFSEC_BEARER_TOKEN=… -- node /path/dist/index.js` |
| **Cursor** | `~/.cursor/mcp.json`(或 `.cursor/mcp.json`)→ `mcpServers` |
| **Windsurf** | `~/.codeium/windsurf/mcp_config.json` → `mcpServers` |
| **VS Code** | `.vscode/mcp.json` → `servers` |
最小配置(Claude Desktop / Cursor / Windsurf 格式):
```json
{
"mcpServers": {
"offsec": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/offsec-labs-mcp/dist/index.js"],
"env": { "OFFSEC_BEARER_TOKEN": "在此处粘贴您的令牌" }
}
}
}
```
运行 `offsec_whoami` 以确认身份验证。新用户?从 **[docs/INSTALLATION.md](docs/INSTALLATION.md)** 开始。
## 状态 — 与实时门户映射
`src/constants.ts` 中的端点已 **针对实时门户** (`https://portal.offsec.com`) 使用登录的 bearer 令牌进行验证,通过读取 SPA 的运行时配置 (`/config.json`)、其 JS 包和实际的 JSON 响应。它们不再是猜测。
门户的连接方式(已发现):
| 问题 | 来源 |
|---------|--------|
| 身份验证 | `Authorization: Bearer <token>` 在所有 `/api/*` 端点上有效 |
| REST API | `https://portal.offsec.com/api/*` |
| 服务网关 | `https://portal.offsec.com/services`(来自 `config.json` → `API_GATEWAY`) |
| 实验室目录 | 一个 **Typesense** 集群。门户从网关获取 **作用域** 搜索密钥,然后直接查询 Typesense。 |
服务器在运行时加载 `/config.json` 以发现网关基础和 Typesense 节点,因此部署更改会自动拾取。所有值仍可以通过环境变量重写。
## 工具
| 工具 | 功能 | 是否修改? |
|------|--------------|-----------|
| `offsec_whoami` | 验证您的令牌是否有效;返回您的个人资料 | 否 |
| `offsec_list_labs` | 列出/搜索目录(默认组 `Play`);按操作系统/难度过滤,分页 | 否 |
| `offsec_get_machine_details` | 操作系统,难度,积分,组,描述,目标,凭证 | 否 |
| `offsec_list_walkthroughs` | 列出通关指南行(主机 ID,类别,解锁状态) | 否 |
| `offsec_get_walkthrough` | 检索解锁的通关指南内容 | 否 |
| `offsec_list_running_labs` | 通过 WebSocket 发现正在运行的实例(ID,IP,状态) | 否 |
| `offsec_start_machine` | 启动机器(异步部署;参见下面的备注) | **是** |
| `offsec_stop_machine` | 关闭正在运行的实例(自动发现 ID,或显式) | **是** |
| `offsec_revert_machine` | 将正在运行的实例重置为干净状态(自动发现 ID,或显式) | **是** |
每个工具都接受 `response_format`:`markdown`(默认)或 `json`。完整的参数参考,示例提示和样本输出在 **[docs/USAGE.md](docs/USAGE.md)** 中。
### 标识符
- **machine_id** 接受数字主机 ID(`189`),slug(`kevin-189`)或名称(`Kevin`)。数字 ID 最可靠。
- **instance_id**(用于停止/重置)是主机 **实例** ID — 一个不同的,更大的数字(例如 `32062625`)。有关如何获取它的限制,请参见下面的内容。
### 实例 ID 和 WebSocket
启动/停止/重置是 **异步** 的,并针对实时门户进行了验证:
| 操作 | 请求 | 响应 |
|--------|---------|----------|
| 启动 | `POST /api/host-instances/` `{host:189}` | `201 {"message":"Deploy request in progress"}` |
| 停止 | `PATCH /api/host-instances/<instanceId>/` `{action:"stop",context_learning_unit_id:189}` | `200 {"message":"Stop action in progress"}` |
| 重置 | `PATCH /api/host-instances/<instanceId>/` `{action:"revert",context_learning_unit_id:189}` | `200 {"message":"Revert action in progress"}` |
**REST API 永远不会返回实例 ID 或目标 IP** — 启动仅确认部署,并且没有 REST 端点列出正在运行的实例(`GET /api/host-instances/` 405;`/api/learning-units/<id>/full` 返回 `ip:null`)。这些值仅存在于事件 **WebSocket**(`wss://portal.offsec.com/ws/events`)中。
此服务器自己读取它们。`offsec_list_running_labs`(和停止/重置中的自动发现)连接到 WebSocket 并运行门户自己的握手 — **使用 bearer 令牌,不需要 cookie**:
1. 升级未经身份验证 → 连接。
2. 发送 `{"action":"sign_in","value":"<bearer token>"}` → `group:"auth"` 成功。
3. 发送 `{"action":"subscribe","value":"host_actions"}` → 服务器立即推送 `host_actions/started` 快照,其中包含每个正在运行的实例:`host_instance: { id, ip, related_host:{id,name}, host_instance_state }`。
因此,您通常 **不需要实例 ID**:`offsec_stop_machine` / `offsec_revert_machine` 在没有 `instance_id` 的情况下自动发现正在运行的实例(OffSec 允许一个并发机器;传递 `machine_id` 以消除歧义)。由于发现通过 `sign_in` 消息进行身份验证,因此此路径 **需要 `OFFSEC_BEARER_TOKEN`**(单独的 cookie 不起作用)。第二个启动返回 `user_has_started_machine`,直到第一个停止,停止/重置在部署过程中返回 `host_action_in_progress` — 在机器达到 `started` 后重试。
## 身份验证
不处理密码。提供以下 **一个** 环境变量(在您的 MCP 客户端配置中设置,或在 `.env` 中用于烟雾测试):
- `OFFSEC_BEARER_TOKEN` — 作为 `Authorization: Bearer <token>` 发送(门户 SPA 本身使用;**推荐**,并且需要 WebSocket 发现)
- `OFFSEC_COOKIE` — 原始 `Cookie:` 头(仅限 REST 的备用方案)
如何获取 bearer:登录 `portal.offsec.com`,打开开发者工具(F12)→ **网络** → 点击任何 `/api/...` 请求 → **请求头** → 复制 `authorization:` 值(在 `Bearer ` 之后)。令牌过期 — 在 401 上刷新。详细、截图友好的步骤在 **[docs/INSTALLATION.md § 3](docs/INSTALLATION.md#3-get-your-offsec-bearer-token)** 中。
### 典型流程
1. `offsec_list_labs`(组 `Play`)→ 选择一个机器 ID/斜杠。
2. `offsec_get_machine_details` → 阅读简介。
3. `offsec_start_machine` → 部署开始(异步)。
4. `offsec_list_running_labs` → 读取其 **IP** 和 **实例 ID**(一旦 `started`)。
5. 通过您的 OpenVPN 客户端连接并操作机器。
6. 如果您想要官方通关指南,请使用 `offsec_get_walkthrough`(启动后解锁)。
7. `offsec_stop_machine`(或 `offsec_revert_machine`)— 不需要实例 ID;它会自动被发现。
## 测试
单元测试在 `tests/fixtures/` 下使用 **真实门户响应** 运行规范化程序和客户端帮助程序(已删除个人身份信息)。无需网络。
```bash
npm test
```
还有一个实时、只读的烟雾测试,使用您的令牌(不会启动任何机器):
```bash
node --env-file=.env scripts/live-smoke.mjs
```
## HTTP 传输(可选)
```bash
TRANSPORT=http PORT=3000 node dist/index.js # 绑定 127.0.0.1:3000/mcp
```
需要可选的 `express` 依赖项(默认安装)。
## 环境变量参考
| 变量 | 目的 |
|-----|---------|
| `OFFSEC_BEARER_TOKEN` | Bearer 身份验证令牌(推荐) |
| `OFFSEC_COOKIE` | 原始 cookie 头(替代 bearer) |
| `OFFSEC_API_BASE_URL` | 重写 REST 基础(默认 `https://portal.offsec.com`) |
| `OFFSEC_API_GATEWAY` | 重写服务网关(默认来自 `config.json`,否则 `…/services`) |
| `OFFSEC_TYPESENSE_HOST` | 重写 Typesense 节点主机(默认来自 `config.json`) |
| `OFFSEC_WS_URL` | 重写事件 WebSocket URL(默认来自 `config.json`,否则 `wss://portal.offsec.com/ws/events`) |
| `OFFSEC_PORTAL_WEB_BASE` | 重写生成的链接的 web 基础 |
| `OFFSEC_USER_AGENT` | 重写 User-Agent 头 |
| `TRANSPORT` | `stdio`(默认)或 `http` |
| `PORT` | HTTP 端口(默认 3000) |