# AI / MCP 集成（检索、校验、实时操作页面）

## 1. 开启（后端配置）

内置 AI 问答（编辑器右下角）：在 `server/vp_config.json` 的 `ai` 段填写 `baseURL` / `apiKey` / `models` / `visionModels`（对应旧 `VJAGENT_LLM_*`），重启后端即可（种子化成固定名 `default` 的平台端点）；也可在 AI 设置里添加 BYO 端点。输入栏相机按钮会截当前大屏并打开标注编辑器（矩形 / 箭头 / 画笔），确认后作为图片附件发给多模态模型；点附件缩略图可查看大图或再编辑。

**模型解析优先级**：会话已定格 → 用户 BYO 端点 → 平台 `default` → 文件/环境变量兜底。配置文件的 `apiKey` 只属于 `default`，不会补给自建端点；自建端点漏填 Key 会得到 `unauthorized: You didn't provide an API key`，而两边常提供同名模型（都叫 `qwen3.6-plus`）看不出差别。AI 设置的端点列表会标「未设 Key」、只给真正生效的那条标「默认」，并可逐条删除；模型药丸显示的是本会话实际所用模型。会话模型首次发言即定格，换端点后要新建会话。

`server/vp_config.json`（或环境变量 `VP_MCP_ENABLED=true` 等）：

```json
{
  "mcp": {
    "enabled": true,
    "dataDir": "./vp_mcp_data",
    "cacheDir": "./pb_data/mcp-cache",
    "publicURL": "https://your-host",
    "localValidateRoots": [],
    "liveEnabled": true,
    "liveSecret": "<32+位稳定密钥>"
  }
}
```

- MCP 数据目录 `vp_mcp_data/` 不打包进二进制，与后端放在一起；更新数据后重启后端即可。
- `liveSecret` 决定工程 MCP 地址里的 token；不设则每次重启随机、旧地址失效。
- 端点：`POST/GET https://<host>/mcp`（Streamable HTTP）；`GET /llms.txt` 索引。
- 反代（nginx `proxy_pass` 到 `127.0.0.1` 且保留对外 Host）开箱可用：SDK 的 DNS 重绑定保护已关（否则 `/mcp` 全部 403 `invalid Host header`），Host 准入看 `allowedHosts`（留空=全放行）。`publicURL` 留空时对外地址按请求推导。

## 2. 静态工具（检索 / 文档 / 校验）

| Tool | 作用 |
|---|---|
| `search_widgets` / `get_widget_metadata` | 检索组件、取 options 元数据；搭建型组件返回 `childPieces` |
| `search_documentation` / `get_doc` | 检索文档章节；`get_doc` 传 `type` 取组件文档（`scene-3d` 等 builder 绑子系统总览）、传 `docId` 取文档页 |
| `list_examples` / `get_example` | 白名单示例 |
| `validate_project` | 校验工程 JSON（内联对象） |
| `validate_project_file` | 按服务器本地路径校验（需配 `localValidateRoots`，省 token：`{ "path": "D:/out/demo.vpproj.json" }`） |

与本技能的分工：查 API/文档/校验走 MCP；按规范拼整份 JSON 用本技能 references/（envelope/data/interactions）+ `scripts/validate-project-json.mjs` 自检。

## 3. Cursor 配置（静态工具）

```json
{ "mcpServers": { "vjappvisual": { "url": "http://127.0.0.1:8090/mcp" } } }
```

需 stdio 代理时用 `npx -y mcp-remote http://127.0.0.1:8090/mcp`。改数据/配置后**重启后端**再刷新 Cursor MCP 面板。

## 4. live 实时页面控制（AI 直接操作打开的工程）

编辑态：完整结构编辑（撤销/保存）；运行态：读取/交互/**临时**改属性（reload 还原，不写回工程）。

两种接入：

- **工程地址（配到 Cursor）**：编辑器右下角「AI」面板复制 `http://<host>/mcp/p/<projectId>?token=…` 写入 `mcp.json`。多标签同工程时 `page_sessions` → `page_attach(sessionId)`（仅一个时自动附着）。
- **本标签地址（应用内 AI 面板）**：`http://<host>/mcp/s/<token>` 精确指向当前标签。

工具面已收敛：同族合并为带 `action` 的单工具、单/批只留批量；会话端点不注册 `page_attach`，运行态会话不注册编辑工具。

| Tool 组 | 能力 |
|---|---|
| `page_sessions` / `page_attach` | 会话总览（含当前目标）/ 选定（仅工程端点） |
| `page_get_screens` / `page_list_elements` | 结构（摘要省 token）；`page_list_elements` 传 `query` 即模糊查找 |
| `page_get_elements` | 元素详情：`uids` 批量（≤50，`fields` 限定选项）；`uid`/`elementUID` 单个。两种读法**互斥**，同传会报错 |
| `page_get_schemas` | 可写属性清单（真实 path/类型/门控 enabled），按 type 去重 |
| `page_list_widget_types` / `page_screenshot` | 组件类型目录 / 页面截图（视觉核对） |
| `page_get_selection` / `page_get_data` / `page_get_params` | 读选中/数据/参数 |
| `page_interact` / `page_emit` | 交互（`action=select/highlight/click`）/ 事件 |
| `page_batch_set_options` | 改属性（编辑态持久+可撤销；运行态临时；单条=长度 1 的 `changes`）；返回以 `FAILED` 开头须先修正 |
| `page_set_data` / `page_set_params` / `page_set_active_screen` / `page_window` | 数据/参数/切页/画面（`action=open/close`） |
| `page_add_element` / `page_remove_element` / `page_move_element` / `page_history` / `page_save` | 结构编辑与撤销重做（`action=undo/redo`）；仅编辑态 |
| `page_export_tsx` / `page_apply_project` | VP-TSX 环路（仅编辑态）：`export_tsx({ path, screenUid? })` 把当前页/指定屏导出为 `.vp.tsx`；`apply_project({ path } | { tsx } | { envelope }, screenUid?)` 编译并 diff 应用——单屏按 uid 合并、结构/连接/屏级选项变化触发整屏替换。工作流见 [../page-live-loop.md](../page-live-loop.md) |

live 端点同时挂载知识子集（`search_documentation` / `get_doc` / `get_widget_metadata`）；示例与校验类工具只在独立 `/mcp` 生成端点。

**推荐工作流（schema-first）**：`page_list_elements` → `page_get_schemas(uids)` 取真实属性 path（绝不臆造）→ 一次 `page_batch_set_options`（检查 `failed`/`warnings`）→ `page_screenshot` 核对 → `page_save`。写属性的 path 权威 = live `page_get_schemas`（或生成 JSON 时的 `get_widget_metadata`）；`get_doc` 是人读向导，不可作为 path 依据。批量读优先 `page_get_elements(uids)`/`page_get_schemas(uids)`，不要逐 uid 串行单读。

### 容易踩的语义（照描述用会踩坑的地方）

- **只回已写入的选项**：`page_get_elements(uid)` 不返回仍是默认值的项（默认值没落库）。要默认值查 `page_get_schemas`。
- **`page_set_data` 是整表替换**，不是追加；空数组 = 清表。列以表结构为准，多余列会被丢掉。
- **`page_set_params` 只在顶层合并**：嵌套对象整段替换，`null` 是存 JSON null 而不是删键。
- **`page_window` 分模式**：运行态才真的开关浮层；编辑态 open 等于把编辑画布切过去、close 回 `applied:false`。切页面用 `page_set_active_screen`。
- **`query` 只搜当前屏**，是普通子串（不是正则），也不搜 uid。要搜别的页先切过去。
- **写不了的东西**：`interactions`、地图/三维/组态的 `pieces`、`data-repeater` 的 `unitTemplate`、选项卡/视图分页——这些是结构字段，只能改 VP-TSX 再 `page_apply_project`。`name`/`visible`/`locked` 是列表里的只读元数据，不是属性 path。
- **字体是整对象**：写 `text-font` 会覆盖整段，要把 `size` 一并带上，否则原有字号丢失。
- **新增页面不能靠单屏 apply**：单屏 envelope 按 uid 合并进现有工程，uid 必须已存在；要加页就导出整工程、在里面加好再整体 apply。
- **未知 uid 一律报错**：`page_emit` 的 `targetUid`、`page_interact` 的 `uid`、`page_set_active_screen` 的 `screenUid`、`page_list_elements` 的 `screenUid` 都不再静默兜底。`limit` 必须 ≥1，`limit=0` 是错误而不是「不限」。

## 5. 安全要点

- WS 注册强制鉴权：编辑态验登录+工程 owner；运行态验工程 token，且需页面浮层手动「允许 AI 交互」。
- 仅暴露公开文档与白名单示例；请求体/超时/并发/频率受限；命令按会话串行。
- `validate_project_file` 只读 `localValidateRoots` 白名单目录（防目录穿越）；公网部署慎开。
