Security (must-fix):
- sandbox.controller: the anonymous GET /api/sb/:id response now sets
X-Content-Type-Options: nosniff, a restrictive CSP, and Content-Disposition=
attachment for any mime outside a raster-image allowlist (png/jpeg/gif/webp/
avif). entry.mime is attacker-controlled, so an evil.svg/evil.html could
otherwise execute script inline on the Docmost origin (stored XSS). Mirrors
the public attachment route's hardening.
Stability:
- client.stashPage: reconcile mirrors AFTER the final document put, not only
before it. The doc blob is the newest entry and FIFO eviction drops the
oldest = this stash's own images, so the stored doc could reference an
evicted blob (consumer 404) and over-report images.mirrored. A bounded loop
now reverts doc-put-evicted mirrors, drops the stale doc blob, and re-puts
until stable. Regenerated packages/mcp/build/.
- sandbox.controller: emit Cache-Control on the 304 branch too (ttlSeconds is
computed before the conditional check).
Docs:
- Bump the MCP tool count 39 -> 40 across all READMEs and AGENTS.md (the
registry now exposes exactly 40 tools).
Refactor:
- SandboxStore.asSink() centralizes the {put,has,evict} sink + uri<->id
mapping; the embedded-MCP and in-app agent-tools wiring sites share it.
Tests:
- security headers (inline vs attachment, nosniff, CSP), 304 Cache-Control,
putAndLink URL form, has()/remove(), asSink() round-trip, getSandboxPublicUrl
(trailing-slash trim + APP_URL fallback), and a stash test where the doc put
itself evicts a mirrored image.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
33 KiB
Docmost MCP Server
English · Русский
Сервер Model Context Protocol (MCP) для Docmost, который позволяет ИИ-агентам читать, искать, писать, реструктурировать, рецензировать, вести версии, комментировать, иллюстрировать и публиковать документацию — безопасно, на живом инстансе и без enterprise-лицензии.
Написан агентом для агентов. Человек правит документ глазами и руками: читает, заходит в редактор, перепечатывает. Агент работает иначе — ему гораздо проще написать небольшую функцию, которая чинит текст, чем перечитывать и заново выдавать весь документ. Поэтому сервер построен вокруг того, как модели на самом деле удобно редактировать: адресовать блок по id, сделать find/replace или передать трансформ
(doc, ctx) => docи позволить модели запрограммировать правку.docmost_transform— это и есть такой интерфейс. Другие Docmost-MCP «заточены под человека» — они дают «открыть страницу» и «заменить страницу»; этот даёт примитивы редактирования, в которых модель сильна.
Сервер предоставляет 40 инструментов, построенных вокруг трёх идей, которые другие Docmost-MCP не сочетают:
- Точечные, экономичные по токенам правки. Адресуйте отдельный блок по id и патчите его или делайте find/replace вместо того, чтобы гонять весь документ ~100 КБ через модель.
- Безопасная запись на живой документ. Каждая мутация проходит через слой коллаборации реального времени (тот же WebSocket, что использует веб-редактор), сериализуется по странице, поэтому никогда не затирает параллельную правку человека и подтверждается как сохранённая до возврата из инструмента.
- Настоящая страховка. История версий, дифф, эквивалентный 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_history→diff_page_versions→restore_page_versionдают агенту (и вам) полный цикл «посмотреть и откатить». Дифф использует тот же конвейерrecreateTransform → ChangeSet → simplifyChanges, что и встроенный просмотр истории Docmost, так что результат совпадает с продуктом. -
Удобство вместо выковыривания cookie. Некоторые community-серверы аутентифицируются, заставляя вас копировать сессионный cookie из DevTools браузера (он истекает), либо лезут напрямую в базу PostgreSQL. Этот сервер логинится по учётным данным и прозрачно переавторизуется на 401/403 (с дедупликацией параллельных логинов), поэтому долгоживущие агенты не падают, когда токен истёк. Он также соблюдает контроль доступа Docmost, потому что ходит через API и сервер коллаборации как обычный пользователь.
Инструменты
Все 40 инструментов, сгруппированы по задачам, для которых вы их возьмёте.
Чтение и поиск
get_workspace— Информация о текущем воркспейсе Docmost.list_spaces— Все пространства воркспейса.list_pages— Недавние страницы пространства, по убываниюupdatedAt(по умолчанию 50, максимум 100). Для поиска в больших пространствах используйтеsearch.search— Полнотекстовый поиск по страницам и контенту (ограниченlimit, максимум 100).get_page— Контент страницы как чистый Markdown (удобно, но это lossy-представление — id блоков и точная структура таблиц/коллаутов аппроксимируются).get_page_json— Lossless 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-basedindex. Отказывается удалять единственную строку таблицы; удаление строки 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 портят вложение при перезаписи.)stash_page— Сериализовать страницу целиком (её полный ProseMirror JSON) в эфемерный blob в оперативной памяти и вернуть ТОЛЬКО короткий анонимный URL — тело никогда не попадает в контекст модели, поэтому это способ передать большую страницу (вместе с её изображениями) внешнему потребителю без усечения. Каждое внутреннее файловое/графическое вложение зеркалируется в тот же sandbox, а егоsrcпереписывается на URL sandbox; внешние http(s)-изображения остаются нетронутыми. Возвращает{ uri, size, sha256, images:{ mirrored, failed } }(sha256— это также ETag blob'а). Blob'ы хранятся только в оперативной памяти, истекают через короткий TTL (~1 ч) и привязаны к тому экземпляру сервера, который их создал.
Комментарии
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_history→diff_page_versions→restore_page_version. - Комментарии:
create_comment(с опциональной inline-привязкой) /list_comments/update_comment/delete_comment/check_new_comments. - Дешёвая навигация по странице (найти раздел/таблицу, получить id блока):
get_outline→get_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