From 4be4a75fa322b92699843b3d712466f8836d8bba Mon Sep 17 00:00:00 2001 From: agent_coder Date: Fri, 10 Jul 2026 20:55:28 +0300 Subject: [PATCH] =?UTF-8?q?docs(mcp):=20=D1=82=D0=BE=D1=87=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20=D0=BE=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D1=82=D0=B5=D1=80=D1=8C=20getPage/exportPageMarkdo?= =?UTF-8?q?wn=20=E2=80=94=20=D0=BA=D0=BE=D0=BD=D0=B2=D0=B5=D1=80=D1=82?= =?UTF-8?q?=D0=B5=D1=80=20=D0=BA=D0=B0=D0=BD=D0=BE=D0=BD=D0=B8=D1=87=D0=B5?= =?UTF-8?q?=D1=81=D0=BA=D0=B8=D0=B9=20(#415)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Описания двух тулов устарели после #345 (канонический конвертер, #293/#351): «LOSSY…approximated» у getPage и «lossless» у exportPageMarkdown искажали роутинг агентов и молчаливо скрывали реальную потерю данных. Четвёртый линк breaking-окна, стоит на #413. - tool-specs.ts: getPage — вместо «LOSSY…approximated» точный закрытый список потерь (canonical for text; теряет лишь id блоков, resolved-якоря комментариев и фиксированный набор ACCEPTED-атрибутов без markdown- представления: спаны/colwidth/фон ячеек таблиц, indent, callout.icon, orderedList.type, link internal/target/rel/class). exportPageMarkdown — убрано «lossless»: round-trip перегенерирует id блоков и молча отбрасывает тот же набор (в первую очередь merge-спаны ячеек); держать в page-JSON, если нужны. Список выведен из источника истины (ATTR_VALUE_FUZZ_ALLOWLIST + MARK_ATTR_ALLOWLIST); opaque carried-verbatim токены (attachmentId, mime, slugId и пр.) намеренно НЕ в списке потерь — они round-trip'ятся. - server-instructions.ts: READ-строка ROUTING_PROSE приведена к тому же точному списку, без противоречия markdown-default роутингу #413. - Исправлена вторая латентная неточность: getPage описывался как сохраняющий resolved-якоря — но client.ts передаёт dropResolvedCommentAnchors: true, resolved-якоря скрыты (getNode, наоборот, их сохраняет — другой тул). - README пакета (EN + RU-зеркало) приведены в соответствие passage-for-passage: убраны безоговорочные «lossless/lossy» для Markdown round-trip; genuinely- lossless ссылки на raw-JSON (getPageJson/getNode) оставлены. Логику не трогает. server-instructions.test.mjs удалён и заменён tool-inventory.test.mjs (без пинов на старую формулировку). mcp tsc чисто, node --test 702/702, tool-inventory 5/5. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/mcp/README.md | 29 +++++++++++++-------- packages/mcp/README.ru.md | 32 +++++++++++++++-------- packages/mcp/src/server-instructions.ts | 2 +- packages/mcp/src/tool-specs.ts | 34 ++++++++++++++++--------- 4 files changed, 62 insertions(+), 35 deletions(-) diff --git a/packages/mcp/README.md b/packages/mcp/README.md index bdbaf6d9..b16b8c29 100644 --- a/packages/mcp/README.md +++ b/packages/mcp/README.md @@ -40,7 +40,7 @@ There are several Docmost MCPs. Here is a capability-by-capability comparison. | **Enterprise license required** | **No** | **Yes** | No | No | No | | Authentication | email + password, **auto re-auth** | API key | email + password | cookie `authToken` (copy from DevTools) | Docmost API / **direct PostgreSQL** | | Read page as Markdown | ✅ | ✅ | ✅ | ✅ | ✅ (read-only) | -| **Lossless Markdown round-trip** (export / import, keeps comment anchors) | ✅ | — | — | — | — | +| **Markdown round-trip** (export / import, keeps comment anchors) | ✅ | — | — | — | — | | Read **lossless ProseMirror JSON** (with block ids) | ✅ | — | — | — | — | | **Compact page outline** (cheap block-id lookup) | ✅ | — | — | — | — | | **Fetch a single block** (by id or index) | ✅ | — | — | — | — | @@ -115,8 +115,10 @@ All 41 tools, grouped by what you'd reach for them. - **`listPages`** — Recent pages in a space, ordered by `updatedAt` desc (default 50, max 100). Use `search` for lookups in large spaces. - **`search`** — Full-text search across pages and content (bounded by `limit`, max 100). -- **`getPage`** — A page's content as clean **Markdown** (convenient, but a *lossy* - view — block ids and exact table/callout structure are approximated). +- **`getPage`** — A page's content as clean **Markdown** (canonical for text; drops only + block ids, resolved-comment anchors, and a fixed no-Markdown-representation attr set — + table spans/colwidth/background, indent, `callout.icon`, `orderedList.type`, and link + `internal`/`target`/`rel`/`class`; use `getPageJson` when you need those). - **`getPageJson`** — A page's **lossless ProseMirror/TipTap JSON**, including every block's `attrs.id` and the `slugId` used in URLs. This is what the per-block editing tools consume. @@ -186,10 +188,14 @@ All 41 tools, grouped by what you'd reach for them. ### Markdown round-trip -- **`exportPageMarkdown`** — Export a page to a single self-contained, **lossless - Docmost-flavoured Markdown** file: a meta header, the body with inline comment anchors - and diagrams, and a trailing comments-thread block. To replace a page's body from plain - authoring Markdown, use `updatePageMarkdown`. +- **`exportPageMarkdown`** — Export a page to a single self-contained + **Docmost-flavoured Markdown** file: a meta header, the body with inline comment anchors + and diagrams, and a trailing comments-thread block. The download → edit → import + round-trip regenerates block ids and **silently drops** the no-Markdown-representation + attr set (table merge spans/colwidth/background, indent, `callout.icon`, + `orderedList.type`, link `internal`/`target`/`rel`/`class`); keep those in ProseMirror + JSON if they must survive. To replace a page's body from plain authoring Markdown, use + `updatePageMarkdown`. > **Removed in this release:** `importPageMarkdown` (the round-trip parser for an > exported Docmost-Markdown file) is **no longer exposed on the external MCP surface**. @@ -293,15 +299,16 @@ so capable clients steer the model automatically. refreshed automatically on the first 401/403 (covering JSON, multipart upload, and the collaboration-token path), with in-flight login de-duplication so a burst of calls triggers a single re-login. -- **Lossless and lossy reads.** `getPageJson` returns the exact ProseMirror tree with - block ids; `getPage` returns clean Markdown for convenience. +- **Precise reads.** `getPageJson` returns the exact ProseMirror tree with block ids; + `getPage` returns canonical Markdown that drops only a fixed, documented attr set. - **Full Docmost schema.** Markdown↔ProseMirror conversion supports callouts (including nested), task lists (bullet *and* numbered checklists), tables, math blocks, embeds, highlights, sub/superscript and more, with defensive caps against pathological input. -- **Structured tables & lossless Markdown round-trip.** Tables can be edited as a matrix +- **Structured tables & Markdown round-trip.** Tables can be edited as a matrix (read, insert/delete rows, set cells by `[row,col]`) without resending the document, and a page can be exported to and re-imported from a self-contained Docmost-flavoured - Markdown file that preserves inline comment anchors and diagrams. + Markdown file that preserves inline comment anchors and diagrams (block ids regenerate + and a fixed no-Markdown-representation attr set is dropped — see `exportPageMarkdown`). - **Token-optimized responses.** API responses are filtered down to the fields agents actually need, and large collections (spaces, pages, comments, history) are paginated. - **Hardened runtime.** Global handlers keep a stray socket error from tearing down the diff --git a/packages/mcp/README.ru.md b/packages/mcp/README.ru.md index 18574af0..8c124439 100644 --- a/packages/mcp/README.ru.md +++ b/packages/mcp/README.ru.md @@ -43,7 +43,7 @@ Docmost-MCP не сочетают: | **Нужна enterprise-лицензия** | **Нет** | **Да** | Нет | Нет | Нет | | Аутентификация | email + пароль, **авто-переавторизация** | API-ключ | email + пароль | cookie `authToken` (копировать из DevTools) | API Docmost / **напрямую PostgreSQL** | | Чтение страницы как Markdown | ✅ | ✅ | ✅ | ✅ | ✅ (только чтение) | -| **Lossless Markdown round-trip** (экспорт/импорт, сохраняет якоря комментариев) | ✅ | — | — | — | — | +| **Markdown round-trip** (экспорт/импорт, сохраняет якоря комментариев) | ✅ | — | — | — | — | | Чтение **lossless ProseMirror JSON** (с id блоков) | ✅ | — | — | — | — | | **Компактная структура страницы** (дешёвый поиск id блока) | ✅ | — | — | — | — | | **Получение одного блока** (по id или индексу) | ✅ | — | — | — | — | @@ -119,8 +119,11 @@ Docmost-MCP не сочетают: 50, максимум 100). Для поиска в больших пространствах используйте `search`. - **`search`** — Полнотекстовый поиск по страницам и контенту (ограничен `limit`, максимум 100). -- **`getPage`** — Контент страницы как чистый **Markdown** (удобно, но это - *lossy*-представление — id блоков и точная структура таблиц/коллаутов аппроксимируются). +- **`getPage`** — Контент страницы как чистый **Markdown** (канонично для текста; теряет + лишь id блоков, якоря разрешённых комментариев и фиксированный набор атрибутов без + markdown-представления — спаны/colwidth/фон ячеек таблиц, отступы (indent), + `callout.icon`, `orderedList.type` и `internal`/`target`/`rel`/`class` у ссылок; + используйте `getPageJson`, когда они нужны). - **`getPageJson`** — **Lossless ProseMirror/TipTap JSON** страницы, включая `attrs.id` каждого блока и `slugId`, используемый в URL. Именно его потребляют инструменты поблочного редактирования. @@ -191,10 +194,14 @@ Docmost-MCP не сочетают: ### Markdown: экспорт и импорт -- **`exportPageMarkdown`** — Экспортировать страницу в один самодостаточный, **lossless - Markdown в диалекте Docmost**: мета-заголовок, тело с inline-якорями комментариев и - диаграммами и завершающий блок тредов комментариев. Чтобы заменить тело страницы из - обычного авторского Markdown, используйте `updatePageMarkdown`. +- **`exportPageMarkdown`** — Экспортировать страницу в один самодостаточный + **Markdown в диалекте Docmost**: мета-заголовок, тело с inline-якорями комментариев и + диаграммами и завершающий блок тредов комментариев. Round-trip скачать → отредактировать → + импортировать перегенерирует id блоков и **молча отбрасывает** набор атрибутов без + markdown-представления (спаны/colwidth/фон ячеек таблиц, отступы (indent), `callout.icon`, + `orderedList.type`, `internal`/`target`/`rel`/`class` у ссылок); держите их в ProseMirror + JSON, если они должны выжить. Чтобы заменить тело страницы из обычного авторского Markdown, + используйте `updatePageMarkdown`. > **Удалено в этом релизе:** `importPageMarkdown` (парсер round-trip для > экспортированного Docmost-Markdown-файла) **больше не отдаётся на внешней MCP-поверхности**. @@ -302,16 +309,19 @@ Docmost-MCP не сочетают: автоматически на первом 401/403 (покрывая JSON, multipart-загрузку и путь токена коллаборации), с дедупликацией параллельных логинов, так что пачка вызовов вызывает один повторный логин. -- **Lossless- и lossy-чтение.** `getPageJson` возвращает точное дерево ProseMirror с id - блоков; `getPage` возвращает чистый Markdown для удобства. +- **Точные чтения.** `getPageJson` возвращает точное дерево ProseMirror с id блоков; + `getPage` возвращает канонический Markdown, теряющий лишь фиксированный, документированный + набор атрибутов. - **Полная схема Docmost.** Конвертация Markdown↔ProseMirror поддерживает коллауты (включая вложенные), списки задач (маркированные *и* нумерованные чек-листы), таблицы, блоки формул, эмбеды, выделение, под/надстрочный текст и прочее, с защитными лимитами против патологического ввода. -- **Структурные таблицы и lossless Markdown round-trip.** Таблицы можно редактировать как +- **Структурные таблицы и Markdown round-trip.** Таблицы можно редактировать как матрицу (чтение, вставка/удаление строк, задание ячеек по `[row, col]`) без пересылки документа, а страницу — экспортировать и заново импортировать как самодостаточный - Markdown-файл в диалекте Docmost, сохраняющий inline-якоря комментариев и диаграммы. + Markdown-файл в диалекте Docmost, сохраняющий inline-якоря комментариев и диаграммы + (id блоков перегенерируются, а фиксированный набор атрибутов без markdown-представления + отбрасывается — см. `exportPageMarkdown`). - **Ответы, оптимизированные по токенам.** Ответы API урезаются до полей, действительно нужных агентам, а большие коллекции (пространства, страницы, комментарии, история) пагинируются. diff --git a/packages/mcp/src/server-instructions.ts b/packages/mcp/src/server-instructions.ts index d3a6b74b..95e3f570 100644 --- a/packages/mcp/src/server-instructions.ts +++ b/packages/mcp/src/server-instructions.ts @@ -40,7 +40,7 @@ import { SHARED_TOOL_SPECS, SharedToolSpec } from "./tool-specs.js"; */ export const ROUTING_PROSE = "Docmost editing guide — choose the tool by intent. The at the end lists every tool with a one-line purpose; the notes below are the routing hints for WHEN to reach for each.\n" + - "READ: find a page -> search (workspace-wide full-text); list -> listPages / listSpaces. Locate blocks and their ids CHEAPLY -> getOutline (compact top-level map; start here, not getPageJson). One block, for editing -> getNode (by attrs.id, or \"#\" for tables, which carry no id) — returns MARKDOWN by default (comment anchors kept for safe write-back); pass format:\"json\" for the raw ProseMirror subtree. Find every occurrence of a string/regex ON a page (and where each is) -> searchInPage, NOT block-by-block getNode — it returns each hit's node ref + block index + context for a targeted comment. Whole page -> getPage (Markdown, lossy; inline tags are comment anchors — markup, not text) or getPageJson (lossless ProseMirror with block ids). Hand a huge page (with images) to an external consumer without pulling it through the model context -> stashPage (returns a short-lived anonymous URL).\n" + + "READ: find a page -> search (workspace-wide full-text); list -> listPages / listSpaces. Locate blocks and their ids CHEAPLY -> getOutline (compact top-level map; start here, not getPageJson). One block, for editing -> getNode (by attrs.id, or \"#\" for tables, which carry no id) — returns MARKDOWN by default (comment anchors kept for safe write-back); pass format:\"json\" for the raw ProseMirror subtree. Find every occurrence of a string/regex ON a page (and where each is) -> searchInPage, NOT block-by-block getNode — it returns each hit's node ref + block index + context for a targeted comment. Whole page -> getPage (Markdown, canonical for text; drops only block ids, resolved-comment anchors, and a fixed no-md-representation attr set: table spans/colwidth/bg, indent, callout.icon, orderedList.type, link internal/target/rel/class; inline tags are comment anchors — markup, not text) or getPageJson (full ProseMirror with block ids, for those dropped attrs). Hand a huge page (with images) to an external consumer without pulling it through the model context -> stashPage (returns a short-lived anonymous URL).\n" + "EDIT: fix wording/typos/numbers -> editPageText (find/replace inside blocks, no node id needed). Edit a block -> getNode(markdown) -> edit the markdown -> patchNode(markdown) (by attrs.id from getOutline; the markdown fragment may be several blocks — a 1->N section rewrite in one call, the first block keeps the id). Reach for patchNode's `node`-JSON only for fine attr/mark work; a table cell with spans/colors/fixed width -> the table tools (patchNode markdown refuses it). Add a block -> insertNode (markdown, before/after a block by attrs.id or by anchor text, or append; `node` for raw JSON or bare table structure). Remove a block -> deleteNode (by attrs.id). Tables -> tableGet / tableUpdateCell / tableInsertRow / tableDeleteRow (address by \"#\" from getOutline; table nodes have no attrs.id). Images -> insertImage (add from a web URL) / replaceImage (swap an existing image). Draw.io diagrams -> drawioCreate (create from mxGraph XML and insert), drawioGet (read a diagram as mxGraph XML + a hash), drawioUpdate (replace a diagram; pass the hash from drawioGet as baseHash for optimistic locking); before authoring a diagram, drawioShapes (look up verified stencil style-strings so a shape name never renders as an empty box) and drawioGuide (on-demand authoring reference: skeleton/layout/containers/icons-aws/icons-azure), and pass layout:\"elk\" to drawioCreate/drawioUpdate to auto-place nodes. Footnotes -> insertFootnote. Bulk/structural rewrite -> updatePageJson (full ProseMirror replace) or updatePageMarkdown (full plain-Markdown body replace, re-imported — block ids regenerate); prefer the granular tools above to avoid resending the whole ~100KB+ document. Complex/scripted rewrite (multiple coordinated edits, renumbering) -> docmostTransform: write a JS `(doc, ctx) => doc` transform, preview the diff with dryRun (default), then apply with dryRun:false; ctx.helpers includes commentsToFootnotes for turning inline comments into numbered footnotes.\n" + "PAGES: new -> createPage (Markdown). Rename (title only) -> renamePage. Move -> movePage. Delete -> deletePage (SOFT delete — the page goes to trash and is restorable; nothing is permanent). Copy/replace a page's whole content from another page (server-side, no document through the model) -> copyPageContent. Sharing -> sharePage / unsharePage / listShares; sharePage makes the page PUBLICLY accessible — do it only when explicitly asked.\n" + "COMMENTS: createComment is always inline and requires an EXACT selection — contiguous text from a single block, <=250 chars (fails rather than leaving an unanchored comment); reply to a thread via parentCommentId. Propose a concrete text fix for one-click human approval -> createComment with suggestedText (the exact plain-text replacement for the selection; the selection must then be UNIQUE in the page — extend it with context if needed); prefer this over editing directly when the change is subjective or needs the author's sign-off. Manage -> listComments, updateComment, resolveComment (resolve/reopen, reversible — prefer over delete to close), deleteComment, checkNewComments.\n" + diff --git a/packages/mcp/src/tool-specs.ts b/packages/mcp/src/tool-specs.ts index e2488f7c..018a743a 100644 --- a/packages/mcp/src/tool-specs.ts +++ b/packages/mcp/src/tool-specs.ts @@ -877,11 +877,17 @@ export const SHARED_TOOL_SPECS = { inAppKey: 'getPage', description: 'Fetch a single page as Markdown by its id. Returns the page title and ' + - 'its Markdown content. The Markdown conversion is LOSSY (block ids, exact ' + - 'table/callout structure are approximated); for a lossless representation ' + - 'use the lossless page-JSON read tool. Inline tags in the markdown ' + - 'are comment highlight anchors (also present for RESOLVED threads) — ' + - 'treat them as markup, not page text.', + 'its Markdown content. The converter is canonical (round-trips text and ' + + 'block structure), so this is sufficient for text edits; use the ' + + 'page-JSON read tool only when you need what Markdown cannot carry. The ' + + 'Markdown drops exactly: (1) block ids (not visible in Markdown); ' + + '(2) resolved-comment anchors (hidden here; only active anchors remain); (3) a fixed set of attributes with no ' + + 'Markdown representation — table-cell colspan/rowspan/colwidth/' + + 'backgroundColor/backgroundColorName, heading/paragraph indent, ' + + 'callout.icon, orderedList.type, and link internal/target/rel/class. ' + + 'Inline tags in the markdown are comment highlight ' + + 'anchors — treat them as markup, not page text.', tier: 'core', catalogLine: 'getPage — fetch a page as Markdown by its id.', // Reconciled: MCP's stricter .min(1) kept; in-app's more-informative @@ -1240,13 +1246,17 @@ export const SHARED_TOOL_SPECS = { inAppKey: 'exportPageMarkdown', // CANONICAL: the MCP copy (a strict superset of the terse in-app wording). description: - 'Export a page to a single self-contained, lossless Docmost-flavoured ' + - 'Markdown file (custom extensions): YAML-free meta header, body with ' + - 'inline comment anchors and diagrams, and a trailing comments-thread ' + - 'block. Designed for a download -> edit body -> page-Markdown import ' + - 'round-trip that preserves everything, including comment highlights. ' + - 'Comment THREADS are preserved in the file but are not re-pushed to the ' + - 'server on import.', + 'Export a page to a single self-contained Docmost-flavoured Markdown ' + + 'file (custom extensions): YAML-free meta header, body with inline ' + + 'comment anchors (resolved ones kept) and diagrams, and a trailing ' + + 'comments-thread block. Designed for a download -> edit body -> ' + + 'page-Markdown import round-trip; block ids regenerate and comment ' + + 'THREADS, though kept in the file, are not re-pushed to the server on ' + + 'import. The round-trip SILENTLY DROPS a fixed set of attributes with no ' + + 'Markdown representation — table-cell merge spans (colspan/rowspan), ' + + 'colwidth, backgroundColor/backgroundColorName, heading/paragraph indent, ' + + 'callout.icon, orderedList.type, and link internal/target/rel/class. Use ' + + 'the page-JSON tools if those must survive.', tier: 'deferred', catalogLine: 'exportPageMarkdown — export a page to self-contained Markdown (body + comments).', -- 2.52.0