# PixelLab Hub MCP 参考文档

> 多账号集成的 PixelLab MCP 代理。工具语义对齐官方 MCP，底层用本站 `provider_keys` 池调度。

**线上端点**

| 项 | 值 |
|----|-----|
| 本文档 URL | https://dotforge.eu.cc/docs/mcp-hub |
| 原始 Markdown | https://dotforge.eu.cc/docs/mcp-hub.md |
| API 域名 | `https://api.dotforge.eu.cc` |
| MCP URL（Streamable HTTP） | `https://api.dotforge.eu.cc/mcp` |
| 旧版 SSE URL | `https://api.dotforge.eu.cc/sse` |
| Web | `https://dotforge.eu.cc` |
| 官方工具说明 | https://api.pixellab.ai/mcp/docs |

---

## 1. 与官方 MCP 的差异（必读）

| 点 | 官方 | Hub（本项目） |
|----|------|----------------|
| 入口 | `https://api.pixellab.ai/mcp` | 本站 `/mcp` |
| 鉴权 | PixelLab 账号 API token | **管理员专用** `plmcp_…`（`mcp_api_keys`） |
| 上游 key | 单账号 | 多账号 `KeySelector` + 资源粘性 |
| 工具范围 | 全量（含 tileset/UI/chat/sandbox 等） | **v1 仅角色 + 动画 + 物件**（+ `agent_help`） |
| `list_*` | 该官方账号下全部资源 | **仅本 Hub 创建并登记**的资源 |
| 本站 credits | 无 | **不扣**；消耗的是上游 PixelLab 额度 |
| 下载链接 | 官方 UUID 直链 | 仍为官方 download URL（UUID 即密钥） |

**非目标（v1）**：tileset / isometric / UI / chat / sandbox；普通用户 MCP；自动同步官方网页创建的资源。

---

## 2. 接入配置

### 2.1 签发 MCP Key（管理员）

需本站 **admin** 会话：

```bash
curl -sS -X POST "https://api.dotforge.eu.cc/api/v1/admin/mcp-keys" \
  -H "Authorization: Bearer <SESSION_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"name":"cursor-laptop"}'
```

响应含 `token`（`plmcp_…`）**仅此一次**。另有：

- `GET /api/v1/admin/mcp-keys` — 列表（无明文）
- `POST /api/v1/admin/mcp-keys/{id}/revoke` — 吊销

### 2.2 OpenCode

```jsonc
{
  "mcp": {
    "pixellab-hub": {
      "type": "remote",
      "url": "https://api.dotforge.eu.cc/mcp",
      "enabled": true,
      "oauth": false,
      "headers": {
        "Authorization": "Bearer plmcp_YOUR_TOKEN"
      },
      "timeout": 120000
    }
  }
}
```

校验：`opencode mcp list` → `pixellab-hub connected`。

### 2.3 Cursor / Claude Desktop 等

```json
{
  "mcpServers": {
    "pixellab-hub": {
      "url": "https://api.dotforge.eu.cc/mcp",
      "transport": "http",
      "headers": {
        "Authorization": "Bearer plmcp_YOUR_TOKEN"
      }
    }
  }
}
```

工具名可能带前缀（如 `mcp__pixellab-hub__create_character`），以客户端实际为准。

### 2.4 传输兼容性

- 优先使用 `/mcp` 和 `http` / `streamable-http` 传输。初始化响应会返回 `Mcp-Session-Id`，后续请求需复用该 session。
- 只支持旧 HTTP+SSE 传输的客户端使用 `/sse` 和 `sse` 传输。首次 GET 会返回 `event: endpoint`，客户端随后向该 endpoint POST JSON-RPC。
- WorkBuddy 5.3 系列可继续配置 `/mcp`；服务端会识别其旧 SSE 握手。其他旧客户端应显式配置 `/sse`，不要依赖 User-Agent 兼容。
- 两个 URL 均使用同一个 `Authorization: Bearer plmcp_…`，且都不是上游 `https://api.pixellab.ai/mcp`。

### 2.5 客户端 Schema 兼容（严格校验器）

上游 PixelLab MCP 是 FastMCP/Pydantic 服务，其 `tools/list` 的 `inputSchema` 会输出 `type: ["string","null"]`、`minimum`/`maximum`、`minLength`/`maxLength`、`format: "uuid"` 与 `default: null`。部分客户端（如 DeepSeek DSH）使用严格 JSON Schema 子集校验器，只要**任何一个**工具的 schema 含不受支持的关键字，就会把该服务已注册的工具**全部回滚清理**，表现为连接成功但工具数为 0。

Hub 已在 `tools/list` 出口处统一清洗：会话中的 `inputSchema` 只保留 `type` / `oneOf` / `properties` / `required` / `additionalProperties` / `items` / `enum` / `const` / `description` / `default` / `title`。

清洗规则：

- `type: ["T","null"]` → `type: "T"`（可选性由 `required` 表达）；
- 删除 `minimum`/`maximum`/`exclusiveMinimum`/`exclusiveMaximum`/`minLength`/`maxLength`/`minItems`/`maxItems`/`format`，并把边界回填进该字段的 `description`（如 `[min 16 max 256]`、`[format: uuid]`），约束不丢失；
- 删除 `default: null`；
- `enum` / `const` 原样保留（取值约束仍有效）；
- **同名属性不会误删**：例如 `create_1_direction_object.style_images[].format` 是一个真实的参数字段名，清洗器区分"关键字"与"属性名"，该字段及其 `default: "png"` 均保留。

此清洗只作用于 `tools/list` 的描述元数据；`tools/call` 的参数仍原样转发给上游，因此上游的校验与错误语义不变。

### 2.5.1 握手前的协议协商探测（`server/discover`）

MCP 2026 规范的「现代协议」客户端（官方客户端的 `versionNegotiation: 'auto'` 模式）会在 `initialize` **之前**先发一个探测请求：

```json
{"jsonrpc":"2.0","id":"server-discover-probe-1","method":"server/discover",
 "params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}}
```

该探测**不可能携带 `Mcp-Session-Id`**。Hub 不在 2026 协议之列，按规范应回答：

```http
HTTP/1.1 400 Bad Request
Content-Type: application/json

{"error":{"code":-32001,"message":"session not found: send initialize first"},
 "id":"server-discover-probe-1","jsonrpc":"2.0"}
```

要点：

- **必须是 `400` + JSON-RPC 错误体**（规范对"需要 session 但未提供"的定义）。客户端会解析该响应体并据其判定服务端为 2025-era，随后继续 `initialize`。
- **`id` 必须原样回显**：探测使用**字符串 id**（如 `server-discover-probe-1`），不回显则客户端无法匹配该响应，只能等到探测超时。
- 早期实现返回 `400` + `text/plain`（`Mcp-Session-Id is required after initialization`）。该响应体不是 JSON-RPC，客户端拿不到可解析的错误对象。
- 会话失效（未知 `Mcp-Session-Id`）同样返回 **`404` + JSON-RPC 错误体**，而非纯文本。

---
### 2.6 POST 响应使用 JSON 模式（默认）

`POST /mcp` 的响应默认返回 **`application/json`**，而非 `text/event-stream` 的 SSE 分块流：

```http
HTTP/2 200
content-type: application/json
```

**为什么**：本服务的每个 RPC 响应都是**单条、已完整生成**的 JSON-RPC 消息，SSE 分块不带来任何流式收益；而 `text/event-stream` 会让中间层（Cloudflare 等）把响应归类为"流"，从而施加缓冲/keepalive 策略。改为普通 JSON 后该请求完全走普通 HTTP 响应路径。

MCP 规范要求客户端 POST 时同时声明 `Accept: application/json, text/event-stream` 并处理两种回包，因此对客户端是透明的（官方 v1/v2 SDK 均已实测通过）。客户端处理速度也更快：同一部署下 `initialize + tools/list` 从 **1154ms 降至 ~200ms**。

需要恢复 SSE 分块时设置服务端 `DisableJSONResponse`。`/sse` 旧传输与 WorkBuddy 握手不受影响，仍在 SSE 流上收发 `message` 事件。

### 2.6 工具列表缓存与握手健壮性

`tools/list` 是客户端**握手期**的关键调用。早期实现每次握手都会**租用一把 provider key** 并可能触发最多 12 次上游余额探测，导致工具同步依赖密钥池状态与上游延迟：

- 池被占满/无可用 key 时返回 `no_provider_key`，严格客户端会注册**零工具**；
- 上游慢或不可达时该请求可能长时间挂起，客户端 15s 超时后同样得到「无工具」。

现在的行为：

| 机制 | 说明 |
|------|------|
| 结果缓存 | 清洗后的工具列表进程内缓存 **10 分钟**，握手不再触碰密钥池（实测 8 次握手仅 1 次上游请求，单次约 0.4–0.6s） |
| 刷新失败降级 | TTL 过期后刷新失败时，回退到**上一次成功**的列表，而不是让握手失败 |
| 空列表不缓存 | 上游异常不会把「无工具」固化成持续状态 |
| 刷新超时 | 取列表的总时限 **10 秒**（含取 key），刻意短于客户端约 15s 的握手超时，保证服务端总是先于客户端放弃 |
| 错误语义 | 上游故障/超时报告 `upstream_error`，不再误报 `no_provider_key` |

**能力声明**：`capabilities.tools.listChanged` 现为 `false`。Hub 的工具面是固定的白名单投影，服务端从不发送 `notifications/tools/list_changed`，因此不再声明该能力（声明却不发送会让客户端等待永不到来的通知）。

**独立 GET SSE 流：默认关闭，返回 405。**

带 `Mcp-Session-Id` 的 `GET /mcp` 不再建立 SSE 长连接，而是返回：

```http
HTTP/1.1 405 Method Not Allowed
Allow: POST, DELETE
```

这是规范允许的降级信号：客户端收到 405 后会自动改用**纯 POST 模式**。

原因不是服务端阻塞，而是**客户端连接池饥饿**。该流只发送 `: connected` / `: keepalive` 注释，从不承载 JSON-RPC，本身没有任何功能；但只要它挂在那里，就会长期占用客户端到本源的连接。当客户端连接池较小或是共享池（代理、受限运行时、同时连多个 MCP 服务器）时，握手关键请求 `POST tools/list` 会**排在该空闲流后面**直到超时，客户端随即注册零工具。

实测（单连接池，同一部署）：

| 场景 | `POST tools/list` |
|------|-------------------|
| 不请求 GET 流 | **113ms** 成功 |
| 先建立 GET 流 | **15s 超时**（DSH `toolCallTimeoutMs`） |

关闭该流后同一场景为 **197ms / 21 工具**。

**兼容性**：`/sse` 路径与 WorkBuddy 在 `/mcp` 上的旧 SSE 握手**不受影响**（它们本身就是 SSE 握手，不是"独立流"场景）。需要恢复旧行为时设置服务端 `AllowStandaloneSSE`。


---

## 3. 核心机制

### 3.1 非阻塞

创建类工具立即返回 id，后台生成（约 2–5 分钟）。用 `get_*` 查状态；完成后用官方 download URL。

### 3.2 账号调度

| 请求类型 | Key 选择 | 额度刷新（本站缓存） |
|----------|----------|----------------------|
| 新建 `create_*` 等 | `KeySelector.Acquire`（**① 从未用过 ② 剩余额度高 ③ 跨 UTC 日空号重检**） | 主路径最多约 12 次 balance 探测；空号冷却到次日 00:00 UTC（+2m）；跨日后最多约 3 次重检；`Release` 会刷 |
| `tools/list` | 临时 Acquire | 会刷；401/429 会 disable/cooldown |
| sticky 后续（get/animate/delete/tags…） | `mcp_resources` 固定创建时账号 | **不**换号；上游 402/429/额度文案会 **NoteOutcome**（cooldown / disable） |
| `list_characters` / `list_objects` | 本地表 | 不访问上游 |

**不做**「整条 MCP 连接绑死一个账号」。同一资源的后续调用必须落在创建账号（官方资源按账号隔离）。

**均匀性**：先从未用过，再按剩余额度。已知空额冷却到 **下一个 UTC 0 点**（PixelLab 日额度重置；另 +2 分钟宽限），跨日后再 `GetBalance`。中途充值可点管理台「刷新余额」立刻清冷却。

**可观测**：API 日志 `mcp tool=… mode=acquire|sticky provider_key=… outcome=…`；管理台 `/admin/keys` 顶部「密钥池健康」与 `GET /api/v1/admin/keys/health`。

### 3.3 资源登记

创建成功后写入 `mcp_resources`（`resource_id` → `provider_key_id`）。  
未知 id 的 sticky 调用返回 `resource_not_found`（不 fan-out 猜账号）。

### 3.4 推荐工作流

```text
1. create_character(...)           → character_id
2. animate_character(character_id, template_animation_id="walking")  # 可立刻排队
3. get_character(character_id)     → 进度 / 完成后的旋转图与下载链
```

---

## 4. v1 工具清单

### 角色与动画

| 工具 | 说明 |
|------|------|
| `create_character` | 创建角色（standard / pro / v3） |
| `create_character_state` | 同一角色变体（服装/姿态等，保持身份） |
| `animate_character` | 排队动画（template / v3 / pro） |
| `get_character` | 状态、旋转、动画、下载 |
| `list_characters` | 本地登记列表 |
| `delete_character` | 删除角色 |
| `update_character_tags` | 替换标签 |
| `delete_animation` | 删角色或物件上的动画 |

### 物件

| 工具 | 说明 |
|------|------|
| `create_map_object` | 地图物件（透明底，可 inpaint） |
| `create_1_direction_object` | 单方向物件（可能多候选 review） |
| `create_8_direction_object` | 八方向物件 |
| `get_map_object` / `get_object` | 查询 |
| `list_objects` | 本地登记列表 |
| `animate_object` | 物件动画 |
| `create_object_state` | 物件变体 |
| `select_object_frames` | review 中选帧成独立物件 |
| `dismiss_review` | 丢弃 review |
| `delete_object` | 删除物件 |
| `update_object_tags` | 替换标签 |

### 其它

| 工具 | 说明 |
|------|------|
| `agent_help` | 用法问答（几乎不耗生成额度） |

**不暴露**：`create_topdown_tileset`、sidescroller/isometric、UI、chat、sandbox、`agent_feedback` 等。

参数细节与官方一致，见：https://api.pixellab.ai/mcp/docs  
下文只列高频用法与 Hub 注意点。

---

## 5. 高频工具用法

### `create_character`

```text
create_character(
  description="brave knight with shining armor",
  name="Knight",
  n_directions=8,          # 4 或 8；pro/v3 常固定 8
  size=48,                 # standard/pro 最大 128；v3 最大 256
  mode="standard",         # standard | pro | v3
  outline="single color black outline",
  shading="basic shading",
  detail="medium detail",
  view="low top-down"
)
```

- **v3 + `reference_image_base64`**：把已有南向角色图转成 8 向（不要用 `create_8_direction_object` 做人形角色）。
- **quadruped**：`body_type="quadruped"` 且 `template` 为 `bear|cat|dog|horse|lion`。

### `animate_character`

```text
# 模板（便宜，1 gen/方向）
animate_character(
  character_id="<uuid>",
  template_animation_id="walking",
  action_description="walking proudly"   # 可选
)

# 自定义 v3（无 template 时默认）
animate_character(
  character_id="<uuid>",
  action_description="casting a fire spell",
  mode="v3",
  frame_count=8
)
```

- **pro 自定义**：先 `confirm_cost=false` 看价，用户确认后再 `true`。
- 模板模式可在角色仍 processing 时排队；v3/pro 建议等 completed。

### `create_*_object` / review

- `create_1_direction_object`：多候选时 `status=review` → `get_object` → `select_object_frames` 或 `dismiss_review`。
- 人形角色请用 `create_character`，不要用 8 向 object 管线。

### `list_*`（Hub 特有）

- 只显示经本 MCP 创建的 id。
- 本地 `status` 可能仍为 `processing`，以 `get_*` 为准（v1 不强制 get 回写 list 状态）。

---

## 6. 错误与运维

| 现象 | 含义 | 处理 |
|------|------|------|
| `401` 无效 MCP API Key | token 错/吊销/过期/非 admin | 重签或检查 Bearer |
| `tool_not_allowed` | 非 v1 白名单 | 换官方 MCP 或等 Hub 扩白名单 |
| `no_provider_key` / `provider_keys_busy` / `provider_quota_exhausted` | 池无可用 key | 管理后台检查密钥与额度 |
| `resource_not_found` | sticky id 未登记 | 必须用本 Hub 创建的 id；勿手填他站 id |
| `upstream_error` | 官方侧失败 | 重试；查上游额度/限流 |
| `404` MCP session not found | session 无效、过期或属于另一把 key | 重新 initialize，不要复用旧 session |
| `406` Not Acceptable | 现代客户端未同时接受 JSON 与 SSE，或 GET 不接受 SSE | 使用标准 MCP SDK 请求头 |
| `415` Unsupported Media Type | POST 不是 `application/json` | 设置 `Content-Type: application/json` |
| GET `/mcp` 返回 `405` | 无 session 的现代独立 SSE GET 不受支持 | 先 POST initialize；旧 SSE 客户端改用 `/sse` |
| 连接成功但**工具数为 0** | 客户端为严格 schema 校验器（如 DSH），旧版本曾因 `inputSchema` 含 `minimum`/`format`/`type: [...,null]` 而整体回滚 | Hub 已内置清洗；若仍出现，请拉取最新后端版本并检查上游是否新增了未知关键字 |
| 工具同步**卡住/超时**后为 0 | 客户端连接成功、`tools/list` 长时间无响应 | 已通过默认关闭独立 GET SSE 流 + 列表缓存 + 10s 刷新上限修复（见 2.6）；若仍复现，检查 `MCP_UPSTREAM_URL` 是否可达、密钥池是否可用，并确认服务端日志出现 `mcp tool=tools/list` 记录 |
| `GET /mcp` 返回 `405` | 独立 SSE 流默认关闭（预期行为） | 无需处理：客户端会自动降级为纯 POST 模式 |
| POST 响应为 `application/json` 而非 SSE | JSON 响应模式为默认（预期行为，见 2.6） | 无需处理；如需旧行为设置服务端 `DisableJSONResponse` |
| `server/discover` 探测返回 `400` + JSON 错误 | 预期行为：Hub 是 2025-era 服务端（见 2.5.1） | 无需处理；客户端会继续 `initialize` 握手 |

环境变量：

| 变量 | 默认 |
|------|------|
| `MCP_ENABLED` | `true`（`false` 时 `/mcp` 404） |
| `MCP_UPSTREAM_URL` | `https://api.pixellab.ai/mcp` |

限流：IP + MCP key 约 120/min（可调）。

审计：`mcp_key.create` / `mcp_key.revoke`。

---

## 7. 安全注意

1. `plmcp_` 与官方 PixelLab token **同等敏感**，勿提交仓库。
2. 仅 admin 可签发与调用。
3. 吊销立即生效。
4. 下载 URL 含 UUID 即可访问（与官方一致），分享即授权。

---

## 8. 相关文档

- 设计：`docs/superpowers/specs/2026-07-24-mcp-hub-design.md`
- 实现计划：`docs/superpowers/plans/2026-07-24-mcp-hub.md`
- 生产运维：`docs/ops/production.md`
- 官方 MCP：https://api.pixellab.ai/mcp/docs
- 官方 REST v2：https://api.pixellab.ai/v2/llms.txt
