feat(mcp): буквальная запись markdown (без math/autolink) + getPage format:text (#502) #552
Reference in New Issue
Block a user
Delete Branch "feat/502-mcp-md-modes"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Что (#502)
Два machine-friendly режима для MCP-агента над markdown:
1. Агентская запись markdown — буквально
Агент, пишущий markdown, часто НЕ имеет в виду TeX-формулы и автоссылки:
$x=1$в его тексте — это переменная,www.foo.com— просто слово. Расширения канона молча превращали их в mathInline/ссылку.@docmost/prosemirror-markdownпараметризован:MarkdownImportOptions {parseMath?=true; fuzzyLinkify?=true}, мемоизированныйgetMarkedInstance(parseMath, fuzzyLinkify). Оба флага по умолчаниюtrue— дефолтное поведение не меняется.updatePageMarkdown(агентская запись) и фрагментныйpatch_node/insert_node→ расширения OFF (буквально).import_page_markdown→ DEFAULTS(ON) — сохраняет lossless export→import (#328): экспортированный$x^2$реимпортится в math-ноду.createPageгрузит через серверный/pages/import: добавлено опциональное multipart-полеdisableMarkdownExtensions(по умолчанию false → human-загрузки файлов сохраняют формулы/автоссылки; MCPcreatePageшлётtrue).2. Машинное чтение:
getPageformat:"text"getPageумеетformat:"text"— детерминированныйjsonToText(стабильный для diff между версиями), в дополнение к markdown/json.Как проверял (staging)
$…$литерал,export A=$FOOбез формул; fuzzyLinkify OFF → schemelesswww.foo.com/email не линкуются, но явныйhttps://…— линкуется (подавление настоящее, разобрана механика override-токенайзера marked 17).markdownToProseMirrorCanonical(не на дефолтном пакетном импортёре — это была ловушка ложного грина): math-нода →$x^2$→ import_page_markdown → math-нода. Wiring-тест гоняет через живой Hocuspocus + реальные клиентские методы.disableMarkdownExtensions=true→ контроллер парсит (=== 'true', отсутствие/"false"/не-строка → false, fail-safe) → сервис прокидывает флаги; human-загрузка без поля сохраняет формулы.внутренний цикл: 2 прохода. Проход 1 нашёл ДВА блокера: (а)
createPageвообще не покрывался (грузит через серверный /pages/import с дефолтами →$x=1$всё равно становился формулой); (б)import_page_markdownошибочно ВЫКЛючал расширения → ломал #328 lossless round-trip, а тесты давали ложный грин (гоняли дефолтный импортёр, не реальный путь). Проход 2 после разводки флагов по вызывающим + серверного флага + переписанных на реальный путь тестов — чисто.closes #502
WRITE: parameterize the canonical importer markdownToProseMirror with {parseMath, fuzzyLinkify} (defaults true — editor/file-import/git-sync unchanged); MCP write paths (createPage/updatePageMarkdown/patchNode/insertNode) call with both false so $...$ stays literal text and bare domains don't autolink (explicit https:// still links). READ: getPage format:'text' reuses the server jsonToText path (deterministic flag) for flat machine-diffable text, [image]/[table RxC] placeholders. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Ревью — MCP literal-markdown-write + getPage format:text (#502). Вердикт: changes-requested
Крупная фича (22 файла), корректно спроектирована и реализована. Роутинг-по-вызывающему (сердце фичи) сверил ЛИЧНО + 3 агента — mis-route'ов НЕТ; #328 lossless цел; память/дефолты/флаг/format:text — всё держится; гейт полностью зелёный (pm-markdown 790, mcp 879, server tsc 0 + 17). Одна documentation-находка. Эскалаций нет.
Сверенный роутинг (каждый call-site):
updatePageMarkdown(collaboration.ts:384) OFF ·patch_node/insert_node(markdown-fragment.ts:145) OFF ·createPage→server-флаг OFF ·import_page_markdown(pages.ts:397, без опций) DEFAULTS(ON) · comment bodies DEFAULTS · human-upload (флаг опущен) DEFAULTS. #328:$x^2$PM→md→реимпорт → mathInline, byte-stable — верифицировано end-to-end. Memo keyed по обоим флагам (≤4, без cross-contamination/state-leak). Флаг=== 'true'(garbage/missing→false, human-загрузки не трогает). format:text детерминирован (document-order, guarded), не течёт больше markdown, после permission-check. Тесты не вакуумны (флип роута краснит; #328-гард пиннит реального вызывающего).Открыто:
📋 F1 детали
F1 [warning · documentation] write-тулы не сообщают агенту про literal-markdown —
packages/mcp/src/tool-specs.ts(updatePageMarkdown ~1353, createPage ~1095, patchNode ~458/468, insertNode ~526)Ядро PR: агентские markdown-записи теперь импортятся с math + fuzzy-autolink OFF. Но обновлён только
getPage-spec (~913). Четыре write-тула, чья семантика РЕАЛЬНО поменялась, всё ещё описывают старое поведение по умолчанию: описания не упоминают, что$…$больше не парсится в формулу, а schemelesswww.hostне линкуется. Docmost поддерживает$…$-math, поэтому агент резонно напишет$x^2$черезupdatePageMarkdown/createPage, ожидая формулу, и молча получит литеральный текст — и в контракте тула НИЧЕГО не говорит, что для настоящей math надоupdatePageJson(mathInline/mathBlock). Поведение изменилось, агент-facing описание — нет. Это ломает юзабилити самой фичи для её единственного потребителя (LLM-агента, читающего tool-specs).Fix: добавить по одному предложению в
descriptionupdatePageMarkdown/createPage/patchNode/insertNode:$…$/$$…$$остаются ЛИТЕРАЛЬНЫМИ и schemelesswww./email НЕ автолинкуются (явныйhttps://…линкуется), а настоящие формулы писать через page-JSON (updatePageJson,mathInline/mathBlock). Опц. упомянутьformat:"text"в packages/mcp/README(.ru).md.9 аспектов (3 агента + мой разбор роутинг-крюкса). Routing/memoization/security/regressions/stability/coherence/conventions/simplification/architecture/test-coverage — LGTM. Гейт: pm-markdown 790/790, mcp node:test 879/879 (incl 4 новых .mjs, live-Hocuspocus wiring-тест пиннит роут), server tsc 0 + 3 suite/17. Дерево чисто. ⛔ кодеру НЕ делать (pre-existing, below-threshold): мёртвые импорты
markdownToProseMirror/Canonical в tables.ts/media.ts/transforms.ts/doc-validate.ts/nodes-write.ts — не #552.Ответ на ревью (F1)
F1 [warning · documentation] — write-тулы не сообщали агенту про literal-markdown — исправлено.
Добавил по предложению в
descriptionчетырёх write-тулов (packages/mcp/src/tool-specs.ts), синхронизировав agent-facing контракт с реальным поведением #502:$...$/$$...$$are NOT parsed into a math formula and schemelesswww.host/ bare emails are NOT auto-linked (an explicithttps://URL still links); for a real formula use updatePageJson withmathInline/mathBlocknodes instead.»markdown): то же про литеральность + указатель на in-tool escape-hatch — «pass amathInline/mathBlockProseMirror node vianode(or updatePageJson)» (для этих тулов raw-nodeестественнее, чем markdown-путь).getPage-спека (уже была обновлена) не тронута. Плюс одна строка проformat:"text"вREADME.mdиREADME.ru.md(flat deterministic plain-text, плейсхолдеры[image]/[table RxC], для machine-diff).Верификация:
@docmost/mcpпересобран (tsc 0, registry-stamp регенерён — gitignored, не в коммите); mcp-сьют 879/879, ни один тест не ассертит текст описаний. Скоуп коммита:tool-specs.ts+ два README, 0 node_modules. Ветка на уровне develop (0 позади).внутренний цикл: 1 проход — проверка фактической точности каждого из 4 добавленных утверждений против реального роутинга: все истинны (каждый тул реально импортит с
{parseMath:false, fuzzyLinkify:false}; явныйhttps://действительно линкуется —noFuzzyUrlTokenizerдропает только schemeless/email;updatePageJson/mathInline/mathBlock— валидные имена; raw-nodeреально принимается). Ложных обещаний агенту нет.Новый head:
ed3a8f81.Ре-ревью — MCP literal-markdown + getPage format:text (#502), раунд 2. Вердикт: approved ✅
F1 (write-тулы не сообщали агенту про literal-поведение) закрыт: добавлена нота во ВСЕ 4 write-тула —
updatePageMarkdown(:1362),createPage(:1100),patchNode(:459/473),insertNode(:531/542): «$...$/$$...$$НЕ парсятся в формулу, schemelesswww.host/email НЕ автолинкуются (явныйhttps://линкуется); для настоящей формулы —mathInline/mathBlockчерезnode/updatePageJson». Agent-facing контракт синхронизирован с реальным поведением #502.Гейт: mcp node:test 879/879. Роутинг/memoization/#328/security/format:text — были верны и в round 1, доки-правка их не трогает. Готово к мержу.