Skip to main content
Glama
doc-fine-tuning-mcp

doc-fine-tuning-mcp

by chaosst

doc-fine-tuning-mcp — opencode office document fine-tuning annotator

An opencode MCP server: when the LLM needs to make fine-grained modifications to your office documents, use it to visually open the document in a standalone browser app window. You click on a whole paragraph (or drag-select a contiguous string for finer-grained annotation) and enter a modification prompt for each location; you can annotate multiple locations at once. When done, hand it back to the agent, and the LLM executes the modifications one by one based on your prompts. The annotation window automatically closes when the agent is cancelled/exits; for multiple rounds of annotation on the same document, it reuses the same window and auto-reloads.

Supported formats: .docx / .xlsx / .pptx (OOXML). v1 does not support legacy formats (.doc/.xls/.ppt).

Workflow

用户对 opencode 说:把 D:\报告.docx 的第 3 段和标题改得更正式
   │
   ▼
opencode 插件 doc-edit-listener 检测到"文档精细修改意图",注入引导
   │
   ▼
LLM 调用 doc-edit_annotate_document("D:\报告.docx")
   │   → 本地 HTTP 服务启动,独立浏览器应用窗口打开标注页 http://127.0.0.1:<port>/?session=xxx
   │   (同一文档再次标注:复用该窗口自动重载,不重开新窗口)
   ▼
你在页面上:看到文档 → 点击整段/单元格/形状(或拖选连续字符串)→ 输入提示词 → 继续标注下一处 → 点【完成】
   │   (标注以 loc + prompt 形式提交给服务端;完成后 agent 窗口重新聚焦)
   ▼
LLM 调用 doc-edit_wait_for_annotations 拿到全部标注
   │   (若窗口被关闭:返回 status=closed + reason,LLM 据此决定重开或询问你)
   ▼
LLM 对每个标注:doc-edit_read_location 读取原文 → 依据提示词生成新内容
   │   → doc-edit_apply_edit 应用修改(首次自动备份 .bak-<时间戳>)
   ▼
标注窗口自动重载(服务端推送),展示修改后的最新文档供你检查
   │   → LLM 再次 doc-edit_wait_for_annotations 等待下一轮标注(多轮循环)
   ▼
你点【完成】且无标注 / 点【取消】/ 关闭窗口 → 修改流程结束

Related MCP server: word-mcp-live

Architecture

opencode
 ├─ 插件 doc-edit-listener(监听消息 → 检测修改意图 → 注入引导)
 └─ MCP 客户端 ──stdio──► doc-edit MCP server(Node/TS, @modelcontextprotocol/sdk)
                          ├─ 标注工具:annotate_document / wait_for_annotations / cancel_session
                          ├─ 编辑原语:read_structure / read_location / apply_edit
                          ├─ 本地 HTTP 服务(127.0.0.1:动态端口)→ H5 标注页 + API
                          └─ Python 编辑引擎(python-docx / openpyxl / python-pptx)
  • Location consistency: The position you click on the page (loc) is generated by the JS side traversing the OOXML; the Python engine resolves the location using the same traversal algorithm, and tests/parity tests guarantee that the loc→text mapping is identical on both sides (especially paragraphs inside Word tables and multi-shape PPTs).

  • The LLM is the executor: Every step of generating new content is done by the LLM based on your prompts; the MCP server is only responsible for "opening the page, collecting annotations, and reading/writing files by location".

Installation

1. Clone the project and install dependencies

cd D:\develop\doc-fine-tuning-mcp
npm install
# Python 编辑引擎(创建 .venv 并安装 python-docx/openpyxl/python-pptx)
cmd //c scripts\\setup_venv.bat

2. Build

npm run build          # 编译 src → dist/(MCP server)
cd web && npm install && npm run build   # 构建 H5 标注页 → web/dist/

3. Register the MCP server with opencode

Add to the mcp block in ~/.config/opencode/opencode.jsonc (development mode, pointing to the local build output):

"doc-edit": {
  "type": "local",
  "command": ["node", "D:/develop/doc-fine-tuning-mcp/dist/index.js"],
  "enabled": true
}

Release mode (optional): after npm pack generates the tgz, switch to npx -y --package <路径>/doc-fine-tuning-mcp-<版本>.tgz doc-fine-tuning-mcp, consistent with other MCPs in the project.

4. Install the listener plugin

The plugin consists of two files: doc-edit-listener.ts and its dependent detection module lib\detect.ts. The opencode plugin directory automatically scans .ts files under it as plugins, but does not recursively scan subdirectories, so detect.ts placed in the lib\ subdirectory will not be mistakenly loaded as a standalone plugin.

copy plugin\doc-edit-listener.ts %USERPROFILE%\.config\opencode\plugin\doc-edit-listener.ts
mkdir %USERPROFILE%\.config\opencode\plugin\lib
copy plugin\lib\detect.ts %USERPROFILE%\.config\opencode\plugin\lib\detect.ts

⚠️ The plugin module can only export the plugin itself (default). Do not add any named function exports to doc-edit-listener.ts, otherwise opencode will treat them as extra hooks/plugins, causing loading to fail with "Unexpected server error" (after hooks are emptied, it cascades to a Provider.defaultModel crash). Put all detection logic in lib/detect.ts.

The plugin depends on @opencode-ai/plugin (version 1.18.11 is already bundled in ~/.config/opencode/node_modules). It takes effect after restarting opencode.

5. End-to-end self-check

npm test                  # 引擎 / parity / mcp 客户端 / 插件 全部测试
node scripts/e2e-verify.ts   # 输出 PASS 即闭环可用

MCP tool reference

Tool

Parameters

Description

annotate_document

path

Opens a standalone annotation window, creates/reuses an annotation session, returns {session_id, url}

wait_for_annotations

session_id, timeout_seconds?

Blocks until done/cancelled/closed/timeout, returns the annotation list + user_actions (operations such as user rollbacks, for the LLM to summarize changes)

cancel_session

session_id

Cancels the annotation session

read_structure

path, max_items?

Document outline (docx paragraph by paragraph / xlsx sampled per sheet / pptx shapes per page)

read_location

path, loc

Original text at the specified location + adjacent context (returns the substring when the docx loc has a range)

apply_edit

path, loc, new_content, mode?, style?

Applies the modification (only replaces that substring when the docx loc has a range; automatically backs up .bak-<时间戳> on first edit)

template_replace

path, variables

Batch-replaces {{变量}} placeholders (docx only; can replace across run breaks; returns {matched, replaced, missing_vars, applied})

find_replace

path, find, replace, match_case?

Full-text find and replace (docx only, plain string not regex; returns {matched, replaced, locations})

preview_edits

path, edits

Batch-previews modifications (applied in memory without writing to disk, returns {loc, before, after} for each)

list_versions

path

Lists version history (auto-snapshots to <path>.versions/ before each apply_edit, newest first; includes operation type/human-readable description/time)

restore_version

path, index

Rolls back to the specified version (snapshots the current state before rolling back; reversible)

mode: replace / append / prepend / insert_after / delete. style (optional): { bold, italic, sizePt, color }.

Note: template_replace / find_replace use run-level format fidelity — when the replacement range spans multiple runs with different formats, the new text is segmented by source run character weight, with each segment inheriting the corresponding run's format (no longer degrading to keeping only the first run's format).

Location descriptor loc

The loc generated by clicking on the page is the sole credential for "which location to change", with six types:

| { kind: "docx-paragraph",       paraIndex }                  // Word 段落(body 文档序,0-based)
| { kind: "docx-cell",            tableIndex, rowIndex, colIndex, paraIndex }   // Word 表格内段落
| { kind: "xlsx-cell",            sheet, row, col }            // Excel 单元格(1-based,同 A1)
| { kind: "xlsx-range",           sheet, row1, col1, row2, col2 }
| { kind: "pptx-shape",           slideIndex, shapeIndex }     // PPT 形状(1-based)
| { kind: "pptx-shape-paragraph", slideIndex, shapeIndex, paraIndex }

Usage notes and tips

  • The LLM should: first annotate_document to let the user annotate → wait_for_annotations to get the annotations → read_location for each annotation to verify the original text → generate new content per the prompt → apply_edit. Do not guess the modification location.

  • Version history for error correction: Before each apply_edit, the document is snapshotted to <path>.versions/. If you find abnormal content after a modification (the replacement result doesn't match the prompt, or the user is unhappy with a round's result), use list_versions to view snapshots and restore_version to roll back to before the modification, then regenerate. After a rollback, the loc indices of old annotations may become invalid — re-verify with read_location or have the user re-annotate (a rollback in the first round is most valuable — annotations are still based on the original document). The "History" tab in the annotation window sidebar also lets you view the version chain directly (each entry includes operation description/time/size) and roll back with one click. User rollback operations are recorded in the session's user_actions and returned with wait_for_annotations — when the LLM sees user_actions containing restore, it should realize the document has been rolled back and subsequent modifications may be invalid, so verify before continuing.

  • Wait polling (never times out): wait_for_annotations never times out by default (waits until the user submits / cancels / closes the window, or the agent exits). Some clients have a timeout cap for a single tool call (about 60s), in which case the single call gets truncated — calling the tool again continues waiting, and the session is not cancelled by the truncation.

  • Window closed: If the user closes the annotation window, wait_for_annotations returns closed + reason (window_closed / page_unload / window_lost / agent_cancelled / agent_exited). The LLM should decide based on the reason whether to reopen annotation (call annotate_document again) or ask the user.

  • Automatic window reload (push mode): After each successful apply_edit / template_replace / find_replace / restore_version modification, the server automatically pushes a reload to the annotation window for that document (about 2s debounce, consecutive modifications merged into one), and the window immediately shows the latest modified content — no need for the LLM to manually call annotate_document again.

  • Scroll viewport preservation: After reload, the page returns to your previous reading position (viewport anchoring — records content near the top of the viewport and restores by offset, rather than simply scrolling to top), while also keeping the current xlsx worksheet and the current pptx page.

  • Window ownership with multi-conversation shared server: Clients like WorkBuddy globally share the same MCP server process, so annotation sessions from all conversations are mixed in the same process. To avoid reloading the window of another conversation after a modification, the LLM should pass the returned session_id as-is to modification tools like apply_edit (the tools already support this optional parameter) after getting annotations from wait_for_annotations; the server then precisely reloads this session's window. When not passed, the server falls back to the "most recently active session", and does not reload when the path is ambiguous (to avoid collateral damage). Annotation windows for the same document are reused under a shared server — it's recommended to operate on the same document in only one conversation at a time.

  • Same-document reuse: Calling annotate_document again for the same document reuses the existing window and auto-reloads (the page re-fetches the latest file and clears the previous round's annotations), without opening a new window.

  • Multi-round annotation loop: After the agent processes one round of annotations, the window auto-reloads for you to review the changes and continue annotating; the agent should call wait_for_annotations again to wait for the next round. Clicking [Done] in the window without adding any annotations means no further changes are needed for this round; clicking [Cancel] directly closes the window to end the round (wait_for_annotations stops accordingly).

  • String-level annotation: Drag-selecting a contiguous string in the Word page makes the annotation precise to that string (loc.range); apply_edit only replaces it.

  • Cancel: When the user clicks [Cancel] on the page, or the LLM calls cancel_session, the session is set to cancelled and the annotation window closes directly; when the agent process exits, all annotation windows close automatically.

  • Modifications are non-destructive: before the first edit, 文档名.bak-<时间戳> is automatically generated, and before each modification, a snapshot is automatically added to the <path>.versions/ version chain (you can roll back any number of steps with list_versions / restore_version).

Known limitations

  • Only .docx / .xlsx / .pptx (OOXML) are supported.

  • Word: the docx-preview render order and the body traversal order are assumed to be 1:1 (extreme layout elements may deviate; see the comment at the top of web/src/viewers/docxViewer.ts).

  • PPT: shape location falls back to text matching, which may be imprecise when shape text is duplicated.

  • Excel: SheetJS + lightweight grid; supports double-click in-page cell editing (the change is handed back to the LLM as an annotation to apply); merged cells other than the top-left are read-only; directly editing a formula cell replaces the formula with a plain value.

  • Session state is in memory and is lost when the MCP server restarts.

FAQ

Q: The browser didn't open automatically? A: The annotation page opens in a standalone Chrome/Edge app window (no address bar/toolbar/tabs). If Chrome/Edge can't be found or fails to launch, the tool still returns url, and the LLM will send you the link to open manually (in this case, window-close detection degrades to page heartbeat).

Q: Why does the annotation window close by itself? A: When you cancel the agent with Esc, or the agent/opencode process exits, the server proactively closes the annotation window; after you click [Done] or [Cancel] on the page, the window stays read-only waiting for reuse (the next round for the same document reloads directly).

Q: The annotation page won't open / white screen? A: Make sure you've run cd web && npm run build (the server only returns a placeholder page when web/dist is missing); make sure the document path is an absolute path and the file exists.

Q: Why is the plugin needed? A: The plugin detects the "fine-tune document" intent at the message layer and injects guidance, so the LLM lets you annotate first before making changes, avoiding it guessing locations and editing directly. Note: the plugin only works with opencode; in other MCP clients like WorkBuddy, the LLM's workflow guidance comes from the tool descriptions and the next field returned by wait_for_annotations (which has the multi-round loop convention built in).

Q: After a modification, the window auto-reloads; if I annotate again and click [Done], will the agent continue processing? A: It depends on whether the agent is still waiting:

  • If the agent is in the "process annotations → call wait_for_annotations again" loop per the guidance, your newly submitted annotations will be picked up immediately and processed (seamless multi-round handoff);

  • If the agent has already ended its turn (no longer calling tools), your annotations are temporarily stored on the server, but the agent is not automatically woken up — in that case, just tell the agent "annotations submitted, please continue processing". This is an inherent boundary of the "LLM is the executor" architecture: the server cannot take over the next round on behalf of the agent.

Q: If two conversations edit the same document at the same time, will the windows get mixed up? A: WorkBuddy globally shares the same server process, so annotation windows for the same document are reused and shared. The post-modification auto-reload is now precisely bound to the session: when the LLM includes the session_id returned by wait_for_annotations in tools like apply_edit, only this conversation's window is reloaded; when not included, it falls back to the most recently active session, and when the path has multiple live sessions, it prefers not reloading over causing collateral damage. It's recommended to operate on the same document in only one conversation at a time.

Available Tools

11 tools
annotate_documentA

打开文档标注页面(H5):创建标注会话并启动本地 HTTP 服务,在独立浏览器应用窗口打开标注页(无地址栏/工具栏/标签页)。若同一文档已有存活窗口则直接复用(原位重载、清空上轮标注),不重新开窗。返回 {session_id, url}。多轮循环:窗口会在文档被修改后自动重载最新内容(apply_edit 成功后服务端主动推送),无需每次手动调用本工具;也可在本轮开始或需要立即刷新时主动调用。同一文档重复调用会复用现有窗口。

Inputs: path (required).

apply_editA

对指定位置应用修改(首次编辑前自动生成 .bak-<时间戳> 备份;同一标注轮次内只有第一次编辑会快照到版本历史,其余编辑复用该版本——一轮多个标注只产生一个版本,版本描述会汇总本轮全部修改)。mode 缺省 replace;引擎错误返回结构化错误。返回 {ok, loc, new_content, version}(version 为 null 表示本轮已快照过、未新增版本;version.desc 表达该快照为'修改前/该轮标注前的状态'——会话场景为'第 N 轮标注前的状态(本轮修改:…)',无会话为'修改前:<编辑摘要>',回退到该版本即恢复为此内容)。修改成功后,该文档的标注窗口会自动重载展示最新内容(无需再手动调用 annotate_document);用户检查后可能继续标注,此时应再次调用 wait_for_annotations 获取下一轮标注。若修改后发现内容异常,可用 list_versions / restore_version 回退到修改前再重新生成。

Inputs: loc (required), path (required), mode, style, +2 more.

cancel_sessionB

取消标注会话(用户点了“取消”)。返回 {session_id, status}。

Inputs: session_id (required).

find_replaceA

在 docx 文档中全文查找并替换字符串(普通字符串,非正则;匹配范围含表格内单元格)。match_case=false 时大小写不敏感,替换文本原样插入。返回 {matched, replaced, locations}。

Inputs: find (required), path (required), replace (required), match_case, +1 more.

list_versionsA

列出文档的版本历史(.versions/ 快照链:同一标注轮次内多个编辑合并为一个版本,即每轮首个编辑前快照;新版本在前)。返回 [{index, path, size, mtime, kind?, desc?, ts?}],无版本时返回 []。每条 desc 表达'修改前/该轮标注前的状态'(如'第 1 轮标注前的状态'或'修改前:替换…'),回退到该版本即恢复为该内容。配合 restore_version 实现任意步回退。

Inputs: path (required).

preview_editsA

批量预览修改(仅内存计算,不写盘):按顺序在内存中对同一文档应用 edits 列表,返回每处的 {loc, before, after}。适合在真正 apply_edit 前检查一批修改是否符合预期。edits 元素为 {loc, new_content?, mode?, style?}。返回 [{loc, before, after}]。

Inputs: path (required), edits (required).

read_locationA

读取指定位置的原文与相邻上下文,供生成新内容前核对。返回 {loc, text, context}。

Inputs: loc (required), path (required).

read_structureA

读取文档大纲:docx 逐段 {loc,text};xlsx 每 sheet 维度与抽样;pptx 每页形状文本。返回 {format, items|sheets|slides}。

Inputs: path (required), max_items.

restore_versionA

将文档直接回退到 list_versions 给出的某个版本(回到该版本锚点:文档被覆盖为该版本内容,回退前不会快照当前状态,因此版本记录不会增加)。⚠️ 警告:该版本之后的全部修改将丢失,且回退本身不可通过新快照撤销(但既有版本链仍保留,可再回退到其它版本)。回退后旧标注的 loc 索引可能失效,应重新 read_structure 遍历后再继续编辑。返回 {restored_index, path, version_path, versions}。

Inputs: path (required), index (required), session_id.

template_replaceA

批量替换 docx 文档中的 {{变量}} 模板占位符。variables 为 {变量名: 值} 映射;未提供值的变量保持原样并在 missing_vars 中列出。同段落多处占位符按从右到左应用保证坐标正确;跨 run 断裂的占位符也能正确替换。返回 {matched, replaced, missing_vars, applied}。

Inputs: path (required), variables (required), session_id.

wait_for_annotationsA

阻塞等待标注会话完成 / 取消 / 窗口关闭,返回用户提交的标注列表。返回 {session_id, status, reason?, annotations, next?},status 为 done | cancelled | closed | timeout。缺省永不超时(timeout_seconds 不传则一直等待直到用户提交/取消/关闭窗口,或 agent 退出);如客户端对单次调用有超时上限,可传 timeout_seconds 设一个安全值,被截断或超时后再次调用本工具继续等待即可(会话在服务端持续存在,不会因单次调用被截断而取消)。status=closed 表示标注窗口已关闭:reason=window_closed/page_unload 是用户主动关闭——本轮标注流程已结束,不要再调用本工具或 annotate_document(除非用户明确要求继续),直接总结结果即可;reason=window_lost(窗口崩溃/被强杀)或 agent_cancelled/agent_exited 时才考虑询问用户是否重开。status=done 且存在标注时:处理这些标注时请在 apply_edit / template_replace / find_replace / restore_version 中带上本返回值中的 session_id(确保标注窗口重载到正确的会话,多对话共享同一 server 时尤为重要);处理完后(窗口会自动重载修改后的内容)应再次调用本工具等待用户下一轮标注。status=done 且标注为空:用户确认本轮无需修改,标注流程已结束(窗口已自动关闭),不要再调用本工具或 annotate_document,直接总结结果即可。

Inputs: session_id (required), timeout_seconds.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4/5.0

    Scored across 11 tools

    Disambiguation4/5

    Most tools have clearly distinct roles in the read-annotate-edit-version workflow. The main ambiguity is among apply_edit, find_replace, and template_replace, which all modify documents, though their descriptions clarify different use cases.

    Naming Consistency4/5

    Tool names are consistently lowercase snake_case and mostly follow a verb_noun pattern like read_location, apply_edit, and list_versions. template_replace and find_replace deviate slightly from the verb-first style, but the naming remains predictable and readable.

    Tool Count5/5

    11 tools is well within the ideal range, and each tool covers a necessary part of the fine-tuning workflow: reading, annotating, waiting, editing, previewing, and versioning. There are no obvious filler or redundant tools.

    Completeness5/5

    The toolset covers the full annotation loop: open a session, wait for annotations, apply edits, preview changes, and roll back via versions. Features like document creation or export are outside the stated fine-tuning purpose, so no significant gaps are apparent.

    Related MCP Connectors

    Related MCP Servers