# Wps Note

> 通过 MCP 工具读取、编辑和管理 WPS 笔记，基于 block 文档模型，所有内容以 XML 格式交换。当用户说"帮我看看笔记"、"搜索笔记"、"创建一篇笔记"、"编辑笔记内容"、 "整理标签"，或提到 WPS 笔记、WPS Note、云笔记时使用。也适用于排查 MCP 工具调用错误 （BLOCK_NOT_FOUND、EDITOR_NOT_READY 等）。

- Skill: `wpsnote/wps-note` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add wpsnote/wps-note`
- Raw SKILL.md: https://api.skillmd.com/api/skills/wpsnote/wps-note/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: wpsnote (https://skillmd.com/u/wpsnote)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/wpsnote/wps-note

---


# WPS Note MCP Skill

核心操作模式：**先定位（outline / search）→ 再读取（read）→ 最后编辑（write）**。所有内容以语义 XML 格式交换，所有定位基于 `block_id`（10 位字母数字）。

## 何时使用

- 用户提到 WPS 笔记、WPS Note、云笔记、金山笔记
- 用户要求读取、编辑、搜索、总结、翻译笔记内容
- 用户需要创建笔记、管理标签、整理笔记库
- 需要排查 MCP 工具调用失败（`BLOCK_NOT_FOUND`、`EDITOR_NOT_READY` 等错误）

**不适用于**：本地 Markdown 文件操作、WPS 文档/表格/演示等其他产品、纯概念讨论。

## 核心概念

### Block 模型

- 笔记由 block 组成，每个 block 有唯一的 `block_id`。
- block 类型：`paragraph`、`heading`、`blockquote`、`code_block`、`list`、`table`、`image`（单图/图片分栏）、`horizontal_rule`、`highlight_block`、`columns`、`embed`、`note_audio_card`。
- `embed` block（电子表格、视频、LaTeX、倒计时等）为**只读占位符**，不可编辑。
- `note_audio_card` block 为语音录音卡片（**只读**），在 XML 中显示为 `<NoteAudioCard/>`，使用 `get_audio_transcript` 工具获取转写内容。
- 表格必须**整表替换**，无法编辑单个单元格。表格单元格仅支持简单块（段落、标题、引用、代码块、分隔线、图片），**不可在单元格内嵌套高亮块、分栏、表格**等容器类块。
- **容器嵌套限制**：`<highlightBlock>` 内仅支持简单块（段落、标题、引用、代码块、分隔线、图片），**不可嵌套** `<highlightBlock>`、`<columns>`、`<table>`。`<columns>` 的每个 `<column>` 内支持简单块和 `<highlightBlock>`，**不可嵌套** `<table>` 或 `<columns>`。

### note_id

- 大部分工具需要 `note_id` 来指定操作的笔记。
- 通过 `list_notes` 或 `search_notes` 获取 `note_id`；若用户要操作当前打开的笔记，可直接调用 `get_current_note` 获取。
- `get_note_info` 可获取笔记元数据（含标签），支持三种模式：单个 `note_id` 查询、批量 `note_ids` 查询（最多 100 个）、全量分页浏览（`page`/`page_size`/`limit`），无需读取全文内容。

### XML 输入输出

- 所有内容以语义 XML 格式收发，使用标签如 `<p>`、`<h1>`-`<h6>`、`<blockquote>`、`<codeblock>`、`<table>`、`<highlightBlock>`、`<columns>` 等。
- 每个块级标签通过 `id` 属性标识 block_id，如 `<p id="aB3kLm9xZq">内容</p>`。
- **重要**：写入操作（`edit_block` 等）的 `block_id` / `anchor_id` 只接受顶层 block ID（由 `get_note_outline` 返回）。`read_note` XML 中容器（`<highlightBlock>`、`<columns>`、`<table>`）内部段落的 `id` 仅供阅读参考，不可用于写入操作。
- 写入时提供 XML 格式内容，系统自动转换为内部 block 模型。`replace` 的目标 block 由工具参数 `block_id` 指定，`content` 建议不写 `id`；若显式写了根块 `id`，必须与目标 `block_id` 一致。`insert` 时新内容中的块级标签应省略 `id`，系统自动分配新的 `block_id`。
- 行内 mark 使用语义标签：`<strong>粗体</strong>`、`<em>斜体</em>`、`<s>删除线</s>`、`<u>下划线</u>`、`<a href="url">链接</a>`。
- **标签管理**：使用 `manage_note_tags` 工具管理笔记标签——直接添加/移除文件级标签，无需编辑文档内容。创建笔记后应调用 `manage_note_tags({ note_id, add: ["标签名"] })` 分配标签；多级标签用 `/`，调用参数不要带 `#` 前缀；编辑已有笔记完成后若无标签也应补充。
- 行内自闭合元素：`<emoji value="😀"/>`（表情）、`<latex formula="E=mc^2"/>`（行内公式）、`<br/>`（硬换行）。
- 样式属性通过 `<span>` 传递：`<span fontColor="#C21C13">红色文字</span>`、`<span fontHighlightColor="#FBF5B3">高亮文字</span>`、`<span fontSize="16">大号文字</span>`。
- **颜色受预设色板约束**，任意 hex 色值会被编辑器静默丢弃。各类颜色预设值：
  - `fontColor`（12 色）：`#080F17` `#C21C13` `#DB7800` `#078654` `#0E52D4` `#0080A0` `#757575` `#DA326B` `#D1A300` `#58A401` `#116AF0` `#A639D7`
  - `fontHighlightColor`（9 色）：`#FBF5B3` `#F8D7B7` `#F7C7D3` `#DFF0C4` `#C6EADD` `#D9EEFB` `#D5DCF7` `#E6D6F0` `#E6E6E6`
  - `highlightBlock` 颜色（6 对 bg→border）：`#FAF1E6`→`#FEC794`、`#FAE6E6`→`#F2A7A7`、`#E6FAEB`→`#AFE3BB`、`#E6EEFA`→`#98C1FF`、`#F5EBFA`→`#E5B5FD`、`#EBEBEB`→`#C5C5C5`
  - `columnBackgroundColor`（42 色，含纯色和渐变）：
    - 6 基础色：`#FFF5EB` `#FFECEB` `#E8FCEF` `#EBF2FF` `#FAF0FF` `#F2F2F2`
    - 18 扩展纯色：`#FCFAE1` `#FEF6E7` `#FFF5ED` `#FFF2F0` `#FFF2F4` `#FFF0F7` `#EEFFEB` `#EBFFF5` `#E8FCFC` `#EBF8FF` `#EBF1FF` `#F0EDFF` `#F2E7E4` `#F0E6DA` `#F5EEDA` `#EDF0EB` `#EDEEF0` `#F0E4DD`
    - 6 饱和色：`#FEF49C` `#BCFAAF` `#ADF4FF` `#C2D3FF` `#FFC7C7` `#E0E0E0`
    - 12 渐变色：使用 CSS `linear-gradient()` 语法，如 `linear-gradient(133deg,#FFFAF7 2.14%,#FFEDFE 96.88%)`
- 完整 XML 格式参考已集成到 MCP Server Instructions 中自动注入（视客户端支持情况）。未注入时可调用 `get_xml_reference` 工具按需获取。

### 只读 Token

- 部分笔记 token 为只读，写入工具会返回 `DOCUMENT_READ_ONLY`。
- 此时 `retryable: false`——不要重试，应告知用户。读取操作不受影响。

### 响应格式

所有工具返回统一的标准信封：

```json
{ "ok": true, "code": "OK", "message": "...", "retryable": false, "data": { ... }, "hints": [] }
```

- `ok`——调用是否成功。
- `code`——机器可读状态码（参见错误恢复）。
- `retryable`——为 `true` 时可直接重试。
- `hints`——建议的后续工具或操作。

## 工作流

### 1. 读取与理解笔记

```
get_current_note()                            → 获取 note_id + word_count + size_category（决定后续策略）
list_notes / search_notes                     → 按条件查找 note_id
get_note_outline(note_id)                     → 查看结构和 block ID（超大文档自动分页）
get_cursor_block()                           → 获取当前光标所在顶层 block_id
read_section(note_id, heading_id)             → 读取某个章节
read_blocks(note_id, block_ids)               → 批量读取指定 block
read_blocks(note_id, block_id, before, after) → 读取单个 block 及上下文
search_note_content(note_id, q)               → 在笔记内搜索文本
read_image(note_id, block_id)                 → 读取图片 block 的实际内容（base64）
get_audio_transcript(shorthand_id)            → 获取语音录音的转写内容
get_xml_reference()                           → 获取 XML 格式完整参考文档
```

**对于长笔记**，优先使用 `get_note_outline` → `read_section`，而非 `read_note`，以减少 token 开销。

### 分页读取大文档

当笔记超过 200 blocks 时，**read_note** 和 **get_note_outline** 的返回会自动分页——需通过 `offset` 和 `block_limit` 参数切换分页查看不同部分。笔记本身无 block 数量上限，这些参数仅控制单次读取返回量。

`read_note` 示例：

```
read_note({ note_id })
→ pagination: { total_blocks: 350, has_more: true, next_offset: 100 }

read_note({ note_id, offset: 100 })
→ pagination: { has_more: true, next_offset: 200 }

read_note({ note_id, offset: 200 })
→ pagination: { has_more: false }  ← 读完
```

也可手动控制每页大小：`read_note({ note_id, offset: 0, block_limit: 50 })`

`get_note_outline` 同理——`has_more: true` 时用 `next_offset` 续读：`get_note_outline({ note_id, offset: next_offset })`

`read_section` 同理——截断时返回 `next_block_offset`，传入 `block_offset` 续读：

```
read_section({ note_id, heading_block_id, max_blocks: 50 })
→ truncated: true, next_block_offset: 50

read_section({ note_id, heading_block_id, block_offset: 50 })
→ truncated: false  ← 读完
```

### 文档大小策略

`get_current_note` 和 `get_note_outline` 都返回 `word_count`、`size_category` 和 `estimated_xml_chars`，据此选择读取策略：

| size_category | 字数范围 | 策略 |
|--------------|---------|------|
| `small` | <5K 字 | `read_note` 直接读取全文 |
| `medium` | 5K-20K 字 | `get_note_outline` → `read_section` 按章节读取 |
| `large` | 20K-80K 字 | `search_note_content` 精准定位 → `read_blocks` 读取目标 + 上下文 → 编辑 |
| `very_large` | >80K 字 | 同 large，精准定位优先 `search_note_content`；`get_note_outline` 支持分页读取（offset/block_limit）可按需获取结构 |

**原则**：文档越大，越应该用搜索定位而非顺序读取。对于 large/very_large 文档，优先使用 search_note_content 找到目标 block，再用 read_blocks 读取其上下文。**操作当前笔记时，先调 `get_current_note` 获取 `size_category`，再决定路径**。

### 2. 编辑笔记内容

```
get_note_outline(note_id)                      → 获取最新 block ID
get_cursor_block()                            → 当前围绕光标位置编辑时，获取锚点 block
read_blocks(note_id, [target_id])              → 确认当前内容
insert_image(note_id, anchor_id, pos, src)     → 插入图片（独立工具，不走 XML，当前需联网）
generate_image(prompt, width?, height?)        → AI 文生图，返回图片 URL（配合 insert_image 插入笔记）
edit_block(note_id, op, ...)                   → 单个编辑操作（替换、插入、删除、更新属性、移动）
batch_edit(note_id, operations)                → 多个操作合并为一次原子事务
```

**关键**：编辑后 block ID 可能变化。连续 insert 可直接使用返回的 `last_block_id` 做锚点；操作其他 block 前通过 `get_note_outline` 刷新。

**参数约束**：`edit_block` / `batch_edit` 只传当前 `op` 需要的字段。`replace` / `insert` 的 `content` 必须直接填写完整 XML 字符串，不能传纯文本、Markdown，或 `"把第二段改成……"` 这类自然语言编辑指令。

**连续插入的正确做法**（避免乱序）：
- **优先**：在一次 `insert` 中将所有内容拼成完整 XML（如 `"<h2>A</h2><p>...</p><h2>B</h2><p>...</p>"`），按 XML 顺序依次插入，无需分多次调用。
- **链式**：如果必须分多次 `insert`，每次使用上一次返回的 `last_block_id` 作为下一次的 `anchor_id`，或在每次写入后调用 `get_note_outline` 刷新 ID。

### 3. 批量编辑（原子事务）

单个操作使用 `edit_block`，多个操作使用 `batch_edit` 合并为原子事务：

```
# 单个操作
edit_block(note_id, op: "replace", block_id: "id1", content: "<p>...</p>")
edit_block(note_id, op: "insert", anchor_id: "id2", position: "after", content: "<h2>...</h2><p>...</p>")

# 多个操作（原子事务）
batch_edit(note_id, operations: [
  { op: "delete",       block_ids: ["id1"] },
  { op: "replace",      block_id: "id2", content: "<p>...</p>" },
  { op: "update_attrs", block_id: "id3", attrs: { level: 2 } },
  { op: "move",         block_id: "id5", anchor_id: "id2", position: "after" },
  { op: "insert",       anchor_id: "id4", position: "after", content: "<h2>...</h2><p>...</p>" }
])
```

**执行顺序固定**（`batch_edit`）：delete → replace → update_attrs → move → insert（与数组顺序无关）。

### 4. 管理笔记与标签

```
get_note_info(note_id)                      → 获取单个笔记元数据（含标签）
get_note_info(note_ids: [...])              → 批量获取多个笔记元数据（最多 100 个）
get_note_info(page, page_size)              → 分页浏览所有笔记元数据
search_notes(keyword, tags, since, before)  → 搜索笔记（也可按标签筛选笔记）
create_note(title)                          → 创建空白笔记（需用 edit_block 添加内容）
import_web_page(url)                       → 从白名单域名导入网页为笔记（微信公众号、知乎、豆瓣等）
delete_note(note_id)                        → 不可恢复——必须与用户确认
sync_note(note_id)                          → 触发同步
find_tags()                                 → 列出所有标签
find_tags(keyword)                          → 搜索标签
manage_note_tags(note_id, add, remove)      → 添加/移除笔记的文件级标签
get_note_stats(detailed)                    → 使用统计
```

## 工具速查表

### 读取/定位工具

| 工具 | 用途 | 关键参数 |
|------|------|----------|
| `get_note_outline` | 获取结构大纲（含 title、word_count、block_count、size_category、estimated_xml_chars、blocks 列表、pagination）——获取 block_id 和文档规模的主要方式，超大文档自动分页 | `note_id`、`max_depth?`、`include_preview?`、`offset?`、`block_limit?` |
| `get_cursor_block` | 获取当前光标所在顶层 block 的 `block_id`、`block_type` 和 `note_id`；光标位于高亮块或分栏内部时不支持 | *（无参数）* |
| `read_blocks` | 按 ID 批量读取 block 的 XML 内容和属性，或读取单个 block 及前后上下文 | `note_id`、`block_ids`（批量）或 `block_id`+`before?`+`after?`（上下文） |
| `read_note` | 读取全文或分页读取（XML），超大文档自动分页并返回 pagination（含 has_more + next_offset） | `note_id`、`max_length?`、`offset?`、`block_limit?` |
| `search_note_content` | 在笔记内搜索文本，返回匹配 block 的 block_id/type/preview，用于编辑前定位 | `note_id`、`query`、`max_results?` |
| `read_section` | 按标题读取完整章节（到下一同级标题），截断时返回 next_block_offset 用于续读 | `note_id`、`heading_block_id`、`max_blocks?`、`block_offset?` |
| `read_image` | 读取图片 block 的实际内容（base64），可供视觉理解 | `note_id`、`block_id` |
| `get_audio_transcript` | 获取语音录音（NoteAudioCard）的转写文本，返回句子列表（含说话人、时间戳） | `shorthand_id`（从 outline 的 `note_audio_card` block attrs 获取） |
| `get_xml_reference` | 获取 XML 格式完整参考文档（所有标签、属性、写入示例） | *（无参数）* |

**示例：**

```
get_note_outline({ note_id: "abc123" })
get_note_outline({ note_id: "abc123", offset: 100 })  // 分页续读
get_cursor_block()
read_blocks({ note_id: "abc123", block_ids: ["aB3kLm9xZq", "xY7nPq2wRt"] })
read_blocks({ note_id: "abc123", block_id: "xY7nPq2wRt", before: 2, after: 3 })
read_note({ note_id: "abc123" })
read_note({ note_id: "abc123", offset: 100 })  // 分页续读
read_note({ note_id: "abc123", offset: 0, block_limit: 50 })  // 手动控制每页大小
search_note_content({ note_id: "abc123", query: "季度总结" })
read_section({ note_id: "abc123", heading_block_id: "aB3kLm9xZq" })
read_section({ note_id: "abc123", heading_block_id: "aB3kLm9xZq", block_offset: 50 })  // 续读截断章节
read_image({ note_id: "abc123", block_id: "imgBlock01" })
get_audio_transcript({ shorthand_id: "sh_abc123" })
get_xml_reference()
```

### 写入工具

| 工具 | 用途 | 关键参数 |
|------|------|----------|
| `edit_block` | 单个编辑操作（推荐入口），编辑后 block_id 可能变化需重新获取。insert 操作返回 `new_block_ids` 和 `last_block_id`。move 操作移动后 block_id 不变 | `note_id`、`op`（replace/insert/delete/update_attrs/move）、`block_id`/`anchor_id`/`block_ids`、`content`/`attrs` |
| `batch_edit` | 多个操作的原子事务（全部成功或全部回滚），执行顺序固定。返回 `new_block_ids` 和 `last_block_id` | `note_id`、`operations[]` |
| `insert_image` | 插入图片（图片不可通过 XML 创建，必须用此工具），当前走在线上传，返回 block_id 和尺寸 | `note_id`、`anchor_id`、`position`、`src`、`alt?` |
| `generate_image` | AI 文生图，返回图片 URL（配合 `insert_image` 插入笔记）。每用户每分钟限 1 次，耗时 30-120 秒 | `prompt`、`width?`（默认 2688）、`height?`（默认 1536） |
| `import_web_page` | 从白名单域名（微信公众号、知乎、豆瓣等）导入网页内容为笔记，返回笔记 ID、标题和摘要。耗时 5-30 秒 | `url` |

**`edit_block` / `batch_edit` 操作类型：**

| `op` | 必填字段 |
|------|----------|
| `"replace"` | `block_id`、`content` |
| `"insert"` | `anchor_id`、`position`（"before"/"after"）、`content` |
| `"delete"` | `block_ids` |
| `"update_attrs"` | `block_id`、`attrs` |
| `"move"` | `block_id`、`anchor_id`、`position`（"before"/"after"） |

`content` 仅在 `replace` / `insert` 中出现，且必须是完整 XML 字符串，不是自然语言、纯文本或 Markdown。

**文件引用参数**（CLI 和脚本场景）：当 `content` 或 `operations` 内容过大导致命令行截断时，可使用文件引用替代——`content_file`（传入文件路径，Electron 读取后填入 `content`）、`operations_file`（同理填入 `operations`）、`__args_file`（从 JSON 文件读取全部参数）。已有显式值时文件引用被忽略。详见 `references/CLI_REFERENCE.md`。

**示例：**

```
// 单个操作 — edit_block
edit_block({ note_id: "abc123", op: "replace", block_id: "xY7nPq2wRt", content: "<p>更新后的段落文本</p>" })
edit_block({ note_id: "abc123", op: "insert", anchor_id: "aB3kLm9xZq", position: "after", content: "<h2>新章节</h2><p>这里是新内容。</p>" })
edit_block({ note_id: "abc123", op: "delete", block_ids: ["xY7nPq2wRt", "kL5mNp8vBc"] })
edit_block({ note_id: "abc123", op: "update_attrs", block_id: "aB3kLm9xZq", attrs: { level: 2 } })
edit_block({ note_id: "abc123", op: "move", block_id: "imgBlock01", anchor_id: "aB3kLm9xZq", position: "after" })

// 多个操作（原子事务） — batch_edit
batch_edit({ note_id: "abc123", operations: [
  { op: "replace", block_id: "xY7nPq2wRt", content: "<p>更新后的段落文本</p>" },
  { op: "insert", anchor_id: "aB3kLm9xZq", position: "after", content: "<h2>新章节</h2>" }
]})

// 图片
insert_image({ note_id: "abc123", anchor_id: "aB3kLm9xZq", position: "after", src: "https://example.com/photo.png", alt: "示意图" })

// AI 文生图 → 插入笔记
generate_image({ prompt: "一只橘猫坐在窗台上，水彩画风格，暖色调" })
→ { image_url: "https://...", task_id: "...", width: 2688, height: 1536 }
// 然后用 insert_image 将返回的 image_url 插入笔记

// 导入网页为笔记
import_web_page({ url: "https://mp.weixin.qq.com/s/xxx" })
→ { fileId: "123456", title: "文章标题", intro: "摘要...", linkUrl: "https://xxx/note/123456" }
```

### 隐藏写入工具

以下工具仍可用但默认不展示，适用于需要单独调用的场景：

| 工具 | 用途 | 关键参数 |
|------|------|----------|
| `replace_block` | 替换单个 block 内容 | `note_id`、`block_id`、`content` |
| `insert_block` | 在指定位置前后插入 | `note_id`、`anchor_id`、`position`、`content` |
| `delete_blocks` | 删除 block | `note_id`、`block_ids` |
| `update_block_attrs` | 修改 block 属性 | `note_id`、`block_id`、`attrs` |

### 管理工具

| 工具 | 用途 | 关键参数 |
|------|------|----------|
| `list_notes` | 列出笔记，支持排序和分页，返回 { notes, meta } | `limit?`、`sort?`、`direction?`、`since?`、`before?`、`page?`、`page_size?`、`starred?` |
| `create_note` | 创建空白笔记（仅含空段落），返回 { fileId, title } | `title?` |
| `get_note_info` | 获取笔记元数据（不读全文），返回 title、时间、intro、tags、starred | `note_id`（单个）、`note_ids`（批量）、`limit?`、`page`+`page_size`（分页） |
| `get_current_note` | 获取当前笔记 ID、元数据和文档统计（含 word_count、size_category） | *（无参数）* |
| `get_cursor_block` | 获取当前光标所在 block 的 `block_id`、`block_type` 和 `note_id`；位于高亮块/分栏内部时不支持 | *（无参数）* |
| `search_notes` | 搜索笔记（关键词 + 标签交集 + 时间范围），也可仅传 tags 按标签浏览 | `keyword?`、`tags?`、`since?`、`before?`、`sort?`、`direction?`、`limit?`、`starred?` |
| `find_tags` | 列出或搜索标签，返回 { id, name } 数组 | `keyword?`（不传列出全部，传入则搜索） |
| `manage_note_tags` | 管理笔记文件级标签（添加/移除） | `note_id`、`add?`（标签名数组）、`remove?`（标签名或 ID 数组） |
| `sync_note` | 触发笔记与云端同步 | `note_id` |
| `delete_note` | 永久删除笔记（不可恢复），须先与用户确认 | `note_id` 或 `note_ids`（批量，最多 100） |
| `get_note_stats` | 使用统计（总笔记数、标签数、分布等） | `detailed?` |
| `get_mcp_logs` | MCP 调用日志，用于诊断 `INTERNAL_ERROR` | `limit?` |

**示例：**

```
list_notes({ sort: "update_time", direction: "desc", limit: 10 })
list_notes({ starred: true })                  // 仅列出已收藏笔记
search_notes({ keyword: "项目方案", tags: ["工作"], since: "2025-01-01T00:00:00Z" })
search_notes({ tags: ["工作"] })  // 按标签筛选笔记（替代原 list_tag_notes）
search_notes({ starred: true })  // 仅在已收藏笔记中搜索
create_note({ title: "会议记录 2025-03-03" })
get_current_note()
get_cursor_block()
get_note_info({ note_id: "abc123" })              // 单个笔记元数据（含标签）
get_note_info({ note_ids: ["abc123", "def456"] }) // 批量查询
get_note_info({ page: 1, page_size: 20 })         // 分页浏览所有笔记
get_note_info({ limit: 5 })                       // 仅获取前 5 条
find_tags()                       // 列出所有标签
find_tags({ keyword: "工作" })    // 搜索标签
manage_note_tags({ note_id: "abc123", add: ["工作", "项目"] })   // 为笔记添加标签
manage_note_tags({ note_id: "abc123", remove: ["草稿"] })        // 移除笔记标签
manage_note_tags({ note_id: "abc123" })                          // 查看当前标签
delete_note({ note_id: "abc123" })  // 不可恢复——必须先与用户确认！
get_mcp_logs({ limit: 20 })       // 查看最近的工具调用日志
```

## Block 类型与 XML 标签

| Block 类型 | XML 标签 | 说明 |
|-----------|----------|------|
| `paragraph` | `<p>` | 支持行内 mark（`<strong>`、`<em>`、`<s>`、`<u>`、`<a>`）及行内节点（`<emoji/>`、`<latex/>`、`<br/>`） |
| `heading` | `<h1>`-`<h6>` | 级别由标签名或 `attrs.level` 控制 |
| `blockquote` | `<blockquote>` | 支持行内 mark 和 `<br/>` 换行 |
| `code_block` | `<codeblock lang="...">` | 纯文本内容；语言通过 `lang` 属性指定 |
| `list` | `<p listType="bullet\|ordered\|todo">` | 通过 `listType`、`listLevel`、`checked` 属性控制；有序列表额外支持 `listId` 和 `listValue` |
| `table` | `<table>` | `<tr>` → `<td>` 结构；必须整表替换。单元格仅支持简单块（`<p>`、`<h1>`-`<h6>`、`<blockquote>`、`<codeblock>`、`<hr/>`、`<img/>`），不可嵌套 `<highlightBlock>`、`<columns>`、`<table>` 等容器 |
| `highlight_block` | `<highlightBlock>` | 高亮块，仅支持简单块（`<p>`、`<h1>`-`<h6>`、`<blockquote>`、`<codeblock>`、`<hr/>`、`<img/>`），不可嵌套 `<highlightBlock>`、`<columns>`、`<table>` 等容器 |
| `columns` | `<columns>` → `<column>` | 分栏布局，每个 column 支持简单块和 `<highlightBlock>`，不可嵌套 `<table>` 或 `<columns>` |
| `image` | `<img/>` | **只读**——不可通过 XML 创建或修改，使用 `insert_image` 工具插入单张图片 |
| `image_column` | `<imageColumn>` | **只读**——图片分栏展示，不可通过 XML 创建 |
| `horizontal_rule` | `<hr/>` | 分隔线 |
| `embed` | `<embed type="..."/>` | **只读**——不可编辑或替换 |
| `note_audio_card` | `<NoteAudioCard/>` | **只读**——语音录音卡片，使用 `get_audio_transcript` 获取转写内容 |

### `update_block_attrs` 支持的属性

| Block 类型 | 属性 | 可选值 |
|-----------|------|--------|
| `heading` | `level` | 1–6 |
| `heading` | `textAlign` | `"left"`、`"center"`、`"right"` |
| `paragraph` | `textAlign` | `"left"`、`"center"`、`"right"` |
| `code_block` | `language` | 语言标识字符串 |
| `todo_list` 子项 | `checked` | `true` / `false` |

## Troubleshooting

### block ID 失效（`BLOCK_NOT_FOUND`）

**现象**：写入操作报 `BLOCK_NOT_FOUND`，即使之前刚读取过该 block。
**原因**：编辑操作会导致 block ID 变化，缓存的 ID 已过期。
**解决**：写入前总是先 `get_note_outline` 或 `search_note_content` 刷新 ID；或使用上次 insert 返回的 `last_block_id` 作为下次 `anchor_id`。

### 表格编辑失败

**现象**：尝试编辑表格单元格时报错或内容丢失。
**原因**：表格只支持整表替换，不能编辑单个单元格。
**解决**：用 `edit_block` 的 `replace` 操作传入完整的 XML 表格（含表头分隔行）。

### embed block 不可写

**现象**：对 embed block 调用 `edit_block` 的 `replace` 操作返回错误。
**原因**：电子表格、视频、音频、LaTeX 等嵌入内容为只读占位符。
**解决**：跳过 embed block，仅操作其他可编辑 block 类型。

### 笔记只读（`DOCUMENT_READ_ONLY`）

**现象**：所有写入操作均返回 `DOCUMENT_READ_ONLY`。
**原因**：当前笔记 token 为只读权限，不可重试。
**解决**：告知用户此笔记为只读。读取操作仍正常。

### 编辑器未就绪（`EDITOR_NOT_READY`）

**现象**：操作返回 `EDITOR_NOT_READY`。
**原因**：笔记编辑器仍在初始化，通常 1-2 秒可恢复。
**解决**：等待片刻后重试。若 3 次仍失败，请用户检查笔记应用。

### WebSocket 未连接（`WEBSOCKET_NOT_CONNECTED`）

**现象**：`get_audio_transcript` 返回 `WEBSOCKET_NOT_CONNECTED`。
**原因**：网络断开或 WebSocket 连接中断。
**解决**：检查网络连接，等待 WebSocket 自动重连后重试。

### 其他注意事项

- **图片必须使用 `insert_image` 工具**：图片不可通过 XML 创建（`<img/>` 标签为只读）。`insert_image` 当前走在线上传，调用时需联网。支持 HTTP/HTTPS URL 和 base64 data URI。**本地文件路径不支持直接传入，需先读取并转为 base64 data URI**。**URL 必须直接指向图片资源（返回 image/* 内容类型），不可为 HTML 页面链接**。若 URL 返回 404、离线或非图片内容，将报 `IMAGE_FETCH_FAILED` 错误。`edit_block` 的 replace/insert/update_attrs 不支持图片（但 **move 可以移动图片 block**）。
- **`generate_image` 为 AI 文生图**：返回图片 URL，而不是直接插入笔记。需配合 `insert_image` 使用。每用户每分钟限 1 次，生成耗时约 30-120 秒。
- **`import_web_page` 仅支持白名单域名**：微信公众号、知乎、豆瓣等白名单域名可导入，非白名单域名会返回 `INVALID_PARAMS`。转换耗时约 5-30 秒。导入完成后自动创建新笔记并跳转。
- **`batch_edit` 执行顺序固定**：delete → replace → update_attrs → move → insert，与数组顺序无关。
- **`move` 操作支持所有 block 类型**：包括图片（`<img/>`）、嵌入内容（`<embed/>`）等无法通过 XML 创建的只读标签。移动后 block_id 保持不变，无需刷新 outline。适用于调整内容顺序、重新组织文档结构。
- **`create_note` 创建空白笔记**：不支持初始内容，需用 `edit_block` 填充。
- **`read_section` 仅限标题**：`heading_block_id` 必须指向 `heading` block，否则报 `INVALID_BLOCK_TYPE`。
- **`get_cursor_block` 仅支持顶层 block**：光标位于高亮块（subdoc）或分栏（columns）内部时，会返回 `UNSUPPORTED_POSITION`。
- **`delete_note` 不可恢复**：必须先与用户确认。
- 当响应包含 `hints` 时，**遵循其建议**——它们指明了最快的恢复路径。

### 错误码速查

| 错误码 | 可重试 | 恢复方法 |
|--------|--------|----------|
| `INVALID_PARAMS` | 否 | 按 inputSchema 修正参数 |
| `EDITOR_NOT_READY` | 是 | 等待后重试 |
| `NO_ACTIVE_EDITOR_WINDOW` | 是 | 请用户打开笔记窗口 |
| `BLOCK_NOT_FOUND` | 否 | 刷新大纲获取有效 ID |
| `INVALID_BLOCK_TYPE` | 否 | 检查 block 类型 |
| `INVALID_CONTENT` | 否 | 修正内容格式 |
| `DOCUMENT_READ_ONLY` | 否 | 告知用户 |
| `FRONTEND_TIMEOUT` | 是 | 缩小范围或重试 |
| `IMAGE_FETCH_FAILED` | 是 | 检查图片 URL 是否直接指向图片资源，修正后重试 |
| `GENERATE_IMAGE_FAILED` | 否 | 修正 prompt 或检查白名单/登录状态 |
| `RATE_LIMITED` | 是 | 等待 60 秒后重试 |
| `WEBSOCKET_NOT_CONNECTED` | 是 | 检查网络，等待 WebSocket 重连后重试 |
| `INTERNAL_ERROR` | 是 | 重试；查 `get_mcp_logs`（隐藏工具） |

完整错误详情参见 `references/ERROR_CODES.md`。

## 常用编排模式

最高频的多工具组合，无需查阅完整用例手册即可使用。

**模式 1：搜索定位 → 分段读取**（长文档优先）
```
search_notes({ keyword }) → note_id
get_note_outline({ note_id }) → heading block ID
read_section({ note_id, heading_block_id }) → 按需读取章节
```

**模式 2：笔记内批量搜索替换**
```
search_note_content({ note_id, query }) → 匹配的 block_id 列表
read_blocks({ note_id, block_ids }) → 读取完整内容
batch_edit({ note_id, operations: [{ op: "replace", ... }, ...] }) → 原子替换
```

**模式 3：创建 → 填充模板**
```
create_note({ title }) → note_id（空白笔记）
manage_note_tags({ note_id, add: ["标签名"] }) → 分配标签
get_note_outline({ note_id }) → 获取空 block ID
batch_edit({ note_id, operations: [
  { op: "replace", block_id, content: "<h1>标题</h1>" },
  { op: "insert", anchor_id, position: "after", content: "<p>模板内容...</p>" }
]})
```

**模式 4：连续追加多段内容**（避免乱序）
```
# 方式 A（推荐）：单次 insert 拼接完整 XML
edit_block({ note_id, op: "insert", anchor_id: "last_id", position: "after",
  content: "<h2>第一部分</h2><p>内容 A</p><h2>第二部分</h2><p>内容 B</p>" })

# 方式 B：链式 insert，用上次返回的 last_block_id 做锚点
edit_block({ ..., anchor_id: "id1", position: "after", content: "<h2>第一部分</h2><p>内容 A</p>" })
→ { last_block_id: "new_id_1" }
edit_block({ ..., anchor_id: "new_id_1", position: "after", content: "<h2>第二部分</h2><p>内容 B</p>" })
→ { last_block_id: "new_id_2" }
```

**模式 5：移动 block（图片、嵌入内容等）**
```
get_note_outline({ note_id }) → 找到源 block 和目标位置
edit_block({ note_id, op: "move", block_id: "img_id", anchor_id: "target_heading", position: "after" })
→ block_id 不变，无需刷新 outline
```

**模式 6：管理笔记标签**
```
find_tags() → 查看已有标签名
manage_note_tags({ note_id, add: ["工作", "项目"] }) → 添加标签
manage_note_tags({ note_id, remove: ["草稿"] }) → 移除标签
manage_note_tags({ note_id }) → 查看当前标签
```

**模式 7：分页读取超大文档**（无标题结构时优先，分页仅控制读取量，笔记无 block 上限）
```
read_note({ note_id })
→ pagination: { total_blocks: 350, has_more: true, next_offset: 100 }

read_note({ note_id, offset: 100 })
→ pagination: { has_more: true, next_offset: 200 }

read_note({ note_id, offset: 200 })
→ pagination: { has_more: false }  ← 读完
```

更多组合模式和端到端场景参见 `references/USE_CASES.md`。

## 完整示例

**用户说**："帮我找到 Q1 报告，更新摘要部分，加一个结论"

**步骤 1**：定位笔记
```
search_notes({ keyword: "Q1 报告" })
→ data.notes[0].note_id = "note_xyz"
```

**步骤 2**：读取结构
```
get_note_outline({ note_id: "note_xyz" })
→ blocks: [
    { id: "h1abc", type: "heading", preview: "Q1 报告" },
    { id: "p1def", type: "paragraph", preview: "执行摘要..." },
    { id: "h2ghi", type: "heading", preview: "营收" },
    { id: "h2jkl", type: "heading", preview: "下一步计划" },
    { id: "p3mno", type: "paragraph", preview: "继续监控..." }
  ]
```

**步骤 3**：确认当前内容
```
read_blocks({ note_id: "note_xyz", block_ids: ["p1def"] })
→ "本季度各部门..."
```

**步骤 4**：原子编辑（替换摘要 + 插入结论）
```
batch_edit({ note_id: "note_xyz", operations: [
  { op: "replace", block_id: "p1def",
    content: "<p>本报告涵盖 2025 年 Q1 各部门业绩表现。营收超出目标 15%。</p>" },
  { op: "insert", anchor_id: "p3mno", position: "after",
    content: "<h2>总结</h2><p>2025 年 Q1 是强劲的一个季度，所有关键指标均实现显著增长。</p>" }
]})
→ ok: true, last_block_id: "new_conclusion_id"
```

> 提示：`batch_edit` / `edit_block` 的 insert 操作中，content 可包含多个块级 XML 元素（如上例的 `<h2>` + `<p>`），它们按 XML 顺序插入，无需分多次调用。返回的 `last_block_id` 可直接用于后续操作。

**步骤 5**：验证结果
```
read_section({ note_id: "note_xyz", heading_block_id: "h1abc", max_blocks: 5 })
→ 确认摘要已更新、结论已添加
```

**结果**：笔记摘要已替换为新内容，文末新增"总结"章节。

## 参考文档

- [API 参考](references/API_REFERENCE.md)——全部工具的完整 inputSchema
- [CLI 参考](references/CLI_REFERENCE.md)——`wpsnote-cli` 的 canonical 命令面、CLI 特有行为和 JSON 输出格式
- [错误码](references/ERROR_CODES.md)——详细错误码、hints 系统和恢复模式
- [用例手册](references/USE_CASES.md)——按复杂度递进的完整用例集（含端到端工作流示例）、Prompt 模板和异常处理速查

