Skip to content

fix(mcp): replace edit_diagram 30s time gate with content comparison#890

Merged
DayuanJiang merged 7 commits into
DayuanJiang:mainfrom
NgoQuocViet2001:fix/diagram-edit-freshness-minimal
Jul 12, 2026
Merged

fix(mcp): replace edit_diagram 30s time gate with content comparison#890
DayuanJiang merged 7 commits into
DayuanJiang:mainfrom
NgoQuocViet2001:fix/diagram-edit-freshness-minimal

Conversation

@NgoQuocViet2001

@NgoQuocViet2001 NgoQuocViet2001 commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • stop expiring a valid get_diagram snapshot after 30 seconds (the wall-clock gate rejected slow-reasoning clients even when they followed the documented workflow — MCP Tool Error Response: Error: You must call get_diagram first before edit_diagram. #885)
  • instead of removing staleness detection entirely, replace the timeout with a content comparison: the server remembers the exact state-store XML the model last saw (lastSeenXml, set by get_diagram / create_new_diagram / edit_diagram / page CRUD) and rejects edit_diagram only when the live browser state actually differs — i.e. the user made manual edits the model hasn't seen yet
  • slow reasoning never trips the gate; unseen user edits still do, with a dedicated error message telling the model to re-fetch once and retry
  • the gate compares the RAW browser-pushed XML on both sides (not the normalised mxfile form) so format differences can never be misread as content changes

Related to #885

Validation

  • MCP server TypeScript build passed
  • npm test: 52/52 passed (45 existing + 4 new unit tests for the gate + wiring tests)
  • Biome check passed
  • Live end-to-end run against the real stdio server + browser:
    1. edit_diagram without get_diagram 12s after create (browser had loaded and pushed autosave) → allowed, no false positive
    2. simulated manual user edit (POST to /api/state like the browser autosave path), then edit_diagram without get_diagramrejected as stale
    3. one get_diagram, then edit_diagramallowed, recovery works
    4. get_diagram, wait 35s (> old 30s limit), edit_diagramallowed, MCP Tool Error Response: Error: You must call get_diagram first before edit_diagram. #885 scenario fixed

Notes

Built on top of @NgoQuocViet2001's original diagnosis in this PR: the 30s wall-clock gate was the root cause of #885. The follow-up commit keeps that fix while restoring the ability to detect mid-session manual browser edits, which the one-time sentinel check gave up.

@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
next-ai-draw-io Ready Ready Preview, Comment Jul 12, 2026 6:22am

The 30s wall-clock gate rejected slow-but-correct clients (DayuanJiang#885).
Instead of a timeout, remember the exact state-store XML the model
last saw (get_diagram / create_new_diagram / edit_diagram / page CRUD)
and reject edit_diagram only when the live browser state differs -
i.e. the user made edits the model hasn't seen yet. Slow reasoning
no longer trips the gate, while unseen manual edits still do.
@DayuanJiang DayuanJiang changed the title fix(mcp): stop expiring diagram context after 30 seconds fix(mcp): replace edit_diagram 30s time gate with content comparison Jul 12, 2026
…ased gate

The 'You MUST call get_diagram BEFORE this tool' requirement and the
'Skipping get_diagram WILL cause user's changes to be LOST' warning no
longer match server behavior: a stale edit is rejected with no side
effects, never silently applied. Describe the freshness check instead,
and direct get_diagram usage at its real purpose - learning the current
diagram content when the model doesn't already know it.
draw.io re-serialises the document when pushing state back (attribute
order, pretty-printing, regenerated diagram ids, viewport attributes,
mxfile host), so byte comparison could flag an unchanged diagram as
stale. Fingerprint what a user can actually change instead - page set,
page names, and each page's root cell tree with sorted attributes -
keeping byte equality as the fast path. A bare mxGraphModel now also
fingerprints identically to its single-page mxfile wrapping.
@DayuanJiang

DayuanJiang commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Thanks for the PR, your diagnosis is spot on. However, the solution may run into trouble in the following scenario:

user: draw a cat
agent: create_diagram
user: change the eyes to blue
agent: get_diagram -> edit_diagram
(user: move the leg position in the panel)
user: make the leg shorter
agent: edit_diagram 

In the last step, the agent should have called get_diagram before editing, since the user had moved the leg position. But it didn't, because it had already used get_diagram earlier in the conversation.

I thought of a better way to solve this: since users rarely change the diagram themselves, the agent can just call get_diagram directly by default. I'd also add a mechanism to track whether the user changed the diagram. If they did, get_diagram will fail with a message: "please get_diagram before edit".

A bare <mxGraphModel> pushed by the embed/sync path carries no page name,
so normalizeToMxfile invents "Page-1" — falsely reading any custom page
name as a content change and re-triggering the stale rejection on every
edit. When either side of the gate comparison is a bare mxGraphModel,
fingerprint cell trees only; full-mxfile comparisons still detect renames.
@DayuanJiang
DayuanJiang merged commit f3a8555 into DayuanJiang:main Jul 12, 2026
8 checks passed
@NgoQuocViet2001

Copy link
Copy Markdown
Contributor Author

@DayuanJiang Thanks for the concrete scenario and the stronger design. Tracking actual diagram-content changes is much more robust than a conversation-level gate, and I appreciate you carrying it through to the merged implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants