本方案决策:Agent 对外读写文档统一使用 Markdown 契约,服务端/CLI/MCP 负责 Markdown 与 BlockNote blocks 的双向受控转换;不要求普通 Agent 直接生成或解析 BlockNote JSON。
短期保持现有 contentMarkdown 契约稳定,避免破坏已经完成的 CLI / Skills / MCP / Web Agent dry-run 链路。下一步重点不是暴露 BlockNote JSON,而是把当前过于简化的 Markdown 双向转换器升级为可验证、可回滚、可跨端复用的受控内容适配层。
核心结论:
- 对外契约:
contentMarkdown继续作为 CLI、Skills、MCP、Web Agent 的默认读写视图。 - 内部存储:
documents.content继续保存 BlockNote JSON string。 - 转换归属:Markdown -> BlockNote blocks 与 BlockNote blocks -> Markdown 都在服务端共享包完成,而不是让 Agent 自行拼或解析 BlockNote JSON。
- 安全链路:所有 Agent 写入继续遵循
Dry-run -> Preview -> User Confirmation -> Commit。 - 可扩展性:未来只给高级/内部工具开放
contentBlocks,且必须经过 schema 校验、归一化和 feature flag,不作为默认 Agent 接口。
- Web 编辑器在
apps/web/src/components/Editor.tsx中使用 BlockNote,初始化时把initialContent解析为PartialBlock[],保存时写入JSON.stringify(editor.document, null, 2)。 - Convex
documents表在packages/convex/schemas/document.ts中把content定义为v.optional(v.string()),注释明确为 BlockNote 格式。 - Mobile 在
apps/mobile/app/(home)/document/[documentId].tsx中通过@notion/business/content-compat把已存内容转换为 TenTap HTML,再把 HTML 序列化回 BlockNote JSON。 packages/business/content-compat/index.ts已存在 BlockNote JSON <-> HTML 的兼容层,支持 heading、list、check list、quote、code block、divider、image、inline styles、link 等结构。
- CLI
packages/my-notion-cli/src/commands/docs.ts的docs create/update/import都只接收 Markdown,并传给contentMarkdown。 - CLI
docs fetch/export、MCPmy_notion_docs_fetch和 Agent 二次编辑都依赖响应里的contentMarkdown,因此反向转换不是附属能力,而是修改文档闭环的前提。 - MCP
packages/my-notion-cli/src/mcp/server.ts的my_notion_docs_create和my_notion_docs_update都暴露contentMarkdown,写工具默认dryRun: true。 - Web Agent
apps/web/src/lib/agent/tools/document-write.ts和apps/web/src/lib/agent/tools/definitions.ts也只让 LLM 生成contentMarkdown,且只返回 dry-run 预览。 - CLI 类型
packages/my-notion-cli/src/types.ts中的DocumentResult同时返回content和contentMarkdown,其中contentFormat表示内部格式。
packages/convex/documents/logic/markdown.ts的markdownToBlockNoteJson目前只是按行拆分,每行生成一个paragraph。blockNoteJsonToMarkdown也只处理content为 string 或 text inline array 的简单场景。- 这会导致 Agent 输出的 Markdown 标题、列表、任务列表、代码块、引用、分隔线、链接、粗体、斜体等结构在写入后丢失或退化。
- 反向读取同样存在损耗:如果 BlockNote -> Markdown 不完整,Agent 会在“读取 -> 修改 -> 写回”链路中丢失结构。
- 当前转换器能维持“可写入、可读取”,但不足以支撑高质量 Agent 写文档和修改文档体验。
milestones/M16-cli-skills-mcp-agent-docs.md 已将“Agent 写入格式策略”列为 P0:需要在“服务端/CLI 增加 Markdown -> BlockNote blocks 转换器”和“让 Agent 直接生成受约束 BlockNote JSON”之间形成决策。
推荐并采纳:
Agent / CLI / Skills / MCP / Web Agent
-> contentMarkdown
-> shared Markdown parser / normalizer
-> BlockNote blocks
-> documents.content
-> shared BlockNote serializer
-> contentMarkdown
-> Agent / CLI / Skills / MCP / Web Agent
不推荐把普通 Agent 的默认输入/输出改为 BlockNote JSON:
Agent
-> constrained BlockNote JSON
-> validation / normalization
-> documents.content
- Markdown 是 Agent 最稳定、成本最低的生成格式,LLM 对 Markdown 的遵循能力明显优于复杂嵌套 JSON。
- CLI、Skills、MCP、Web Agent 当前已经围绕
contentMarkdown建立契约,继续沿用能最大限度减少破坏性变更。 - BlockNote JSON 是编辑器内部格式,细节受 BlockNote 版本、Web/Mobile 兼容层和自定义扩展影响,不适合作为通用外部输入协议。
- Markdown 转换器可以集中演进、统一测试、统一降级,错误恢复比“每个 Agent 自行生成 JSON”更可控。
- CLI 已经具备读取、导出和更新文档能力;因此 BlockNote -> Markdown 必须是一等能力,保证 Agent 二次编辑不丢结构。
- 移动端已有 HTML/BlockNote 兼容层,未来可把 Markdown 转换能力放到共享包,形成 Web / Mobile / CLI / Convex 一致的内容适配策略。
优势:
- Agent 侧最简单,提示词和工具 schema 稳定。
- 支持 CLI 文件流,适合
--content-file、docs import、MCP 工具调用。 - 错误恢复容易:非法 Markdown 可以按 plain text 或 paragraph 降级。
- 与
docs export --format markdown、搜索、RAG、知识库摘要天然一致。 - 便于在 dry-run 中展示人类可读预览。
- 支持 Agent “读取 Markdown 视图 -> 修改 Markdown -> 写回 BlockNote”的完整闭环。
劣势:
- 会存在 Markdown 与 BlockNote 表达能力差异。
- 高级 BlockNote 能力需要额外扩展 Markdown 约定,例如 callout、columns、toggle。
- 需要维护双向转换器和 round-trip 测试。
优势:
- 理论上转换损耗更低。
- 可精确表达 BlockNote block 类型、props、children 和 inline styles。
- 对复杂布局或编辑器内部操作更接近最终状态。
劣势:
- Agent 生成嵌套 JSON 的稳定性和可读性差,尤其是长文、代码块、列表嵌套和 escape 场景。
- schema 复杂,错误定位成本高,用户很难审阅 dry-run。
- BlockNote 版本升级可能导致外部 Agent 输出失效。
- 对 Skills/MCP 文档和第三方 Agent 适配成本高。
- 一旦 JSON 部分合法但语义错误,可能比 Markdown 退化更难恢复。
暂不作为默认方案。
原因:
- 短期会扩大接口面,增加 CLI / MCP / Web Agent / Convex / 文档 / 测试矩阵。
- 双通道会让 Agent 不知道优先选哪种格式,增加使用歧义。
- 可作为长期高级能力,但必须通过 feature flag、明确 schema 版本和独立验证工具引入。
文件:
packages/convex/documents/logic/markdown.ts- 可考虑新增
packages/convex/documents/logic/markdown.test.ts
做法:
- 将当前“按行 paragraph”实现升级为受控双向转换器。
markdownToBlockNoteJson支持最小必需语法:#到######heading ->headingblock,props.level为 1-6。- 空行分段 ->
paragraph。 -/*/+列表 ->bulletListItem。1.列表 ->numberedListItem。- [ ]/- [x]->checkListItem。>->quote。- fenced code block ->
codeBlock,保留 language。 ---/***/___->divider。- inline bold / italic / code / link -> BlockNote inline content。
blockNoteJsonToMarkdown反向支持同一组结构:heading->#到######。paragraph-> Markdown 段落。bulletListItem/numberedListItem/checkListItem-> 对应列表。quote->>。codeBlock-> fenced code block。divider->---。- inline text styles -> bold / italic / code / link。
- 对不支持的 Markdown 节点降级为 paragraph,不抛非必要异常。
- 对不支持的 BlockNote block 降级为纯文本 Markdown,优先保留文字内容、顺序和层级。
- 输出统一补齐 BlockNote 常用 props:
backgroundColor、textColor、textAlignment。 - block id 可选择不生成或生成稳定前缀;需要与 Web BlockNote 初始化兼容。
为什么:
- 保持外部 Markdown 契约不变,同时显著提升读取、写入和二次编辑质量。
- 让 CLI/MCP/Agent 同时受益,不需要改多个入口。
文件:
packages/my-notion-cli/src/types.tspackages/convex/cli/index.tspackages/convex/documents/logic/markdownWrite.ts
做法:
- 保持响应中的
contentFormat: "blocknote-json"。 - 明确
contentMarkdown是从内部 BlockNote JSON 序列化得到的 Agent 可编辑 Markdown 视图。 - 可新增内部常量,例如
CONTENT_FORMAT_BLOCKNOTE_JSON = "blocknote-json",避免散落字符串。 - 暂不对外新增
contentBlocks参数。 - dry-run 继续返回
contentMarkdown,不要求客户端展示 BlockNote JSON。
为什么:
- 让外部消费者清楚:外部读写视图是 Markdown,内部存储主格式是 BlockNote JSON,同时提供稳定 Markdown 回读。
文件:
apps/web/src/lib/agent/tools/definitions.tsapps/web/src/lib/agent/tools/document-write.ts
做法:
- 明确
contentMarkdown是唯一默认写入格式。 - 明确读取文档后,Agent 应基于返回的
contentMarkdown生成更新内容。 - 在 tool description 中补充“不要生成 BlockNote JSON;由系统转换为编辑器块”。
- dry-run 结果可增加
inputFormat: "markdown"和targetFormat: "blocknote-json"元数据。
为什么:
- 减少 Agent 误生成 JSON 的概率。
- 让 UI 后续展示预览时能清楚说明转换边界。
文件:
packages/my-notion-cli/README.mdpackages/my-notion-skills/README.mdpackages/my-notion-skills/my-notion-docs/SKILL.mdpackages/my-notion-skills/my-notion-mcp/SKILL.mdpackages/my-notion-skills/my-notion-docs/references/cli-commands.md
做法:
- 统一写明:Agent 只生成 Markdown,不生成 BlockNote JSON。
- 说明系统会在服务端完成 Markdown <-> BlockNote blocks 双向转换。
- 说明
docs fetch/export返回的 Markdown 是系统从 BlockNote JSON 序列化得到的 Agent 可编辑视图。 - 说明推荐 Markdown 子集和不支持内容的降级策略。
- 修改 Skills 后必须运行:
pnpm sync:skills
pnpm sync:skills:package
pnpm sync:skills:check为什么:
- Agent 生态依赖文档和 Skill 指令;必须从源头减少格式歧义。
文件:
packages/convex/documents/logic/markdown.test.ts或包内现有测试目录。packages/my-notion-cli/__tests__/docs-command.test.tsscripts/e2e-my-notion-mcp-sdk-client.mjs
测试范围:
- heading、paragraph、bullet list、numbered list、check list、quote、code block、divider。
- inline bold、italic、code、link。
- Markdown -> BlockNote -> Markdown 的 round-trip 语义不丢高频结构。
- BlockNote -> Markdown -> BlockNote 的 round-trip 至少保留文本、层级和常用块类型。
- append 模式不会破坏已有 blocks。
- fetch/export 能返回可读 Markdown。
- MCP dry-run 仍不写入,真实写入后 fetch 能读取 Markdown。
为什么:
- 该策略的核心风险在转换损耗;必须用测试锁住高频语法。
文件:
docs/agent-document-write-format-strategy.mddocs/README.mdmilestones/M16-cli-skills-mcp-agent-docs.mdprogress/20260527-20260531-consolidated.md
做法:
- 将本方案沉淀为长期维护文档。
- 在
docs/README.md增加索引。 - 在 M16 中将 P0 写入格式策略从“待决策”改为“已决策:Markdown 外部读写契约 + 服务端 BlockNote 双向转换”。
- 在 progress 中追加阶段摘要。
为什么:
- 这是跨 CLI / Skills / MCP / Web Agent / Mobile 的长期接口决策,不能只存在聊天记录中。
CLI / MCP / Web Agent 默认写入参数:
{
"title": "Project Plan",
"contentMarkdown": "# Project Plan\n\n- Goal\n- Scope",
"dryRun": true
}更新参数:
{
"documentId": "j57...",
"contentMarkdown": "## Update\n\nNew section.",
"mode": "append",
"dryRun": true
}{
"dryRun": true,
"confirmationRequired": true,
"inputFormat": "markdown",
"targetFormat": "blocknote-json",
"contentMarkdown": "# Project Plan\n\n- Goal\n- Scope"
}真实写入后:
{
"id": "j57...",
"title": "Project Plan",
"content": "[{\"type\":\"heading\",...}]",
"contentMarkdown": "# Project Plan\n\n- Goal\n- Scope",
"contentFormat": "blocknote-json"
}CLI docs fetch/export、MCP my_notion_docs_fetch 和 Web Agent document read 应优先让 Agent 使用 contentMarkdown:
{
"id": "j57...",
"title": "Project Plan",
"content": "[{\"type\":\"heading\",...}]",
"contentMarkdown": "# Project Plan\n\n- Goal\n- Scope",
"contentFormat": "blocknote-json"
}Agent 修改已有文档时,流程应是:
fetch document -> read contentMarkdown -> generate updated Markdown -> dry-run update -> user confirms -> commit
暂不在 CLI / MCP / Skills 默认文档中开放:
{
"contentBlocks": []
}如果未来开放,必须满足:
- 使用
contentFormat: "blocknote-json"或inputFormat: "blocknote-json"显式声明。 - 只允许受控 block 类型。
- 服务端校验和归一化。
- dry-run 展示 Markdown/HTML 预览,而不是只展示 JSON。
- 默认关闭,仅内部或高级工具 feature flag 启用。
Agent generates Markdown
-> CLI / MCP / Web Agent receives contentMarkdown
-> dry-run returns Markdown preview and confirmationRequired
-> user confirms
-> Convex mutation receives contentMarkdown
-> markdownToBlockNoteJson converts to BlockNote blocks
-> documents.content stores BlockNote JSON string
-> Web Editor reads JSON into BlockNote
-> Mobile converts BlockNote JSON to HTML/TenTap view
-> fetch/export returns contentMarkdown via blockNoteJsonToMarkdown
-> Agent edits returned Markdown
-> update append/overwrite repeats the same conversion path
- 空内容:创建时允许无正文但标题必须非空;更新时至少 title 或 contentMarkdown 之一非空。
- 非法 Markdown:不应导致写入失败,应尽量降级为 paragraph。
- 未闭合代码块:按 code block 直到文末处理,或降级为 paragraph;需测试固定。
- 深层嵌套列表:首版可扁平化或有限支持,避免生成不兼容 children。
- HTML in Markdown:默认转义为文本,不执行原始 HTML,避免 XSS 和跨端不一致。
- 图片:短期只支持普通 Markdown 链接;图片写入需结合 EdgeStore 上传能力后再定义。
- 表格:短期可降级为 paragraph 或 code block;BlockNote 表格支持稳定前不承诺。
- Callout / toggle / columns:短期不支持;未来通过扩展 Markdown 指令或内部 blocks schema 增加。
- JSON 注入:即使 Markdown 内容看起来像 JSON,也按 Markdown 文本处理,不作为 BlockNote JSON 执行。
- BlockNote 独有结构:反向转换时优先保留文字和顺序,无法表达的 props 可丢弃,但不能导致读取失败。
- Agent 二次编辑:
overwrite前必须先 fetch,基于contentMarkdown生成新 Markdown;复杂结构无法无损时应提示用户风险。
- 假设外部 Agent 的主要目标是“可靠生成可读文档”,不是精确控制 BlockNote 内部布局。
- 假设 CLI / Skills / MCP 的对外兼容性优先级高于高级排版能力。
- 决策
contentMarkdown是唯一默认写入输入,也是 Agent 默认读取和二次编辑视图。 - 决策
documents.content继续存 BlockNote JSON string,不做数据库 schema 迁移。 - 决策 Markdown/BlockNote 双向转换器应尽量放在共享业务/Convex 可复用层,避免 Web、Mobile、CLI 各自实现。
- 决策高级 BlockNote JSON 输入暂不进入 M16 当前发布闭环。
- 新增
docs/agent-document-write-format-strategy.md。 - 更新
docs/README.md索引。 - 更新 M16 后续规划,标记格式策略已决策。
- 更新 progress 汇总。
- 替换
packages/convex/documents/logic/markdown.ts的简化实现。 - 增加双向转换器单测和 round-trip 测试。
- 确保 CLI/MCP/Web Agent 不需要改调用参数。
- 更新 Web Agent tool description。
- 更新 CLI README / Skills 文档。
- 运行 Skills 同步与漂移检查。
- 用 CLI 创建包含标题、列表、代码块的文档。
- 用 MCP Client dry-run 和真实写入验证。
- 用 Web Editor 打开文档确认结构可编辑。
- 用 Mobile 读取同一文档确认兼容层不崩溃。
文档和 Skill:
pnpm sync:skills
pnpm sync:skills:package
pnpm sync:skills:checkCLI / MCP:
pnpm --filter @mynotion/cli test
pnpm --filter @mynotion/cli typecheck
pnpm --filter @mynotion/cli build
pnpm e2e:mcp
pnpm e2e:mcp:clientWeb / Convex:
pnpm --filter @notion/web exec convex codegen
pnpm --filter @notion/web typecheck
pnpm --filter @notion/web build必要时补充:
pnpm --filter @notion/web lint- 技术文档明确结论:默认选择 Markdown 读写视图,不让普通 Agent 直接生成或解析 BlockNote JSON。
- CLI / MCP / Skills / Web Agent 文档和 tool schema 中没有格式歧义。
- 转换器支持常用 Markdown 子集,并有单测覆盖。
- 反向转换器支持同一组高频 BlockNote block,并有 round-trip 测试覆盖。
docs create/import/update仍保持向后兼容。docs fetch/export --format markdown返回适合 Agent 二次编辑的 Markdown。- MCP 写工具仍默认
dryRun: true,dry-run 仍包含confirmationRequired: true。 - 真实写入后的文档能在 Web BlockNote 编辑器打开,并能被 Mobile 兼容层读取。
- fetch/export 能返回可读 Markdown,满足 Agent 后续读取和增量更新。
- 是否引入第三方 Markdown parser,还是先实现项目内轻量 parser?
- 推荐:先评估
micromark/mdast生态,若包体和 ESM 兼容可接受,优先用成熟 parser;否则实现受控轻量 parser。
- 推荐:先评估
- 是否把转换器从
packages/convex移到packages/business/content-compat?- 推荐:中期迁移到共享包,但需要确认 Convex runtime 是否能安全依赖该包及其依赖。
- 表格、图片、callout 是否进入第一版?
- 推荐:不进入第一版;先支持高频结构,复杂结构后续用扩展语法或受控 blocks 能力。
选择 Markdown <-> BlockNote blocks 双向转换 作为 Agent 读写文档主路径。
BlockNote JSON 应定位为 My-Notion 内部编辑器存储格式,而不是默认 Agent 生成或编辑协议。短期投入应集中在“可靠双向 Markdown 转换器 + 明确格式契约 + round-trip 测试覆盖 + 文档同步”上,这能在不破坏现有 CLI/MCP/Skills 能力的前提下,显著提升 Agent 读取、修改和写入文档质量。