diff --git a/packages/mcp-server/src/diagram-operations.ts b/packages/mcp-server/src/diagram-operations.ts
index 5315f191..64dd810a 100644
--- a/packages/mcp-server/src/diagram-operations.ts
+++ b/packages/mcp-server/src/diagram-operations.ts
@@ -216,6 +216,20 @@ export function applyDiagramOperations(
continue
}
+ // A cut-off cell (its XML still streaming in a preview) would
+ // parse in Chrome to a cell without its geometry
+ const syntaxError = getXmlSyntaxError(
+ `${op.new_xml}`,
+ )
+ if (syntaxError) {
+ errors.push({
+ type: "update",
+ cellId: op.cell_id,
+ message: `new_xml is not well-formed XML: ${syntaxError}`,
+ })
+ continue
+ }
+
// Parse the new XML
const newDoc = parser.parseFromString(
`${op.new_xml}`,
@@ -268,6 +282,20 @@ export function applyDiagramOperations(
continue
}
+ // A cut-off cell (its XML still streaming in a preview) would
+ // parse in Chrome to a cell without its geometry
+ const syntaxError = getXmlSyntaxError(
+ `${op.new_xml}`,
+ )
+ if (syntaxError) {
+ errors.push({
+ type: "add",
+ cellId: op.cell_id,
+ message: `new_xml is not well-formed XML: ${syntaxError}`,
+ })
+ continue
+ }
+
// Parse the new XML
const newDoc = parser.parseFromString(
`${op.new_xml}`,
diff --git a/packages/mcp-server/tests/diagram-operations.test.ts b/packages/mcp-server/tests/diagram-operations.test.ts
index 77d4de13..97af0dcd 100644
--- a/packages/mcp-server/tests/diagram-operations.test.ts
+++ b/packages/mcp-server/tests/diagram-operations.test.ts
@@ -149,3 +149,52 @@ describe("a wrapped mxCell with its wrapper's id", () => {
expect(result).toContain('label="B"')
})
})
+
+describe("cut-off new_xml", () => {
+ // While edit_diagram streams, the last operation's XML is often
+ // incomplete. Chrome's DOMParser keeps the partial cell, so it must be
+ // refused before it reaches the diagram.
+ const CUT = ``
+
+ it("refuses an update whose XML is cut off", () => {
+ const { result, errors } = applyDiagramOperations(DOC, [
+ { operation: "update", cell_id: "b", new_xml: CUT },
+ ])
+ expect(errors).toHaveLength(1)
+ expect(errors[0]).toMatchObject({ type: "update", cellId: "b" })
+ expect(errors[0].message).toContain("not well-formed")
+ expect(result).toContain('value="B"')
+ expect(result).not.toContain("B2")
+ })
+
+ it("refuses an add whose XML is cut off", () => {
+ const { result, errors } = applyDiagramOperations(DOC, [
+ {
+ operation: "add",
+ cell_id: "n",
+ new_xml: ` {
+ const { result, errors } = applyDiagramOperations(DOC, [
+ {
+ operation: "update",
+ cell_id: "b",
+ new_xml: ``,
+ },
+ {
+ operation: "add",
+ cell_id: "n",
+ new_xml: ``,
+ },
+ ])
+ expect(errors).toHaveLength(1)
+ expect(result).toContain('value="B3"')
+ expect(result).not.toContain('id="n"')
+ })
+})