UniDoc MCP 协议
从理解,到动手实现。步骤、协议与可复制的示例,都在这份文档中。
版本:2026-09-16
UniDoc MCP 是编辑器之外的可选 AI 能力层。没有 MCP 客户端时,编辑器、DOCX/UDoc
转换和离线 WASM 路径照常运行;MCP 也不会暴露 Rust 后端源码或浏览器 Cookie。
端点
生产端点为 https://app.unidoc.top/mcp。客户端应先读取:
GET /.well-known/oauth-protected-resource/mcp
GET /.well-known/oauth-authorization-server服务器支持 OAuth 2.1 授权码流程和 S256 PKCE:
POST /oauth/mcp/register
GET /oauth/mcp/authorize
POST /oauth/mcp/consent (UniDoc 登录后的确认页)
POST /oauth/mcp/token动态注册只接受 ChatGPT 官方回调、本机 localhost/127.0.0.1 回调,或由部署者通过UNIDOC_MCP_REDIRECT_URIS 明确配置的精确 URI。客户端不得把 access token 放进 URL;调用
MCP 时使用:
Authorization: Bearer <access_token>
Content-Type: application/json授权范围为 mcp:read、可选的 mcp:write 和单独的 mcp:publish。网站发布必须同时
持有 mcp:read 与 mcp:publish;已有的 read/write 授权不会自动获得公开发布权限。
文档编辑仍使用 mcp:write。发布工具只接受显式获得 mcp:publish 的 OAuth grant,
浏览器 Cookie 回落路径不会自动获得该权限。新增发布授权是补充权限;已有登录会话有效时
不要求用户重新登录。当前 UniDoc 浏览器会话也可在受保护环境中调用文档工具;
匿名公开快照和只读分享不会被提升为 MCP 身份。
JSON-RPC
请求使用 JSON-RPC 2.0:
{"jsonrpc":"2.0","id":1,"method":"tools/list"}初始化后调用 tools/call:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "unidoc_list_blocks",
"arguments": {"documentId": "doc_…"}
}
}工具结果同时放在 content[0].text(JSON 字符串)和 structuredContent,便于不同 MCP
客户端选择文本或结构化读取。未知方法返回 JSON-RPC -32601;工具业务错误返回result.isError=true。
生产 MCP 请求与 AI 接口共用账户配额:默认每分钟 12 次请求、12 万预算单位、3 并发;
每天 200 次请求、100 万预算单位,另受全局上限约束。超额返回 HTTP 429 和Retry-After,配额持久保存,重启不会清空。认证失败返回 HTTP 401;不要把这两种
HTTP 错误当成 tools/call 的成功结果。
工具
| 工具 | 范围 | 用途 |
|---|---|---|
unidoc_open_document | read | 在隔离 MCP 会话中打开已校验的 UDoc JSON |
unidoc_list_documents | read | 列出当前 MCP 身份打开的文稿 |
unidoc_get_document | read | 读取完整 UDoc JSON 和 revision |
unidoc_get_capabilities | read | 读取模型、批注和写入约束 |
unidoc_list_blocks / unidoc_get_block | read | 按稳定 para_id 读取段落 |
unidoc_list_comments | read | 读取原生批注、回复和扩展字段 |
unidoc_validate | read | 校验 UDoc JSON,不打开会话 |
unidoc_create_comment | write | 按 Unicode 文本偏移创建原生批注 |
unidoc_reply_comment | write | 通过 parentId 回复,不创建第二个锚点 |
unidoc_update_comment | write | 修改批注正文,保留锚点 |
unidoc_resolve_comment | write | 设置 resolved |
unidoc_delete_comment | write | 删除批注、回复和持久锚点 |
unidoc_apply_patch | write | 原子执行段落级 HTML 更新、插入、删除 |
unidoc_export_udoc | read | 导出当前 UDoc 容器的 base64 |
unidoc_publish_website | read + publish | 将显式提交的 HTML 保存为账户归属的 UDoc 来源,并发布为独立公网网站 |
unidoc_get_website | read | 按 siteId 读取当前账户所属网站的发布信息 |
unidoc_export_website | read | 按 siteId 导出当前账户所属网站的原生 .udoc 来源 |
AI 发布网站
AI 已持有网页 HTML 时,可直接调用 unidoc_publish_website,不必先开编辑器、创建文稿
会话或调用浏览器上传接口。这是立即公开发布操作,只有用户要求发布的内容才应提交。
{
"jsonrpc": "2.0",
"id": 20,
"method": "tools/call",
"params": {
"name": "unidoc_publish_website",
"arguments": {
"html": "<!doctype html><html lang=\"zh-CN\"><meta charset=\"utf-8\"><title>学习实验室</title><button onclick=\"this.textContent='实验开始'\">开始实验</button></html>",
"title": "学习实验室",
"requestId": "lesson-site-20260916-v1"
}
}
}html 和 requestId 必填;title、baseUrl 可选。baseUrl 用于解析 HTML 中的相对
资源地址,不是让服务器抓取该 URL。它应指向资源实际所在的公开 HTTPS 基址,例如https://assets.example.com/lesson/index.html。本地 HTML 旁边的图片、CSS、JS 文件不会
自动上传;请把这些资源内联,或先提供可公开访问的 HTTPS 构建产物。
成功结果的 structuredContent 包含 siteId、别名 id、url、status:"published"、title、contentSha256、bytes、createdAt、replayed,以及source:{"kind":"udoc","persisted":true}。将服务器返回的 URL 交给用户,不自行猜测
地址。公开页面运行在 https://sites.unidoc.top/,保留脚本交互,并与编辑器账户隔离。
其沙箱环境不能被当成原网站登录会话、Cookie、同源存储或后端服务的迁移;外部模块、API
和字体仍须满足各自的可访问性及 CORS 条件。
发布会持久保存账户归属的 .udoc 来源和发布收据。相同账户以相同 requestId 和相同
内容重试,应得到原发布结果;同一个 ID 配不同内容会被拒绝。发布是不可变快照;更新网站
内容时使用新的 requestId 创建新版本。unidoc_get_website 和unidoc_export_website 接收 {"siteId":"服务器返回的站点 ID"},只允许来源所属账户
读取发布信息和导出 UDoc,不因持有公开 URL 而获得来源读取权限。查询返回上述发布元信息;
导出返回 siteId、fileName、mimeType: "application/vnd.unidoc.udoc3" 与 base64。
将 base64 解码为原始字节,以返回的 .udoc 文件名保存,不改写成 HTML 或 JSON。
完整调用说明见 AI 网页发布说明。浏览器原有 /sites/publish
流程仍校验其 OmniDoc sourceObjectId;MCP 不通过伪造 Cookie、Origin 或云对象 ID 来
调用该浏览器接口。
批注模型
批注是 UDoc 的原生 comments[] 项。根批注的正文块 HTML 中保存三个零宽标记:
<span data-ud-comment-id="7" data-ud-comment-edge="start" …></span>
<span data-ud-comment-id="7" data-ud-comment-edge="end" …></span>
<span data-ud-comment-id="7" data-ud-comment-edge="reference" …></span>点批注只保存 reference。回复是带 parentId 的普通 comments[] 项,复用根批注的
范围,所以 DOCX ↔ UDoc 不会因回复增加第二个范围。start/end 是渲染后文本的 Unicode
标量偏移,HTML 标签和实体不计作 HTML 字节;客户端应先读取段落再提交偏移。
编辑器的 CSS Highlight 只是显示层,导出 DOCX 时由转换器生成 Word 原生黄色批注范围;
它不与“开始”面板的文字高亮状态混用。
写入一致性
文档修改工具都要求:
{"expectedRevision":3,"requestId":"ai-run-42-op-7"}expectedRevision 不匹配时不会写入,返回 structuredContent.conflict=true 以及当前
revision。相同 requestId 重试会返回第一次的结果;同一个 ID 配不同参数会被拒绝。批量
patch 会先在副本上执行和校验,失败时整批不提交;含有原生批注标记的块不能被 patch 删除,
避免生成无锚点批注。
网站发布不修改进程内文稿 revision,使用上述持久化 requestId 机制,不要求expectedRevision。
部署边界
第一版 MCP 会话存放在当前 Rust 进程内存中,unidoc_open_document 接收显式 UDoc JSON,
适合作为 AI 转换、审阅和局部编辑的安全能力层。它不会自动读取当前浏览器画布、Vault 或
其他账号的文稿。生产多实例部署应在网关保持会话粘性,或后续把 McpState 接到同一账号
授权的持久文稿存储后再开放跨实例会话。这项内存会话边界不适用于网站的持久来源与发布
收据;后端部署必须保留站点数据目录及其所有权记录。