feat(mcp): создание и редактирование drawio-ноды из XML (get/insert/update_drawio) #398
Reference in New Issue
Block a user
Delete Branch "%!s()"
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?
Контекст
drawio-диаграмма в Docmost — это ProseMirror-нода
drawio, ссылающаяся на вложениеdiagram.drawio.svg. Это editable-SVG: пиксельный рендер + исходный mxfile-XML в атрибутеcontentкорневого<svg>. Рендер XML→SVG делает сам drawio-редактор в браузере; серверного рендерера в проекте нет.Файлы:
packages/editor-ext/src/lib/drawio.ts(attrs:src,attachmentId,title,width,height,size,aspectRatio,align)apps/client/src/features/editor/components/drawio/drawio-menu.tsx,.../drawio-view.tsx(fetch SVG →DrawIoEmbed→ exportxmlsvg→uploadFile)DRAWIO_URL(embed редактора) по умолчаниюhttps://embed.diagrams.net—apps/client/src/lib/config.tsПроблема
В MCP нет способа создать или отредактировать drawio-диаграмму:
insert_image/replace_imageработают только с растровыми картинками, скачиваемыми по http-URL;Цель (скоуп)
Дать агенту создавать и редактировать drawio-ноду, оперируя mxfile-XML как источником правды. Источник XML — вне скоупа (агент пишет руками либо берёт у внешнего drawio MCP, передаётся inline-текстом).
Три тула:
get_drawio— чтение (нужно для осмысленного редактирования)insert_drawio— создание новой ноды из XMLupdate_drawio— замена XML у существующей нодыВне скоупа
Контракты тулзов
get_drawio(pageId, attachmentId?)attachmentId→ список drawio-нод страницы:[{ index, attachmentId, title, width, height, size }](обходgetPageJson)attachmentId→ скачать SVG (fetchInternalFile), распарсить jsdom-ом, прочитать атрибутcontent→ mxfile XML. Если содержимое<diagram>сжато (base64 → raw-deflate) — распаковать (zlib.inflateRawSync+decodeURIComponent){ attachmentId, fileName, size, width, height, xml }contentнет → ошибка «не editable drawio SVG» (без мутаций)insert_drawio(pageId, xml, { placement?, align? })placement: append (по умолчанию) / after-text / replace-text (как уinsert_image)<mxfile>или<mxGraphModel>; голый mxGraphModel обернуть в mxfile). Мусор → ошибка до заливкиdiagram.drawio.svg:<svg content="<escaped mxfile>" width/height data-drawio-stale="1">+ тело-плейсхолдер-баннерPOST /files/upload(новыйuploadBuffer){ type: "drawio", attrs: { src, attachmentId, title: "diagram.drawio.svg", size, width, height, align } }и вставить существующимinsertNode/mutatePageContent(page-locked){ attachmentId, placement, renderStale: true, note }update_drawio(pageId, attachmentId, xml, { align? })diagram.drawio.svgreplaceImage)attrs.attachmentId === attachmentId(общийrepointAttachment): обновитьsrc/attachmentId/size/title, сохранитьtype/width/height/align/aspectRatio{ oldAttachmentId, newAttachmentId, renderStale: true, note }Рефакторинг / переиспользование
uploadImage→uploadBuffer(pageId, buffer, fileName, mime)+ часть fetch-по-URL (packages/mcp/src/client.ts, ~L3006)attachmentIdизreplaceImage(~L3262) в общийrepointAttachmentreadEmbeddedXml(svgBuffer)(jsdom + inflate),buildDrawioSvg(xml, { width, height })(баннер + встроенныйcontent)jsdomуже в зависимостяхpackages/mcp;zlibвстроенныйattachmentId(у drawio-ноды нетattrs.id— в outline такие блоки идут сid: null)Плейсхолдер-баннер
Diagram edited via agent — open in editor to re-renderdata-drawio-stale="1"на корне<svg>renderStale: trueв ответах тулзовcontent-XML при этом всегда корректный и редактируемыйРегистрация
packages/mcp/src/index.ts(stdio) + зеркально вhttp.ts; добавить в строку MCP-гайдаreplace_image)Крайние случаи / риски
contentпри чтении — обязательный inflate-фолбэкcontent→ понятная ошибка, без мутацийreplace_image)resolveInternalFilePathwidth/height/align/aspectRatioпри updateКритерии приёмки
get_drawioбез аргумента перечисляет drawio-ноды; сattachmentIdвозвращает XMLget_drawioчитает и сжатый, и несжатыйcontentget_drawioотдаёт понятную ошибку на не-editable SVGinsert_drawioсоздаёт ноду; XML открывается в редакторе drawio и показывает диаграммуinsert_drawioотклоняет битый XML до заливки (без orphan)insert_imageupdate_drawioзаменяет XML, репойнтит все ссылающиеся ноды, сохраняетwidth/height/alignrenderStale: true; превью — баннер до открытия+сохранения человекомНа будущее (отдельные задачи)
create_drawio(pageId, description)— оркестрация: gitmost как MCP-клиент внешнего drawio MCP, чтобы большой XML не шёл через контекст агента