Files
gitmost/packages/mcp/README.ru.md
vvzvlad 1f5987d6b0 feat(mcp): serve embedded community MCP server at /mcp
Replace the removed enterprise EE MCP (private apps/server/src/ee submodule,
license-gated /mcp route) with our docmost-mcp, vendored as an isolated ESM
workspace package and served by the server over HTTP — no enterprise license.

Backend:
- Add packages/mcp (@docmost/mcp): vendored docmost-mcp refactored into a
  side-effect-free createDocmostMcpServer() factory (38 tools preserved),
  stdio entry kept in stdio.ts, Streamable-HTTP session manager in http.ts.
- Add apps/server McpModule: @Post/@Get/@Delete('mcp') (served at /mcp via the
  existing global-prefix exclude), @SkipTransform + reply.hijack to bridge raw
  Fastify req/res into the SDK transport. The module dynamically imports the
  ESM-only package from CommonJS via a Function-indirected import resolved with
  require.resolve + file:// URL. Gated by the workspace ai.mcp toggle, a
  service-account (MCP_DOCMOST_EMAIL/PASSWORD/API_URL) and optional MCP_TOKEN;
  per-session idle eviction (MCP_SESSION_IDLE_MS).
- Drop the enterprise license check on mcpEnabled in workspace.service.
- Dockerfile: copy packages/mcp into the production image.
- .env.example: document MCP_DOCMOST_*, MCP_TOKEN, MCP_SESSION_IDLE_MS.

Frontend:
- Recreate the community "AI & MCP" workspace-settings panel (mcp-settings.tsx):
  admin-only toggle on settings.ai.mcp with optimistic update, copyable
  ${APP_URL}/mcp URL; wired into workspace-settings page. Reuses existing i18n.

Fixes:
- Pin packages/mcp tiptap deps to 3.20.4 (matching the client) and inline
  getStyleProperty, preventing a duplicate @tiptap/core@3.26.1 from leaking into
  the client editor via pnpm shamefully-hoist (was breaking apps/client tsc).
2026-06-16 23:54:53 +03:00

31 KiB

Docmost MCP Server

English · Русский

Сервер Model Context Protocol (MCP) для Docmost, который позволяет ИИ-агентам читать, искать, писать, реструктурировать, рецензировать, вести версии, комментировать, иллюстрировать и публиковать документацию — безопасно, на живом инстансе и без enterprise-лицензии.

Написан агентом для агентов. Человек правит документ глазами и руками: читает, заходит в редактор, перепечатывает. Агент работает иначе — ему гораздо проще написать небольшую функцию, которая чинит текст, чем перечитывать и заново выдавать весь документ. Поэтому сервер построен вокруг того, как модели на самом деле удобно редактировать: адресовать блок по id, сделать find/replace или передать трансформ (doc, ctx) => doc и позволить модели запрограммировать правку. docmost_transform — это и есть такой интерфейс. Другие Docmost-MCP «заточены под человека» — они дают «открыть страницу» и «заменить страницу»; этот даёт примитивы редактирования, в которых модель сильна.

Сервер предоставляет 38 инструментов, построенных вокруг трёх идей, которые другие Docmost-MCP не сочетают:

  1. Точечные, экономичные по токенам правки. Адресуйте отдельный блок по id и патчите его или делайте find/replace вместо того, чтобы гонять весь документ ~100 КБ через модель.
  2. Безопасная запись на живой документ. Каждая мутация проходит через слой коллаборации реального времени (тот же WebSocket, что использует веб-редактор), сериализуется по странице, поэтому никогда не затирает параллельную правку человека и подтверждается как сохранённая до возврата из инструмента.
  3. Настоящая страховка. История версий, дифф, эквивалентный Docmost, восстановление одним вызовом и предпросмотр (dry-run) для скриптовых правок — чтобы агент мог редактировать смело, а вы всегда могли увидеть и откатить сделанное.

Почему именно этот сервер (в сравнении с альтернативами)

Существует несколько Docmost-MCP. Ниже — сравнение по возможностям. «Официальный» — встроенный MCP Docmost; остальные — community-проекты на GitHub.

Возможность Этот сервер Официальный (встроенный) MrMartiniMo/docmost-mcp cyborgx0x/mcp-docmost aleksvin8888 / isak-landin
Нужна enterprise-лицензия Нет Да Нет Нет Нет
Аутентификация email + пароль, авто-переавторизация API-ключ email + пароль cookie authToken (копировать из DevTools) API Docmost / напрямую PostgreSQL
Чтение страницы как Markdown (только чтение)
Lossless Markdown round-trip (экспорт/импорт, сохраняет якоря комментариев)
Чтение lossless ProseMirror JSON (с id блоков)
Компактная структура страницы (дешёвый поиск id блока)
Получение одного блока (по id или индексу)
Создание / перемещение / удаление страниц
Поблочные правки (patch/insert/delete по id)
Хирургический find/replace (с сохранением структуры)
Скриптовый JS-трансформ (песочница, dry-run дифф)
Структурное редактирование таблиц (CRUD строк/ячеек)
История версий страницы
Дифф двух версий
Восстановление версии (обратимое)
Комментарии (CRUD + inline-привязка)
Поллинг новых комментариев с момента времени
Изображения (вставка / замена)
Публичные ссылки (создать / отозвать / список)
Экспорт в HTML / PDF
Безопасная запись через real-time-collab (без затирания, с подтверждением) n/a n/a (только чтение)

Что это даёт на практике

  • Никакого enterprise-налога. Официальный MCP Docmost — enterprise-функция: нужна активная enterprise-лицензия. Этот сервер — MIT и работает с любым self-hosted Docmost через стандартный API + сокет коллаборации, имея лишь email и пароль аккаунта.

  • Экономия токенов при редактировании. Большинство Docmost-MCP (и официальный) предлагают только запись «заменить всю страницу» — агент вынужден скачать весь документ, изменить и загрузить обратно, оплачивая весь документ дважды на каждой мелкой правке. Этот сервер позволяет агенту изменить ровно один блок (patch_node / insert_node / delete_node), сделать find/replace с сохранением структуры (edit_page_text) или скопировать страницу на стороне сервера (copy_page_content) — причём документ ни разу не проходит через модель.

  • Записи, которые не воюют с редактором. Наивная запись через REST конфликтует с тем, что в этот момент печатает человек, и может молча затереть его правки или упасть на дебаунс-сохранении Docmost. Этот сервер применяет каждое изменение через живой документ коллаборации (Hocuspocus/Yjs), читая и записывая синхронно в пределах одного тика синхронизации, чтобы никакая параллельная правка не вклинилась, сериализует записи по странице мьютексом и ждёт подтверждения сохранения от сервера до возврата. Если сокет отвалился посреди записи, инструмент возвращает ошибку, а не ложный успех.

  • Агентоориентированная модель редактирования. Серверы «под человека» дают «открыть страницу» и «заменить страницу», потому что это отражает то, как работает человек. Модель редактирует лучше, программируя правку — адресуя блоки по id, делая find/replace или передавая трансформ (doc, ctx) => doc (docmost_transform, с dry-run диффом перед коммитом). Этот сервер построен вокруг этого — поэтому у него есть примитивы редактирования, которых у остальных просто нет.

  • Страховка при редактировании, которой нет у других. list_page_historydiff_page_versionsrestore_page_version дают агенту (и вам) полный цикл «посмотреть и откатить». Дифф использует тот же конвейер recreateTransform → ChangeSet → simplifyChanges, что и встроенный просмотр истории Docmost, так что результат совпадает с продуктом.

  • Удобство вместо выковыривания cookie. Некоторые community-серверы аутентифицируются, заставляя вас копировать сессионный cookie из DevTools браузера (он истекает), либо лезут напрямую в базу PostgreSQL. Этот сервер логинится по учётным данным и прозрачно переавторизуется на 401/403 (с дедупликацией параллельных логинов), поэтому долгоживущие агенты не падают, когда токен истёк. Он также соблюдает контроль доступа Docmost, потому что ходит через API и сервер коллаборации как обычный пользователь.


Инструменты

Все 38 инструментов, сгруппированы по задачам, для которых вы их возьмёте.

Чтение и поиск

  • get_workspace — Информация о текущем воркспейсе Docmost.
  • list_spaces — Все пространства воркспейса.
  • list_pages — Недавние страницы пространства, по убыванию updatedAt (по умолчанию 50, максимум 100). Для поиска в больших пространствах используйте search.
  • search — Полнотекстовый поиск по страницам и контенту (ограничен limit, максимум 100).
  • get_page — Контент страницы как чистый Markdown (удобно, но это lossy-представление — id блоков и точная структура таблиц/коллаутов аппроксимируются).
  • get_page_jsonLossless ProseMirror/TipTap JSON страницы, включая attrs.id каждого блока и slugId, используемый в URL. Именно его потребляют инструменты поблочного редактирования.
  • get_outline — Компактная структура страницы из блоков верхнего уровня ({index, type, id, level, firstText}; для таблиц добавляются число строк/столбцов и тексты ячеек заголовка, для списков — число пунктов) без тела документа. Дешёвый способ найти раздел или таблицу и получить id блока перед get_node / patch_node / insert_node.
  • get_node — Получить полное ProseMirror-поддерево одного блока (lossless), не вытягивая всю страницу. Адресуйте его по id блока (из get_outline / get_page_json) или формой #<index> для блока верхнего уровня — используйте #<index> для таблиц/строк/ячеек, у которых нет id.

Жизненный цикл страниц

  • create_page — Создать страницу из Markdown и поместить в иерархию (опционально parentPageId) одним вызовом. Использует import API Docmost для чистой конвертации Markdown→ProseMirror.
  • rename_page — Изменить только заголовок страницы, не трогая и не пересылая контент.
  • move_page — Сменить родителя страницы (вложить или вынести в корень); поддерживает позиционирование по fractional-index. Возвращает успех только при положительно подтверждённом результате.
  • delete_page — Удалить одну страницу.
  • copy_page_content — Заменить тело одной страницы копией тела другой, полностью на стороне сервера — документ не проходит через модель. У целевой страницы сохраняются собственные заголовок и slug (URL не меняется).

Редактирование

  • edit_page_text — Хирургический find/replace внутри текста страницы. Сохраняет всю структуру: id блоков, marks, ссылки, коллауты, таблицы. Предпочтительный инструмент для правки формулировок, опечаток, чисел и имён.
  • patch_node — Заменить один блок, адресованный по attrs.id (из get_page_json), без пересылки документа.
  • insert_node — Вставить блок до/после другого (по attrs.id или по якорному тексту) либо добавить в конец.
  • delete_node — Удалить один блок по его attrs.id.
  • update_page_json — Заменить весь контент страницы документом ProseMirror (массовые перезаписи или когда у узлов нет id). content опционален — опустите его, чтобы изменить только заголовок. Сохраняет переданные id блоков, поэтому якоря заголовков и история остаются стабильными.
  • docmost_transform — Агентоориентированный интерфейс редактирования: вместо перепечатывания документа агент пишет функцию, которая его чинит. Редактирует страницу, запуская произвольный JS-трансформ (doc, ctx) => doc на её живом документе ProseMirror. Работает в песочнице (без require/process/fs/сети, таймаут 5 с). По умолчанию dry-run: возвращает предпросмотр диффа без записи; установите dryRun:false, чтобы применить атомарно. ctx даёт доступ к комментариям страницы и набору хелперов (walk, getList, blockText, insertMarkerAfter, setCalloutRange, commentsToFootnotes, …) для многошаговых согласованных перезаписей — например перенумерации или превращения inline-комментариев в нумерованные сноски.

Таблицы

  • table_get — Прочитать таблицу как матрицу: {rows, cols, cells (text[][]), cellIds} (id абзаца на ячейку или null). Адресуйте таблицу через #<index> (из get_outline) или любой id блока внутри неё. Используйте cellIds вместе с patch_node для правок ячеек с форматированием.
  • table_insert_row — Вставить строку из текстовых ячеек, дополненную до числа столбцов таблицы (передать ячеек больше числа столбцов — ошибка). index — 0-based позиция вставки (0 вставляет перед заголовком); опустите, чтобы добавить в конец.
  • table_delete_row — Удалить строку по 0-based index. Отказывается удалять единственную строку таблицы; удаление строки 0 делает заголовком следующую строку.
  • table_update_cell — Задать текстовое содержимое ячейки [row, col] (0-based). Для форматирования используйте patch_node по id абзаца ячейки из table_get.

Markdown: экспорт и импорт

  • export_page_markdown — Экспортировать страницу в один самодостаточный, lossless Markdown в диалекте Docmost: мета-заголовок, тело с inline-якорями комментариев и диаграммами и завершающий блок тредов комментариев. Рассчитан на цикл «скачать → отредактировать тело → import_page_markdown», сохраняющий всё, включая выделения комментариев.
  • import_page_markdown — Заменить контент страницы из Markdown-файла в диалекте Docmost, созданного export_page_markdown, восстанавливая якоря-выделения комментариев и диаграммы из их inline-HTML. (Треды комментариев из файла не пересоздаются на сервере — записываются только тело страницы и inline-марки комментариев; тредами управляйте через инструменты/UI комментариев.)

Изображения

  • insert_image — Загрузить локальное изображение и вставить за один шаг: добавить в конец, поставить вместо текстового плейсхолдера (replaceText) или после заданного блока (afterText). Сохраняет id всех остальных блоков.
  • replace_image — Заменить существующее изображение. Загружает новый файл как новое вложение (чистый URL, который рендерится и сбрасывает кэш браузера), затем перенаправляет все узлы, ссылавшиеся на старое вложение (рекурсивно, включая коллауты/таблицы), через живой документ, сохраняя комментарии, выравнивание и alt-текст. (Перезапись «по месту» намеренно не используется — некоторые версии Docmost портят вложение при перезаписи.)

Комментарии

  • create_comment — Добавить комментарий к странице, опционально привязав inline к точному фрагменту текста (первое вхождение оборачивается comment-маркой).
  • list_comments — Список комментариев страницы (контент возвращается как Markdown).
  • update_comment — Изменить существующий комментарий.
  • delete_comment — Удалить комментарий.
  • check_new_comments — Найти комментарии, созданные после заданной метки времени ISO-8601, по пространству, опционально в рамках поддерева страниц — идеально для агента, который следит за обратной связью в документе.

Версии и история

  • list_page_history — Сохранённые версии страницы (Docmost авто-снапшотит при каждом сохранении), новые сверху, курсорная пагинация. id каждого элемента — это historyId.
  • diff_page_versions — Дифф двух версий (или версии против живой страницы). Возвращает вставленный/удалённый текст, счётчики целостности (изображения, ссылки, таблицы, коллауты, маркеры сносок) и человекочитаемую Markdown-сводку — посчитано тем же конвейером, что использует встроенный просмотр истории Docmost.
  • restore_page_version — Записать сохранённую версию обратно как текущий контент. У Docmost нет эндпоинта восстановления, поэтому создаётся новый снапшот — само восстановление тоже обратимо.

Публикация

  • share_page — Сделать страницу публично доступной (идемпотентно) и вернуть её публичный URL (<app>/share/<key>/p/<slugId>); опционально индексирование поисковиками.
  • unshare_page — Отозвать публичный доступ к странице.
  • list_shares — Все публичные ссылки воркспейса с заголовками и публичными URL.

Как выбрать инструмент редактирования

Та же подсказка отдаётся в рантайме через поле instructions MCP-сервера, так что подходящие клиенты направляют модель автоматически.

  • Правки текста (формулировки, опечатки, числа): edit_page_text.
  • Один блок (абзац/заголовок/коллаут/ячейка таблицы): patch_node / insert_node / delete_node, адресуя узел по его attrs.id из get_page_json.
  • Изображения: insert_image / replace_image.
  • Новая страница: create_page.
  • Массовая перезапись или узлы без id: update_page_json.
  • Многошаговая / скриптовая перезапись (перенумерация, сноски, согласованные правки): docmost_transform — предпросмотр через dryRun, затем применение.
  • Скопировать контент целой страницы из другой (на стороне сервера): copy_page_content.
  • Переименовать страницу (только заголовок): rename_page.
  • Чтение: get_page (Markdown) / get_page_json (lossless ProseMirror с id).
  • Просмотр изменений: list_page_historydiff_page_versionsrestore_page_version.
  • Комментарии: create_comment (с опциональной inline-привязкой) / list_comments / update_comment / delete_comment / check_new_comments.
  • Дешёвая навигация по странице (найти раздел/таблицу, получить id блока): get_outlineget_node.
  • Таблицы (добавить/удалить строку, задать ячейку): table_get / table_insert_row / table_delete_row / table_update_cell.
  • Round-trip страницы через Markdown (скачать, отредактировать, залить обратно без потерь, с комментариями): export_page_markdown / import_page_markdown.

Как это устроено (технические детали)

  • Безопасная запись через коллаборацию реального времени. Мутации контента применяются через WebSocket коллаборации Docmost (Hocuspocus + Yjs). Сервер подключается, ждёт первичной синхронизации, чтобы локальный документ отражал авторитетный серверный (включая правки, которых ещё нет в дебаунс-снапшоте REST), затем читает → трансформирует → пишет синхронно в одном тике, чтобы никакое удалённое обновление не вклинилось, и ждёт подтверждения сохранения до возврата.
  • Сериализация записи по странице. Асинхронный мьютекс по pageId гарантирует, что две записи MCP в одну страницу никогда не пересекаются; разные страницы друг друга не блокируют.
  • Прозрачная переавторизация. Логин по email/паролю; истёкшие токены обновляются автоматически на первом 401/403 (покрывая JSON, multipart-загрузку и путь токена коллаборации), с дедупликацией параллельных логинов, так что пачка вызовов вызывает один повторный логин.
  • Lossless- и lossy-чтение. get_page_json возвращает точное дерево ProseMirror с id блоков; get_page возвращает чистый Markdown для удобства.
  • Полная схема Docmost. Конвертация Markdown↔ProseMirror поддерживает коллауты (включая вложенные), списки задач (маркированные и нумерованные чек-листы), таблицы, блоки формул, эмбеды, выделение, под/надстрочный текст и прочее, с защитными лимитами против патологического ввода.
  • Структурные таблицы и lossless Markdown round-trip. Таблицы можно редактировать как матрицу (чтение, вставка/удаление строк, задание ячеек по [row, col]) без пересылки документа, а страницу — экспортировать и заново импортировать как самодостаточный Markdown-файл в диалекте Docmost, сохраняющий inline-якоря комментариев и диаграммы.
  • Ответы, оптимизированные по токенам. Ответы API урезаются до полей, действительно нужных агентам, а большие коллекции (пространства, страницы, комментарии, история) пагинируются.
  • Закалённый рантайм. Глобальные обработчики не дают случайной ошибке сокета уронить stdio-сервер; move_page требует положительно подтверждённого успеха; движок диффа откатывается к грубому поблочному диффу, а не падает на патологическом документе.

Установка

npm install
npm run build

Конфигурация

Серверу нужны три переменные окружения:

  • DOCMOST_API_URL — полный URL к API вашего Docmost (например, https://docs.example.com/api).
  • DOCMOST_EMAIL — email аккаунта для аутентификации.
  • DOCMOST_PASSWORD — пароль аккаунта.

Использование с Claude Desktop / произвольным MCP-клиентом

Добавьте сервер в конфигурацию MCP (например, claude_desktop_config.json):

{
  "mcpServers": {
    "docmost-local": {
      "command": "node",
      "args": ["./build/index.js"],
      "env": {
        "DOCMOST_API_URL": "http://localhost:3000/api",
        "DOCMOST_EMAIL": "test@docmost.com",
        "DOCMOST_PASSWORD": "test"
      }
    }
  }
}

Разработка

# Режим наблюдения
npm run watch

# Сборка
npm run build

# Тесты (unit + mock; live end-to-end набор требует запущенного Docmost)
npm test
npm run test:e2e

Происхождение и благодарности

Проект начинался как форк MrMartiniMo/docmost-mcp (автор Moritz Krause) и существенно его расширяет — добавлены поблочное редактирование узлов, хирургические правки текста, песочница docmost_transform, история версий / дифф / восстановление, комментарии, вставка/замена изображений, публичные ссылки, серверное копирование страниц, двойное чтение JSON/Markdown, прозрачная переавторизация и значительное упрочнение. Инструменты комментариев портированы из upstream PR #3 от Max Nikitin. Спасибо обоим.

Лицензия

MIT