内容
# anka-mcp
Anka [模型上下文协议](https://modelcontextprotocol.io)(MCP)服务器,用于 Anka macOS 虚拟化。它作为一个**可流式传输的 HTTP 服务器**运行,带有 bearer-token 认证,并暴露两个精选的、特定用途的工具集,根据配置自动启用:
- **控制器后端** - 与 [Anka Build Cloud Controller](https://docs.veertu.com/anka/anka-build-cloud/working-with-controller-and-api/) REST API 通信,请求来自机队的 VM,并返回 SSH 连接详细信息。不使用 `anka` CLI。
- **本地后端** - 驱动本地 `anka` CLI,带有少量安全、受限的生命周期命令,受运行 VM 限制的保护。
故意没有通用的“运行任意 anka 命令”工具。
## 后端及其启用时机
| 后端 | 启用条件 | 工具 |
| --------- | ----------------------------------------------------- | -------------------------------------------------------------------- |
| 控制器 | 设置了 `ANKA_CONTROLLER_URL` | `controller_list_templates`、`controller_request_vm`、`controller_get_vm`、`controller_terminate_vm` |
| 本地 | 设置了 `ANKA_LOCAL=on`,或在未配置控制器时设置为 `auto`(检测 `anka` CLI) | `local_list_templates`、`local_start_vm`、`local_show_vm`、`local_ssh_access`、`local_delete_vm` |
当设置了 `ANKA_CONTROLLER_URL` 时,本地后端默认关闭,因此仅暴露控制器工具。设置 `ANKA_LOCAL=on` 或 `ANKA_LOCAL=auto` 以启用两者。如果未启用任一后端,则服务器拒绝启动。
## 要求
- Node.js >= 18
- 对于本地后端:安装了 `anka` CLI 并位于 `PATH`(或将 `ANKA_BIN` 指向它)
- 对于控制器后端:可以访问 Anka Build Cloud Controller
## 安装和运行
### 从 npm
需要 Node.js >= 18。
```bash
# 不需要全局安装即可运行
npx @veertu/anka-mcp
# 或全局安装
npm install -g @veertu/anka-mcp
anka-mcp
```
在启动前设置身份验证和后端环境变量(参见 [配置](#配置))。示例:
```bash
export MCP_AUTH_TOKEN="$(openssl rand -hex 32)"
echo "MCP_AUTH_TOKEN=$MCP_AUTH_TOKEN"
anka-mcp
```
### 从源码(贡献者)
```bash
npm install
npm run build
export MCP_AUTH_TOKEN="$(openssl rand -hex 32)"
echo "MCP_AUTH_TOKEN=$MCP_AUTH_TOKEN"
npm start
```
对于**多客户端**部署,请使用管理 API 而不是单个共享令牌:
```bash
export MCP_ADMIN_TOKEN="$(openssl rand -hex 32)"
export ANKA_CONTROLLER_URL="http://your-controller:8090"
npm start
# 创建每个客户端的 MCP 令牌(仅显示一次明文):
curl -s -X POST "http://localhost:9111/admin/tokens" \
-H "Authorization: Bearer $MCP_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"label":"team-a"}'
```
端点在 `http://<host>:<port>/mcp` 处提供。对于本地开发,无需身份验证(绝不能在 localhost 以外公开):
```bash
MCP_ALLOW_NO_AUTH=1 npm run dev
```
## 用例 1:控制器机队
代理向 MCP 服务器请求 VM;服务器生成临时 SSH 密钥,通过控制器 `startup_script` 将其传递给 VM(带有 `startup_script_condition: 1`,以便脚本立即运行,在网络启动之前),等待控制器报告实例启动,并且使用该密钥的 SSH 身份验证探针通过转发端口成功,然后返回主机 IP、转发 SSH 端口、私钥路径和可用的 `ssh` 命令。然后,代理自行 SSH 登录。
```mermaid
flowchart LR
agent["AI 代理"] -->|"controller_request_vm {vmid}"| mcp["anka-mcp"]
mcp -->|"POST /api/v1/vm"| ctl["控制器 API"]
mcp -->|"poll GET /api/v1/vm?id="| ctl
mcp -->|"{host, port, username, private_key_path, command}"| agent
agent -->|"ssh -p port username@host"| vm["macOS VM"]
```
VM 模板必须公开 SSH 访客端口的端口转发(默认 `22`,例如 `port-forward-22` 标签),或传递 `addSshPortForward: true` 给 `controller_request_vm` 以在启动时添加规则。
## 用例 2:本地笔记本电脑
代理通过有限的命令集管理开发人员自己机器上的 VM:列出模板、启动(始终克隆选定的模板到一个全新的 VM,因此原始模板永远不会被触摸)、显示、准备 SSH 访问、删除。运行 VM 限制(默认 2)防止超过本地 Anka 并发限制。删除工具始终需要特定的 VM 名称,永远不能删除所有 VM。
对于 SSH,`local_ssh_access` 在主机上生成一个一次性 ed25519 密钥对,使用 `anka cp` 将公钥复制到正在运行的 VM 中,安装到 VM 的 `~/.ssh/authorized_keys` 中(通过 `anka run`),并返回私钥路径和可用的 `ssh` 命令。然后,代理(在同一台机器上)直接连接到 VM 的共享网络 IP。VM 模板必须启用远程登录(sshd)。
## 配置
所有配置均通过环境变量进行。
### HTTP 传输 + 身份验证
| 变量 | 默认值 | 描述 |
| --------------------- | --------- | -------------------------------------------------------------------- |
| `MCP_HTTP_PORT` | `9111` | HTTP 服务器监听的端口。 |
| `MCP_HTTP_HOST` | `127.0.0.1` | 绑定的接口。默认本地主机;设置为 `0.0.0.0` 以在 TLS 后面进行远程访问。 |
| `MCP_AUTH_TOKEN` | (无) | 所有 MCP 客户端的旧式单一 bearer 令牌。仍然受支持。 |
| `MCP_ADMIN_TOKEN` | (无) | `/admin/*` 路由的管理 bearer 令牌。启用令牌管理。 |
| `MCP_DB_PATH` | `./anka-mcp.db` | 客户端令牌和实例所有权的 SQLite 数据库。 |
| `MCP_REVOKE_CLEANUP` | `on` | 当令牌被吊销时,终止其控制器 VM(设置为 `off` 以跳过)。 |
| `MCP_ALLOW_NO_AUTH` | `false` | 设置为 `1` 以在未经身份验证的情况下运行(仅限本地开发)。 |
| `MCP_ALLOWED_ORIGINS` | (无) | DNS 重新绑定保护的以逗号分隔的源白名单。 |
| `MCP_LOG` | `on` | 请求日志记录到 stderr。设置为 `off`(或 `0`/`false`/`no`)以禁用。 |
| `MCP_AUDIT_LOG` | (无) | 可选的仅追加审计日志文件(复制 stderr 日志行)。 |
| `MCP_MAX_BODY_BYTES` | `1048576` | 最大 JSON 请求正文大小(1 MiB)。 |
| `MCP_RATE_LIMIT_RPM` | `120` | 每个客户端 IP 每分钟的最大请求数(`0` = 禁用)。 |
| `MCP_SESSION_IDLE_MS` | `3600000` | 空闲 MCP 会话回收阈值(1 小时)。 |
| `MCP_MAX_SESSIONS` | `50` | 最大并发 MCP 会话数。 |
| `MCP_MAX_RESPONSE_CHARS` | `32768` | 最大序列化工具响应大小(32 KiB)。 |
启用日志记录时,每个 MCP 请求都会以 stderr 写入客户端源(IP 和用户代理)、JSON-RPC 方法、工具名称和参数、工具响应(密码和私钥已编辑)以及任何底层 `anka` 或控制器 API 调用。示例:
```
anka-mcp: 2026-06-22T16:52:15.021Z [127.0.0.1 (Cursor/1.x)] mcp tools/call {"tool":"local_list_templates","args":{}}
anka-mcp: 2026-06-22T16:52:15.059Z [127.0.0.1 (Cursor/1.x)] anka anka -j list -> ok
anka-mcp: 2026-06-22T16:52:15.059Z [127.0.0.1 (Cursor/1.x)] tool local_list_templates args={} -> {"vms":[...]}
```
### 控制器后端
| 变量 | 默认值 | 描述 |
| ----------------------------- | -------- | --------------------------------------------------------- |
| `ANKA_CONTROLLER_URL` | (无) | 基本 URL,例如 `http://anka.controller:8090`。启用后端。 |
| `ANKA_CONTROLLER_AUTH` | (无) | 原始 `Authorization` 头值(根令牌 / UAK / 基本)。 |
| `ANKA_CONTROLLER_TLS_INSECURE` | `false` | 设置为 `1` 以跳过 TLS 证书验证。 |
| `ANKA_CONTROLLER_POLL_INTERVAL_MS` | `3000` | 实例状态轮询之间的间隔。 |
| `ANKA_CONTROLLER_START_TIMEOUT_MS` | `180000` | 等待 VM 成为 SSH 就绪的最长时间。 |
| `ANKA_CONTROLLER_SSH_PROBE` | `on` | 启用时,`controller_request_vm` 在返回前运行 SSH 身份验证探针(设置为 `0` 以跳过)。 |
### 本地后端
| 变量 | 默认值 | 描述 |
| ---------------- | ------- | --------------------------------------------------------- |
| `ANKA_LOCAL` | `auto`* | `auto`(检测二进制文件),`on` 或 `off`。* 默认情况下,如果设置了 `ANKA_CONTROLLER_URL`,则默认为 `off`。 |
| `ANKA_BIN` | `anka` | `anka` 二进制文件的路径(或名称)。 |
| `ANKA_TIMEOUT_MS` | `300000` | 单个 anka 调用可以运行的最长时间。 |
| `ANKA_LOCAL_MAX_VMS` | `2` | 允许运行的最大 VM 数量,在启动被拒绝之前。 |
| `ANKA_LOCAL_POLL_INTERVAL_MS` | `2000` | 等待 VM 的 IP 时的状态轮询之间的间隔。 |
| `ANKA_LOCAL_IP_TIMEOUT_MS` | `120000` | `local_start_vm` / `local_ssh_access` 等待 IP 的最长时间。 |
### SSH 连接详细信息(返回给代理)
| 变量 | 默认值 | 描述 |
| --------------------- | ------- | ----------------------------------------------- |
| `ANKA_VM_SSH_USER` | `anka` | 用于 SSH 登录到 VM 的用户名。 |
| `ANKA_VM_SSH_PASSWORD` | `admin` | 用于 SSH 登录到 VM 的密码。 |
| `ANKA_VM_SSH_GUEST_PORT` | `22` | 映射到 SSH 的访客端口(在端口转发中匹配)。 |
返回的 `ssh` 命令包括 `-o IdentitiesOnly=yes`,因此本地 ssh 代理不会提供其他密钥并导致身份验证失败。如果您构建自己的命令,请使用该标志或使用 `SSH_AUTH_SOCK=` 前缀禁用代理。
对于生产环境,请在服务器前面终止 TLS,以便 bearer 令牌和返回的凭证不会以明文发送。
参见 [SECURITY.md](SECURITY.md) 以获取完整的操作员安全指南。
### 远程部署
默认情况下,服务器仅绑定到 **localhost** (`127.0.0.1`)。要将其公开到网络上:
1. 设置 `MCP_HTTP_HOST=0.0.0.0`(或特定接口)。
2. 在 anka-mcp 前面终止 **TLS**。
3. 防火墙仅允许受信任的客户端访问。
4. 通过管理 API 发出**每个客户端的令牌** - 避免共享 `MCP_AUTH_TOKEN`。
5. 永远不要在非 localhost 主机上使用 `MCP_ALLOW_NO_AUTH`。
服务器在启动时绑定到非环回地址时会打印警告。
### 多客户端令牌和 VM 隔离
当设置了 `MCP_ADMIN_TOKEN` 时,服务器会公开一个管理 API,用于创建和吊销每个客户端的 MCP bearer 令牌。令牌存储在 SQLite 中(`MCP_DB_PATH`);只有哈希秘密被持久化。
| 方法 | 路径 | 身份验证 | 目的 |
| -------- | -------------------- | --------------------------- | ------------------------------- |
| `POST` | `/admin/tokens` | `Bearer MCP_ADMIN_TOKEN` | 创建客户端令牌 |
| `GET` | `/admin/tokens` | 管理令牌 | 列出令牌(无秘密) |
| `DELETE` | `/admin/tokens/:id` | 管理令牌 | 吊销令牌并清理 VM |
管理令牌和 MCP 令牌是分开的:管理令牌永远不能在 `/mcp` 上工作,而客户端令牌永远不能在 `/admin/*` 上工作。
**控制器 VM 隔离:** 每个客户端令牌只能 `controller_get_vm` / `controller_terminate_vm` 通过 `controller_request_vm` 创建的实例。吊销令牌会立即阻止 MCP 访问,并默认情况下(`MCP_REVOKE_CLEANUP=on`),尽力终止由该令牌拥有的所有控制器实例。吊销响应包括 `cleanup.terminated` 和 `cleanup.failed` 数组。
旧式的 `MCP_AUTH_TOKEN` 仍然作为单个共享客户端身份(`legacy`)起作用;所有在其下创建的控制器 VM 共享一个所有权桶。
备份 `anka-mcp.db` 以进行灾难恢复;它在第一次启动时自动创建。
## 工具
### 控制器
- `controller_list_templates` - 列出注册表模板(`id`、`name`、`arch`)以查找 `vmid`。
- `controller_request_vm` `{ vmid, tag?, name?, externalId?, addSshPortForward? }` - 启动一个 VM,通过控制器 `startup_script` 安装临时 SSH 密钥,等待 SSH 身份验证通过转发端口成功,返回 `{ instance_id, ssh: { host, port, username, private_key_path, command } }`。控制器 `external_id` 自动填充为 MCP 客户端、IP、用户代理、会话和凭证 ID;传递 `externalId` 以追加自定义 `ref`。
- `controller_get_vm` `{ instance_id }` - 获取现有实例的当前状态和 SSH 详细信息(必须由调用者的令牌拥有)。
- `controller_terminate_vm` `{ instance_id }` - 终止实例(必须由调用者的令牌拥有)。
### 本地
- `local_list_templates` - 列出本地 VM 库以查找要克隆的模板。
- `local_start_vm` `{ template, name?, wait?, timeoutSeconds? }` - 克隆给定的模板到一个全新的、一次性 VM 并启动它。原始模板永远不会启动或修改。`name` 默认生成一个自动生成的名称(`mcp-<id>`)。受运行 VM 限制的约束。默认情况下,等待新 VM 启动并获取 IP,然后返回 `{ ok, name, source, ip }`;传递 `wait: false` 以立即返回。使用 `local_delete_vm` 删除时。
- `local_show_vm` `{ name }` - 获取 VM 的 IP 地址。
- `local_ssh_access` `{ name }` - 在正在运行的 VM 中安装临时 SSH 密钥(如果需要,等待待处理 IP);返回 `{ ip, port, user, private_key_path, command }`。
- `local_delete_vm` `{ name }` - 删除一个特定的 VM。
以 `-` 开头的 VM/模板名称被拒绝,因此名称永远不会被重新解释为 CLI 标志。
## 连接客户端
将任何支持可流式传输 HTTP 的 MCP 客户端指向 `http://<host>:<port>/mcp`,并使用 bearer 令牌。例如,Cursor 的 `mcp.json`:
```json
{
"mcpServers": {
"anka": {
"url": "http://your-host:9111/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}
```
## 测试
```bash
npm test # 运行一次测试套件
npm run test:watch # 监视模式
```
测试套件(Vitest)是密封的 - 它不需要真正的 Anka 安装或控制器:
- 单元测试涵盖配置解析、令牌存储、控制器客户端 + `extractSsh` / `isSshReady`(针对进程内模拟控制器),以及输入验证 / 输出塑形。
- 端到端测试通过 HTTP 启动真实服务器,并通过 MCP 协议使用模拟的 `anka` 二进制文件([test/fixtures/fake-anka.mjs](test/fixtures/fake-anka.mjs))和模拟控制器([test/helpers/controllerMock.ts](test/helpers/controllerMock.ts))来测试工具。它们验证身份验证、管理令牌 API、每个令牌的控制器 VM 隔离、吊销清理、后端门控、每个后端工具暴露、控制器请求 -> SSH 流、本地 2-VM 保护、标志注入拒绝和 SSH 密钥注入流。
## 添加新工具
1. 在 `src/tools/controller/` 或 `src/tools/local/` 下创建一个文件,导出一个工具,使用 `defineTool({ name, config, handler })`。
2. 将其添加到该后端 `index.ts` 中的数组中(`controllerTools` 或 `localTools`)。然后,当启用该后端时,它会自动注册。
## 项目布局
```
src/
index.ts # 入口:启动 HTTP 服务器
auth.ts # MCP bearer 令牌解析
config.ts # 环境驱动的配置 + 后端检测
anka.ts # runAnka():execFile 包装器 + JSON 信封解析
controller.ts # Anka Build Cloud Controller API 客户端
server.ts # createServer():McpServer + 注册工具
tokens/
store.ts # SQLite 令牌 + 实例所有权存储
schema.ts # 数据库迁移
ownership.ts # 控制器实例访问助手
cleanup.ts # 吊销时控制器 VM 终止
tools/
define-tool.ts # defineTool 助手 + jsonResult
index.ts # 注册启用的后端工具
controller/ # 控制器_* 工具
local/ # 本地_* 工具 (+ vms.ts: 列表/计数/名称模式)
transports/
http.ts # 可流式传输的 HTTP 传输 + 身份验证/源中间件
admin.ts # /admin/tokens 路由
test/
fixtures/fake-anka.mjs # 假的 anka CLI,用于无环测试
helpers/ # 模拟控制器 + HTTP 上的 MCP 客户端
unit/ # 配置、控制器客户端、验证/塑形
e2e/ # HTTP 上的完整服务器(身份验证、控制器、本地)
```
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器