wps-note

Original🇨🇳 Chinese
Translated

Read, edit, and manage WPS Notes via MCP tools, based on the block document model, with all content exchanged in XML format. Use this when users say "help me check my notes", "search notes", "create a note", "edit note content", "organize tags", or mention WPS Notes, WPS Note, or cloud notes. It also applies to troubleshooting MCP tool call errors (such as BLOCK_NOT_FOUND, EDITOR_NOT_READY).

5installs
Added on

NPX Install

npx skill4agent add wpsnote/wpsnote-skills wps-note

Tags

Translated version includes tags in frontmatter

SKILL.md Content (Chinese)

View Translation Comparison →

WPS Note MCP Skill

Core Operation Mode: Locate first (outline / search) → Read next → Edit last. All content is exchanged in semantic XML format, and all positioning is based on
block_id
(10 alphanumeric characters).

When to Use

  • Users mention WPS Notes, WPS Note, cloud notes, or Kingsoft Notes
  • Users request to read, edit, search, summarize, or translate note content
  • Users need to create notes, manage tags, or organize note libraries
  • Need to troubleshoot MCP tool call failures (errors like
    BLOCK_NOT_FOUND
    ,
    EDITOR_NOT_READY
    )
Not applicable to: Local Markdown file operations, other WPS products like documents/spreadsheets/presentations, or pure conceptual discussions.

Core Concepts

Block Model

  • Notes are composed of blocks, each with a unique
    block_id
    .
  • Block types:
    paragraph
    ,
    heading
    ,
    blockquote
    ,
    code_block
    ,
    list
    ,
    table
    ,
    image
    (single image/image columns),
    horizontal_rule
    ,
    highlight_block
    ,
    columns
    ,
    embed
    ,
    note_audio_card
    .
  • embed
    blocks (spreadsheets, videos, LaTeX, countdowns, etc.) are read-only placeholders and cannot be edited.
  • note_audio_card
    blocks are voice recording cards (read-only), displayed as
    <NoteAudioCard/>
    in XML. Use the
    get_audio_transcript
    tool to obtain transcribed content.
  • Tables must be replaced in full; individual cells cannot be edited. Table cells only support simple blocks (paragraphs, headings, blockquotes, code blocks, horizontal rules, images), and cannot contain nested container blocks like highlight blocks, columns, or tables.
  • Container Nesting Restrictions:
    <highlightBlock>
    only supports simple blocks (paragraphs, headings, blockquotes, code blocks, horizontal rules, images), and cannot nest
    <highlightBlock>
    ,
    <columns>
    , or
    <table>
    . Each
    <column>
    within
    <columns>
    supports simple blocks and
    <highlightBlock>
    , but cannot nest
    <table>
    or
    <columns>
    .

note_id

  • Most tools require
    note_id
    to specify the note to operate on.
  • Obtain
    note_id
    via
    list_notes
    or
    search_notes
    ; if users want to operate on the currently open note, directly call
    get_current_note
    to retrieve it.
  • get_note_info
    can retrieve note metadata (including tags), supporting three modes: single
    note_id
    query, batch
    note_ids
    query (up to 100 notes), and full pagination browsing (
    page
    /
    page_size
    /
    limit
    ), without needing to read the full content.

XML Input/Output

  • All content is sent and received in semantic XML format, using tags such as
    <p>
    ,
    <h1>
    -
    <h6>
    ,
    <blockquote>
    ,
    <codeblock>
    ,
    <table>
    ,
    <highlightBlock>
    ,
    <columns>
    , etc.
  • Each block-level tag identifies the block_id via the
    id
    attribute, e.g.,
    <p id="aB3kLm9xZq">Content</p>
    .
  • Important: The
    block_id
    /
    anchor_id
    for write operations (such as
    edit_block
    ) only accepts top-level block IDs (returned by
    get_note_outline
    ). The
    id
    of paragraphs inside containers (
    <highlightBlock>
    ,
    <columns>
    ,
    <table>
    ) in the
    read_note
    XML is for reading reference only and cannot be used for write operations.
  • Provide content in XML format when writing; the system automatically converts it to the internal block model. For
    replace
    , the target block is specified by the tool parameter
    block_id
    , and it is recommended not to write the
    id
    in
    content
    ; if the root block
    id
    is explicitly written, it must match the target
    block_id
    . When using
    insert
    , omit the
    id
    from block-level tags in the new content, and the system will automatically assign a new
    block_id
    .
  • Inline marks use semantic tags:
    <strong>bold</strong>
    ,
    <em>italic</em>
    ,
    <s>strikethrough</s>
    ,
    <u>underline</u>
    ,
    <a href="url">link</a>
    ,
    <tag>#tagname</tag>
    .
  • Tag Writing:
    <tag>#name</tag>
    can be used in inline content like paragraphs. The text must start with
    #
    ; multi-level tags are separated by
    /
    (e.g.,
    <tag>#work/project</tag>
    ); the id attribute is optional—for existing tags, you can pass the id obtained via
    find_tags
    , while for new tags, omit the id and the system will automatically create it. Tags are automatically synchronized to the left tag tree. Restrictions:
    <tag>
    should be placed in the first block (first line) of the note; each level can have a maximum of 20 characters, with up to 10 levels; emojis, spaces, square brackets, and other characters are not supported;
    <tag>
    cannot be nested inside
    <a>
    (tags and links are mutually exclusive). Invalid content will be degraded to plain text and return a warning. After creating a note, insert the tag line before writing the body; if there are no tags after editing an existing note, add them.
  • Inline self-closing elements:
    <emoji value="😀"/>
    (emoji),
    <latex formula="E=mc^2"/>
    (inline formula),
    <br/>
    (hard line break).
  • Style attributes are passed via
    <span>
    :
    <span fontColor="#C21C13">red text</span>
    ,
    <span fontHighlightColor="#FBF5B3">highlighted text</span>
    ,
    <span fontSize="16">large text</span>
    .
  • Colors are constrained by preset palettes; arbitrary hex color values will be silently discarded by the editor. Preset values for various colors:
    • fontColor
      (12 colors):
      #080F17
      #C21C13
      #DB7800
      #078654
      #0E52D4
      #0080A0
      #757575
      #DA326B
      #D1A300
      #58A401
      #116AF0
      #A639D7
    • fontHighlightColor
      (9 colors):
      #FBF5B3
      #F8D7B7
      #F7C7D3
      #DFF0C4
      #C6EADD
      #D9EEFB
      #D5DCF7
      #E6D6F0
      #E6E6E6
    • highlightBlock
      colors (6 bg→border pairs):
      #FAF1E6
      #FEC794
      #FAE6E6
      #F2A7A7
      #E6FAEB
      #AFE3BB
      #E6EEFA
      #98C1FF
      #F5EBFA
      #E5B5FD
      #EBEBEB
      #C5C5C5
    • columnBackgroundColor
      (42 colors, including solid and gradient):
      • 6 base colors:
        #FFF5EB
        #FFECEB
        #E8FCEF
        #EBF2FF
        #FAF0FF
        #F2F2F2
      • 18 extended solid colors:
        #FCFAE1
        #FEF6E7
        #FFF5ED
        #FFF2F0
        #FFF2F4
        #FFF0F7
        #EEFFEB
        #EBFFF5
        #E8FCFC
        #EBF8FF
        #EBF1FF
        #F0EDFF
        #F2E7E4
        #F0E6DA
        #F5EEDA
        #EDF0EB
        #EDEEF0
        #F0E4DD
      • 6 saturated colors:
        #FEF49C
        #BCFAAF
        #ADF4FF
        #C2D3FF
        #FFC7C7
        #E0E0E0
      • 12 gradient colors: Use CSS
        linear-gradient()
        syntax, e.g.,
        linear-gradient(133deg,#FFFAF7 2.14%,#FFEDFE 96.88%)
  • Complete XML format references are automatically integrated into MCP Server Instructions (depending on client support). If not injected, call the
    get_xml_reference
    tool to obtain it on demand.

Read-Only Token

  • Some note tokens are read-only, and write tools will return
    DOCUMENT_READ_ONLY
    .
  • In this case,
    retryable: false
    —do not retry, inform the user instead. Read operations are not affected.

Response Format

All tools return a unified standard envelope:
json
{ "ok": true, "code": "OK", "message": "...", "retryable": false, "data": { ... }, "hints": [] }
  • ok
    —whether the call was successful.
  • code
    —machine-readable status code (see Error Recovery).
  • retryable
    —if
    true
    , you can retry directly.
  • hints
    —suggested subsequent tools or operations.

Workflow

1. Read and Understand Notes

get_current_note()                            → Get note_id + word_count + size_category (determines subsequent strategy)
list_notes / search_notes                     → Find note_id by conditions
get_note_outline(note_id)                     → View structure and block IDs (automatically paginated for extra-large documents)
get_cursor_block()                           → Get the top-level block_id where the current cursor is located
read_section(note_id, heading_id)             → Read a specific section
read_blocks(note_id, block_ids)               → Batch read specified blocks
read_blocks(note_id, block_id, before, after) → Read a single block and its context
search_note_content(note_id, q)               → Search text within the note
read_image(note_id, block_id)                 → Read the actual content of an image block (base64)
get_audio_transcript(shorthand_id)            → Get transcribed content of voice recordings
get_xml_reference()                           → Get complete XML format reference document
For long notes, prioritize using
get_note_outline
read_section
instead of
read_note
to reduce token overhead.

Pagination for Reading Large Documents

When a note has more than 200 blocks, the returns of read_note and get_note_outline will be automatically paginated—use the
offset
and
block_limit
parameters to switch pages and view different parts. There is no upper limit on the number of blocks in a note; these parameters only control the amount returned per read.
Example of
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 }  ← Finished reading
You can also manually control the page size:
read_note({ note_id, offset: 0, block_limit: 50 })
get_note_outline
works similarly—when
has_more: true
, use
next_offset
to continue reading:
get_note_outline({ note_id, offset: next_offset })
read_section
works similarly—when truncated, it returns
next_block_offset
, which can be passed as
block_offset
to continue reading:
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  ← Finished reading

Document Size Strategy

Both
get_current_note
and
get_note_outline
return
word_count
,
size_category
, and
estimated_xml_chars
, based on which you can choose the reading strategy:
size_categoryWord Count RangeStrategy
small
<5K wordsUse
read_note
to read the full text directly
medium
5K-20K wordsUse
get_note_outline
read_section
to read by section
large
20K-80K wordsUse
search_note_content
for precise positioning →
read_blocks
to read the target and context → Edit
very_large
>80K wordsSame as large; prioritize
search_note_content
for precise positioning;
get_note_outline
supports pagination (offset/block_limit) to obtain structure as needed
Principle: The larger the document, the more you should use search positioning instead of sequential reading. For large/very_large documents, first use search_note_content to find the target block, then use read_blocks to read its context. When operating on the current note, first call
get_current_note
to get
size_category
, then decide the path
.

2. Edit Note Content

get_note_outline(note_id)                      → Get the latest block IDs
get_cursor_block()                            → Get the anchor block when editing around the current cursor position
read_blocks(note_id, [target_id])              → Confirm current content
insert_image(note_id, anchor_id, pos, src)     → Insert image (independent tool, does not use XML, requires internet connection currently)
generate_image(prompt, width?, height?)        → AI text-to-image, returns image URL (use with insert_image to insert into notes)
edit_block(note_id, op, ...)                   → Single edit operation (replace, insert, delete, update attributes, move)
batch_edit(note_id, operations)                → Combine multiple operations into one atomic transaction
Key: Block IDs may change after editing. For consecutive inserts, you can directly use the returned
last_block_id
as the anchor; before operating on other blocks, refresh via
get_note_outline
.
Parameter Constraints:
edit_block
/
batch_edit
only pass the fields required for the current
op
. The
content
for
replace
/
insert
must be a complete XML string, not plain text, Markdown, or natural language editing instructions like "change the second paragraph to...".
Correct way to perform consecutive inserts (avoid out-of-order):
  • Priority: Combine all content into a complete XML in one
    insert
    (e.g.,
    "<h2>A</h2><p>...</p><h2>B</h2><p>...</p>"
    ), insert in XML order, no need to call multiple times.
  • Chained: If you must perform multiple inserts, use the
    last_block_id
    returned from the previous call as the
    anchor_id
    for the next call, or call
    get_note_outline
    to refresh IDs after each write.

3. Batch Editing (Atomic Transaction)

Use
edit_block
for single operations, and
batch_edit
to combine multiple operations into an atomic transaction:
# Single operation
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>")

# Multiple operations (atomic transaction)
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>" }
])
Execution order is fixed (for
batch_edit
): delete → replace → update_attrs → move → insert (regardless of array order).

4. Manage Notes and Tags

get_note_info(note_id)                      → Get single note metadata (including tags)
get_note_info(note_ids: [...])              → Batch get metadata for multiple notes (up to 100)
get_note_info(page, page_size)              → Browse all note metadata by page
search_notes(keyword, tags, since, before)  → Search notes (can also filter by tags)
create_note(title)                          → Create blank note (need to add content with edit_block)
import_web_page(url)                       → Import web pages from whitelisted domains as notes (WeChat Official Accounts, Zhihu, Douban, etc.)
delete_note(note_id)                        → Irrecoverable—must confirm with user first
sync_note(note_id)                          → Trigger synchronization
get_note_stats(detailed)                    → Usage statistics

Tool Cheat Sheet

Read/Location Tools

ToolPurposeKey Parameters
get_note_outline
Get structural outline (including title, word_count, block_count, size_category, estimated_xml_chars, blocks list, pagination) — main way to get block_id and document scale, automatically paginated for extra-large documents
note_id
max_depth?
include_preview?
offset?
block_limit?
get_cursor_block
Get the
block_id
,
block_type
, and
note_id
of the top-level block where the current cursor is located; not supported when the cursor is inside a highlight block or columns
* (No parameters) *
read_blocks
Batch read XML content and attributes of blocks by ID, or read a single block and its surrounding context
note_id
block_ids
(batch) or
block_id
+
before?
+
after?
(context)
read_note
Read full text or paginated content (XML), automatically paginated for extra-large documents and returns pagination (including has_more + next_offset)
note_id
max_length?
offset?
block_limit?
search_note_content
Search text within the note, returns block_id/type/preview of matching blocks, used for positioning before editing
note_id
query
max_results?
read_section
Read a complete section by heading (until the next sibling heading), returns next_block_offset for continuation when truncated
note_id
heading_block_id
max_blocks?
block_offset?
read_image
Read the actual content of an image block (base64), for visual understanding
note_id
block_id
get_audio_transcript
Get transcribed text of voice recordings (NoteAudioCard), returns list of sentences (including speaker, timestamp)
shorthand_id
(obtained from attrs of
note_audio_card
block in outline)
get_xml_reference
Get complete XML format reference document (all tags, attributes, writing examples)* (No parameters) *
Examples:
get_note_outline({ note_id: "abc123" })
get_note_outline({ note_id: "abc123", offset: 100 })  // Continue reading with pagination
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 })  // Continue reading with pagination
read_note({ note_id: "abc123", offset: 0, block_limit: 50 })  // Manually control page size
search_note_content({ note_id: "abc123", query: "Quarterly Summary" })
read_section({ note_id: "abc123", heading_block_id: "aB3kLm9xZq" })
read_section({ note_id: "abc123", heading_block_id: "aB3kLm9xZq", block_offset: 50 })  // Continue reading truncated section
read_image({ note_id: "abc123", block_id: "imgBlock01" })
get_audio_transcript({ shorthand_id: "sh_abc123" })
get_xml_reference()

Write Tools

ToolPurposeKey Parameters
edit_block
Single edit operation (recommended entry), block_id may change after editing and needs to be re-obtained. Insert operations return
new_block_ids
and
last_block_id
. Move operations do not change block_id
note_id
op
(replace/insert/delete/update_attrs/move)、
block_id
/
anchor_id
/
block_ids
content
/
attrs
batch_edit
Atomic transaction for multiple operations (all succeed or all roll back), execution order is fixed. Returns
new_block_ids
and
last_block_id
note_id
operations[]
insert_image
Insert image (images cannot be created via XML, must use this tool), currently uses online upload, returns block_id and dimensions
note_id
anchor_id
position
src
alt?
generate_image
AI text-to-image, returns image URL (use with
insert_image
to insert into notes). Limit 1 time per user per minute, takes 30-120 seconds
prompt
width?
(default 2688)、
height?
(default 1536)
import_web_page
Import web content from whitelisted domains (WeChat Official Accounts, Zhihu, Douban, etc.) as notes, returns note ID, title, and abstract. Takes 5-30 seconds
url
Operation types for
edit_block
/
batch_edit
:
op
Required Fields
"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
only appears in
replace
/
insert
, and must be a complete XML string, not natural language, plain text, or Markdown.
File Reference Parameters (CLI and script scenarios): When
content
or
operations
are too large to cause command line truncation, you can use file references instead—
content_file
(pass file path, Electron reads it and fills into
content
),
operations_file
(fills into
operations
similarly),
__args_file
(reads all parameters from JSON file). Explicit values take precedence over file references. See
references/CLI_REFERENCE.md
for details.
Examples:
// Single operation — edit_block
edit_block({ note_id: "abc123", op: "replace", block_id: "xY7nPq2wRt", content: "<p>Updated paragraph text</p>" })
edit_block({ note_id: "abc123", op: "insert", anchor_id: "aB3kLm9xZq", position: "after", content: "<h2>New Section</h2><p>This is new content.</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" })

// Multiple operations (atomic transaction) — batch_edit
batch_edit({ note_id: "abc123", operations: [
  { op: "replace", block_id: "xY7nPq2wRt", content: "<p>Updated paragraph text</p>" },
  { op: "insert", anchor_id: "aB3kLm9xZq", position: "after", content: "<h2>New Section</h2>" }
]})

// Image
insert_image({ note_id: "abc123", anchor_id: "aB3kLm9xZq", position: "after", src: "https://example.com/photo.png", alt: "Diagram" })

// AI text-to-image → Insert into note
generate_image({ prompt: "An orange cat sitting on a windowsill, watercolor style, warm tones" })
→ { image_url: "https://...", task_id: "...", width: 2688, height: 1536 }
// Then use insert_image to insert the returned image_url into the note

// Import web page as note
import_web_page({ url: "https://mp.weixin.qq.com/s/xxx" })
→ { fileId: "123456", title: "Article Title", intro: "Abstract...", linkUrl: "https://xxx/note/123456" }

Hidden Write Tools

The following tools are still available but not displayed by default, suitable for scenarios that require separate calls:
ToolPurposeKey Parameters
replace_block
Replace content of a single block
note_id
block_id
content
insert_block
Insert before/after specified position
note_id
anchor_id
position
content
delete_blocks
Delete blocks
note_id
block_ids
update_block_attrs
Modify block attributes
note_id
block_id
attrs

Management Tools

ToolPurposeKey Parameters
list_notes
List notes, supports sorting and pagination, returns { notes, meta }
limit?
sort?
direction?
since?
before?
page?
page_size?
starred?
create_note
Create blank note (only contains empty paragraph), returns { fileId, title }
title?
get_note_info
Get note metadata (does not read full text), returns title, time, intro, tags, starred
note_id
(single)、
note_ids
(batch)、
limit?
page
+
page_size
(pagination)
get_current_note
Get current note ID, metadata, and document statistics (including word_count, size_category)* (No parameters) *
get_cursor_block
Get the
block_id
,
block_type
, and
note_id
of the block where the current cursor is located; not supported when inside a highlight block/columns
* (No parameters) *
search_notes
Search notes (keyword + tag intersection + time range), can also pass only tags to browse by tag
keyword?
tags?
since?
before?
sort?
direction?
limit?
starred?
find_tags
List or search tags, returns array of { id, name }
keyword?
(list all if not passed, search if passed)
sync_note
Trigger synchronization between note and cloud
note_id
delete_note
Permanently delete note (irrecoverable), must confirm with user first
note_id
or
note_ids
(batch, up to 100)
get_note_stats
Usage statistics (total notes, number of tags, distribution, etc.)
detailed?
get_mcp_logs
MCP call logs, used for diagnosing
INTERNAL_ERROR
limit?
Examples:
list_notes({ sort: "update_time", direction: "desc", limit: 10 })
list_notes({ starred: true })                  // List only starred notes
search_notes({ keyword: "Project Plan", tags: ["Work"], since: "2025-01-01T00:00:00Z" })
search_notes({ tags: ["Work"] })  // Filter notes by tag (replaces original list_tag_notes)
search_notes({ starred: true })  // Search only in starred notes
create_note({ title: "Meeting Minutes 2025-03-03" })
get_current_note()
get_cursor_block()
get_note_info({ note_id: "abc123" })              // Single note metadata (including tags)
get_note_info({ note_ids: ["abc123", "def456"] }) // Batch query
get_note_info({ page: 1, page_size: 20 })         // Browse all notes by page
get_note_info({ limit: 5 })                       // Get only the first 5 entries
find_tags()                       // List all tags
find_tags({ keyword: "Work" })    // Search tags
delete_note({ note_id: "abc123" })  // Irrecoverable—must confirm with user first!
get_mcp_logs({ limit: 20 })       // View recent tool call logs

Block Types and XML Tags

Block TypeXML TagDescription
paragraph
<p>
Supports inline marks (
<strong>
,
<em>
,
<s>
,
<u>
,
<a>
,
<tag>
) and inline nodes (
<emoji/>
,
<latex/>
,
<br/>
)
heading
<h1>
-
<h6>
Level is controlled by tag name or
attrs.level
blockquote
<blockquote>
Supports inline marks and
<br/>
line breaks
code_block
<codeblock lang="...">
Plain text content; language is specified via
lang
attribute
list
<p listType="bullet|ordered|todo">
Controlled by
listType
,
listLevel
,
checked
attributes; ordered lists additionally support
listId
and
listValue
table
<table>
<tr>
<td>
structure; must be replaced in full. Cells only support simple blocks (
<p>
,
<h1>
-
<h6>
,
<blockquote>
,
<codeblock>
,
<hr/>
,
<img/>
), cannot nest
<highlightBlock>
,
<columns>
,
<table>
or other containers
highlight_block
<highlightBlock>
Highlight block, only supports simple blocks (
<p>
,
<h1>
-
<h6>
,
<blockquote>
,
<codeblock>
,
<hr/>
,
<img/>
), cannot nest
<highlightBlock>
,
<columns>
,
<table>
or other containers
columns
<columns>
<column>
Column layout, each column supports simple blocks and
<highlightBlock>
, cannot nest
<table>
or
<columns>
image
<img/>
Read-only—cannot be created or modified via XML, use
insert_image
tool to insert single image
image_column
<imageColumn>
Read-only—image column display, cannot be created via XML
horizontal_rule
<hr/>
Horizontal rule
embed
<embed type="..."/>
Read-only—cannot be edited or replaced
note_audio_card
<NoteAudioCard/>
Read-only—voice recording card, use
get_audio_transcript
to get transcribed content

Attributes Supported by
update_block_attrs

Block TypeAttributeOptional Values
heading
level
1–6
heading
textAlign
"left"
,
"center"
,
"right"
paragraph
textAlign
"left"
,
"center"
,
"right"
code_block
language
Language identifier string
todo_list
item
checked
true
/
false

Troubleshooting

Invalid Block ID (
BLOCK_NOT_FOUND
)

Symptom: Write operations report
BLOCK_NOT_FOUND
, even though the block was just read. Cause: Editing operations cause block IDs to change, and cached IDs are expired. Solution: Always refresh IDs via
get_note_outline
or
search_note_content
before writing; or use the
last_block_id
returned from the previous insert as the next
anchor_id
.

Table Editing Failure

Symptom: Errors or content loss when trying to edit table cells. Cause: Tables only support full replacement; individual cells cannot be edited. Solution: Use the
replace
operation of
edit_block
to pass the complete XML table (including header separator row).

Embed Block Cannot Be Written

Symptom: Calling
replace
operation of
edit_block
on embed blocks returns an error. Cause: Embedded content like spreadsheets, videos, audio, LaTeX, etc., are read-only placeholders. Solution: Skip embed blocks and only operate on other editable block types.

Read-Only Note (
DOCUMENT_READ_ONLY
)

Symptom: All write operations return
DOCUMENT_READ_ONLY
. Cause: The current note token has read-only permission, cannot retry. Solution: Inform the user that this note is read-only. Read operations still work normally.

Editor Not Ready (
EDITOR_NOT_READY
)

Symptom: Operations return
EDITOR_NOT_READY
. Cause: The note editor is still initializing, usually recovers in 1-2 seconds. Solution: Wait a moment and retry. If it fails 3 times, ask the user to check the note app.

WebSocket Not Connected (
WEBSOCKET_NOT_CONNECTED
)

Symptom:
get_audio_transcript
returns
WEBSOCKET_NOT_CONNECTED
. Cause: Network disconnected or WebSocket connection interrupted. Solution: Check network connection, wait for WebSocket to reconnect automatically and retry.

Other Notes

  • Images must use the
    insert_image
    tool
    : Images cannot be created via XML (the
    <img/>
    tag is read-only).
    insert_image
    currently uses online upload, requires internet connection when called. Supports HTTP/HTTPS URLs and base64 data URIs. Local file paths are not supported directly; they need to be read and converted to base64 data URIs first. URLs must directly point to image resources (return image/ content type), not HTML page links*. If the URL returns 404, is offline, or is non-image content, it will report
    IMAGE_FETCH_FAILED
    error.
    edit_block
    's replace/insert/update_attrs does not support images (but move can move image blocks).
  • generate_image
    is AI text-to-image
    : Returns image URL, not directly inserted into notes. Need to use with
    insert_image
    . Limit 1 time per user per minute, takes about 30-120 seconds to generate.
  • import_web_page
    only supports whitelisted domains
    : Whitelisted domains like WeChat Official Accounts, Zhihu, Douban can be imported; non-whitelisted domains will return
    INVALID_PARAMS
    . Conversion takes about 5-30 seconds. After import, a new note is automatically created and opened.
  • batch_edit
    execution order is fixed
    : delete → replace → update_attrs → move → insert, regardless of array order.
  • move
    operation supports all block types
    : Including read-only tags that cannot be created via XML, such as images (
    <img/>
    ), embedded content (
    <embed/>
    ), etc. Block_id remains unchanged after moving, no need to refresh outline. Suitable for adjusting content order and reorganizing document structure.
  • create_note
    creates blank note
    : Does not support initial content; need to fill with
    edit_block
    .
  • read_section
    is limited to headings
    :
    heading_block_id
    must point to a
    heading
    block, otherwise it reports
    INVALID_BLOCK_TYPE
    .
  • get_cursor_block
    only supports top-level blocks
    : When the cursor is inside a highlight block (subdoc) or columns, it returns
    UNSUPPORTED_POSITION
    .
  • delete_note
    is irrecoverable
    : Must confirm with user first.
  • When the response contains
    hints
    , follow the suggestions—they indicate the fastest recovery path.

Error Code Quick Reference

Error CodeRetryableRecovery Method
INVALID_PARAMS
NoCorrect parameters according to inputSchema
EDITOR_NOT_READY
YesWait and retry
NO_ACTIVE_EDITOR_WINDOW
YesAsk user to open note window
BLOCK_NOT_FOUND
NoRefresh outline to get valid IDs
INVALID_BLOCK_TYPE
NoCheck block type
INVALID_CONTENT
NoCorrect content format
DOCUMENT_READ_ONLY
NoInform user
FRONTEND_TIMEOUT
YesNarrow scope or retry
IMAGE_FETCH_FAILED
YesCheck if image URL directly points to image resource, correct and retry
GENERATE_IMAGE_FAILED
NoCorrect prompt or check whitelist/login status
RATE_LIMITED
YesWait 60 seconds and retry
WEBSOCKET_NOT_CONNECTED
YesCheck network, wait for WebSocket to reconnect and retry
INTERNAL_ERROR
YesRetry; check
get_mcp_logs
(hidden tool)
Complete error details can be found in
references/ERROR_CODES.md
.

Common Orchestration Patterns

The most frequent multi-tool combinations, usable without referring to the complete use case manual.
Pattern 1: Search Positioning → Sectional Reading (priority for long documents)
search_notes({ keyword }) → note_id
get_note_outline({ note_id }) → heading block ID
read_section({ note_id, heading_block_id }) → Read sections as needed
Pattern 2: Batch Search and Replace in Notes
search_note_content({ note_id, query }) → List of matching block_ids
read_blocks({ note_id, block_ids }) → Read full content
batch_edit({ note_id, operations: [{ op: "replace", ... }, ...] }) → Atomic replacement
Pattern 3: Create → Fill Template
create_note({ title }) → note_id (blank note)
get_note_outline({ note_id }) → Get empty block ID
batch_edit({ note_id, operations: [
  { op: "replace", block_id, content: "<h1>Title</h1>" },
  { op: "insert", anchor_id, position: "after", content: "<p>Template content...</p>" }
]})
Pattern 4: Append Multiple Sections Continuously (avoid out-of-order)
# Method A (recommended): Single insert with complete XML
edit_block({ note_id, op: "insert", anchor_id: "last_id", position: "after",
  content: "<h2>Part 1</h2><p>Content A</p><h2>Part 2</h2><p>Content B</p>" })

# Method B: Chained insert, use last_block_id from previous return as anchor
edit_block({ ..., anchor_id: "id1", position: "after", content: "<h2>Part 1</h2><p>Content A</p>" })
→ { last_block_id: "new_id_1" }
edit_block({ ..., anchor_id: "new_id_1", position: "after", content: "<h2>Part 2</h2><p>Content B</p>" })
→ { last_block_id: "new_id_2" }
Pattern 5: Move Blocks (images, embedded content, etc.)
get_note_outline({ note_id }) → Find source block and target position
edit_block({ note_id, op: "move", block_id: "img_id", anchor_id: "target_heading", position: "after" })
→ block_id remains unchanged, no need to refresh outline
Pattern 6: Paginate to Read Extra-Large Documents (priority when there is no title structure; pagination only controls reading amount, no block limit for notes)
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 }  ← Finished reading
More combination patterns and end-to-end scenarios can be found in
references/USE_CASES.md
.

Complete Example

User says: "Help me find the Q1 report, update the summary section, and add a conclusion"
Step 1: Locate the note
search_notes({ keyword: "Q1 Report" })
→ data.notes[0].note_id = "note_xyz"
Step 2: Read structure
get_note_outline({ note_id: "note_xyz" })
→ blocks: [
    { id: "h1abc", type: "heading", preview: "Q1 Report" },
    { id: "p1def", type: "paragraph", preview: "Executive Summary..." },
    { id: "h2ghi", type: "heading", preview: "Revenue" },
    { id: "h2jkl", type: "heading", preview: "Next Steps" },
    { id: "p3mno", type: "paragraph", preview: "Continue monitoring..." }
  ]
Step 3: Confirm current content
read_blocks({ note_id: "note_xyz", block_ids: ["p1def"] })
→ "This quarter, each department..."
Step 4: Atomic edit (replace summary + insert conclusion)
batch_edit({ note_id: "note_xyz", operations: [
  { op: "replace", block_id: "p1def",
    content: "<p>This report covers the performance of all departments in Q1 2025. Revenue exceeded the target by 15%.</p>" },
  { op: "insert", anchor_id: "p3mno", position: "after",
    content: "<h2>Conclusion</h2><p>Q1 2025 was a strong quarter, with all key indicators achieving significant growth.</p>" }
]})
→ ok: true, last_block_id: "new_conclusion_id"
Tip: In the insert operation of
batch_edit
/
edit_block
, content can contain multiple block-level XML elements (like
<h2>
+
<p>
in the example), which are inserted in XML order without needing multiple calls. The returned
last_block_id
can be directly used for subsequent operations.
Step 5: Verify results
read_section({ note_id: "note_xyz", heading_block_id: "h1abc", max_blocks: 5 })
→ Confirm summary is updated and conclusion is added
Result: The note summary has been replaced with new content, and a "Conclusion" section has been added at the end.

Reference Documents

  • API Reference——Complete inputSchema for all tools
  • CLI Reference——Canonical command interface for
    wpsnote-cli
    , CLI-specific behaviors, and JSON output format
  • Error Codes——Detailed error codes, hints system, and recovery modes
  • Use Case Manual——Complete use case set (including end-to-end workflow examples) by complexity, prompt templates, and quick reference for exception handling