feat(mcp-server): load .drawio.svg, change summary, XML reference by topic, preview options, truncation message (#982)

* feat(mcp-server): load .drawio.svg (Editable SVG) files with load_diagram

* feat(mcp-server): summarize the user's manual changes per cell in stale rejections and get_diagram

* feat(mcp-server): XML reference for tables, layers and groups via get_drawing_guide topic

* feat(mcp-server): DRAWIO_LANG, DRAWIO_UI and DRAWIO_DARK for the preview page

Three host-config environment variables fix the language, theme and
dark mode of the draw.io editor embedded in the preview page. The new
drawio-themes module holds the theme list and draw.io's locale names
(zh-hant becomes zh-tw); drawioEmbedParams() in http-server.ts builds
the variable tail of the iframe query and getHtmlPage fills the new
{{DRAWIO_PARAMS}} placeholder. Without the variables the page keeps
sending dark=auto as before. Both READMEs document the variables.

* feat(mcp-server): report XML cut off inside a cell and explain drawing in parts

create_new_diagram and add_page now detect bare-cell XML that ends inside an
unfinished mxCell (XML comments stripped first) and return an error with the
last 300 characters of the input, asking the model to resend from that cell or
continue with edit_diagram add operations. isMxCellXmlComplete moves from
lib/utils.ts into packages/mcp-server/src/new-diagram.ts and is re-exported
from lib/utils.ts for the web app. The drawing guide gains a "Large diagrams"
paragraph and the INSTRUCTIONS edit_diagram line mentions drawing in parts.

* fix(mcp-server): review fixes for load .drawio.svg, change summary, XML reference, draw.io embed options, truncation message

* fix(mcp-server): Codex review fixes for .drawio.svg loading, change summary, references, embed options and truncation

- Truncation check: a closing tag such as </mxCell/> that the auto-fix
  repairs is no cut, and input without any cell keeps the validator's
  message
- Guide: a call rejected as cut off drew nothing, so all of its cells are
  sent again; the topic pointer is its own paragraph
- Change summary: decoded labels (no &nbsp; or merged words from <br>),
  a fast path for equal XML, and "the order of the cells changed" as
  the fallback
- DRAWIO_UI in any case, DRAWIO_LANG=zh-Hans maps to draw.io's zh
- load_diagram and export_diagram describe which files load again
This commit is contained in:
Dayuan Jiang
2026-10-11 20:40:05 +09:00
committed by GitHub
parent cb066a4825
commit 8ab837679e
19 changed files with 949 additions and 146 deletions
+71
View File
@@ -0,0 +1,71 @@
/**
* Short XML references returned by get_drawing_guide with a topic. Each
* snippet is the bare mxCell elements of one page, as create_new_diagram
* accepts them, and is checked by tests/xml-reference.test.ts.
*/
import { indent } from "./xml-examples.ts"
const TABLE_ROW_STYLE =
"shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;"
const TABLE_CELL_STYLE =
"shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;"
const TABLES_EXAMPLE = `<mxCell id="table1" style="shape=table;startSize=0;container=1;collapsible=0;childLayout=tableLayout;" vertex="1" parent="1">
<mxGeometry x="40" y="40" width="240" height="80" as="geometry"/>
</mxCell>
<mxCell id="row1" style="${TABLE_ROW_STYLE}" vertex="1" parent="table1">
<mxGeometry y="0" width="240" height="40" as="geometry"/>
</mxCell>
<mxCell id="cell11" value="Name" style="${TABLE_CELL_STYLE}fontStyle=1;" vertex="1" parent="row1">
<mxGeometry x="0" y="0" width="120" height="40" as="geometry"/>
</mxCell>
<mxCell id="cell12" value="Role" style="${TABLE_CELL_STYLE}fontStyle=1;" vertex="1" parent="row1">
<mxGeometry x="120" y="0" width="120" height="40" as="geometry"/>
</mxCell>
<mxCell id="row2" style="${TABLE_ROW_STYLE}" vertex="1" parent="table1">
<mxGeometry y="40" width="240" height="40" as="geometry"/>
</mxCell>
<mxCell id="cell21" value="Alice" style="${TABLE_CELL_STYLE}" vertex="1" parent="row2">
<mxGeometry x="0" y="0" width="120" height="40" as="geometry"/>
</mxCell>
<mxCell id="cell22" value="Admin" style="${TABLE_CELL_STYLE}" vertex="1" parent="row2">
<mxGeometry x="120" y="0" width="120" height="40" as="geometry"/>
</mxCell>`
const LAYERS_EXAMPLE = `<mxCell id="L2" value="Layer 2" parent="0"/>
<mxCell id="2" value="On the default layer" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="40" y="40" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="3" value="On Layer 2" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="L2">
<mxGeometry x="200" y="40" width="120" height="60" as="geometry"/>
</mxCell>`
const GROUPS_EXAMPLE = `<mxCell id="group1" style="group" vertex="1" connectable="0" parent="1">
<mxGeometry x="40" y="40" width="260" height="60" as="geometry"/>
</mxCell>
<mxCell id="2" value="A" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="group1">
<mxGeometry x="0" y="0" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="3" value="B" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="group1">
<mxGeometry x="140" y="0" width="120" height="60" as="geometry"/>
</mxCell>`
export const XML_REFERENCE: Record<"tables" | "layers" | "groups", string> = {
tables: `## Tables
A table is three levels of cells, as draw.io's sidebar inserts them: the table container (style "shape=table;startSize=0;container=1;collapsible=0;childLayout=tableLayout;", parent="1", geometry wide enough for all columns), one row per table row (style as below, parent = table id, width = table width, height = row height, y = row offset), and one cell per column inside each row (style as below, parent = row id, x = column offset, y = 0, width = column width, height = row height; the cell text is the value). A header row can use fontStyle=1 on its cells. Example with 2 rows and 2 columns:
${indent(TABLES_EXAMPLE)}
`,
layers: `## Layers
A layer is the one case of a cell with parent="0" and no vertex or edge attribute; id="1" is the default layer. Add a layer cell and give shapes parent="<layer id>" to put them on it:
${indent(LAYERS_EXAMPLE)}
`,
groups: `## Groups
A group is a container with style="group" (parent="1", geometry enclosing its children). Children use parent="<group id>" and coordinates relative to the group; moving the group moves them:
${indent(GROUPS_EXAMPLE)}
`,
}