fix(mcp-server): review fixes for the preview URL and the theme menu

The stalled-tab note names the page start_session opened (the shell
with DRAWIO_PREVIEW_UI=shell) instead of the classic page. With the
shell not built, start_session opens the classic page and the server
says so at startup, as it does for a missing dist/drawio. BROWSER=none
skips the system browser, as Vite and CRA dev servers do: the e2e tests
set it. Without a fixed DRAWIO_UI, the shell's draw.io URL carries
themes=1 so Extras > Theme is offered, as on the classic page.
This commit is contained in:
dayuan.jiang
2026-10-11 20:56:07 +09:00
parent b6bd032c2c
commit 3ef14318f9
6 changed files with 45 additions and 4 deletions
+3
View File
@@ -47,7 +47,10 @@ export function getDrawioSrc(source: DrawioSource, dark: boolean): string {
proto: "json",
configure: "1",
}
// Without a fixed theme, embedded draw.io shows Extras > Theme only
// with themes=1 (its isThemeMenuVisible)
if (source.ui) params.ui = source.ui
else params.themes = "1"
Object.assign(params, {
spin: "0",
libraries: "0",
+1
View File
@@ -190,6 +190,7 @@ To give the AI your own drawing rules, write them in `~/.next-ai-drawio/instruct
| `DRAWIO_DARK` | `auto` | Dark mode of the draw.io editor: `auto` follows the system, `1` forces dark, `0` forces light. The page header keeps following the system. |
| `DRAWIO_PREVIEW_UI` | `classic` | The preview page `start_session` opens. `shell` opens the new canvas page built from the web app's canvas: it syncs with the server like the classic page, and an AI change of the page on screen is marked and undone with one Ctrl+Z (in progress: no History panel or download yet). |
| `DRAWIO_AUTO_SCREENSHOT` | unset | Set to `true` to attach a screenshot to every `create_new_diagram` and `edit_diagram` result, so the AI checks each drawing. Costs 2 to 10 s per call; the preview tab must be open and in front. A call can still pass `screenshot: false`. |
| `BROWSER` | unset | Set to `none` and `start_session` does not open the preview in the system browser (the convention of Vite and Create React App dev servers); the result still names the URL. |
| `DEBUG` | unset | Set to `true` to log debug messages to stderr. |
### Private Deployment (Self-hosted draw.io)
+8 -1
View File
@@ -122,7 +122,9 @@ export function previewUrl(
sessionId: string,
ui: PreviewUi = PREVIEW_UI,
): string {
const path = ui === "shell" ? "/shell/" : ""
// The classic page stands in while the shell is not built (a run from
// the sources), as embed.diagrams.net does for a missing dist/drawio
const path = ui === "shell" && shellDir !== null ? "/shell/" : ""
return `http://localhost:${port}${path}?mcp=${sessionId}`
}
@@ -430,6 +432,11 @@ export function startHttpServer(port = 6002): Promise<number> {
"No bundled draw.io (dist/drawio missing); the preview loads it from embed.diagrams.net",
)
}
if (PREVIEW_UI === "shell" && shellDir === null) {
log.warn(
"The canvas shell is not built (dist/shell missing); start_session opens the classic page",
)
}
resolve(port)
})
})
+4 -3
View File
@@ -382,9 +382,10 @@ registerWriteTool(
lastSeenXml: "",
}
// Open browser
// Open the browser. BROWSER=none (the convention of CRA and Vite
// dev servers) skips it: the e2e tests and headless hosts set it
const browserUrl = previewUrl(port, sessionId)
await open(browserUrl)
if (process.env.BROWSER !== "none") await open(browserUrl)
// A saved diagram comes back from its file. lastSeenXml stays
// empty, so edit_diagram asks for get_diagram first.
@@ -1287,7 +1288,7 @@ function previewStalled(sessionId: string): boolean {
* already succeeded must not run again, so it points to screenshot_diagram.
*/
function previewStalledNote(sessionId: string, next = "then retry") {
return `The preview tab is not responding (browsers pause background tabs). Ask the user to bring the preview tab to the front (http://localhost:${getServerPort()}?mcp=${sessionId}), ${next}.`
return `The preview tab is not responding (browsers pause background tabs). Ask the user to bring the preview tab to the front (${previewUrl(getServerPort(), sessionId)}), ${next}.`
}
function previewStalledError(sessionId: string) {
@@ -870,6 +870,15 @@ describe("canvas shell page", () => {
expect(previewUrl(6002, "mcp-x", "classic")).toBe(
"http://localhost:6002?mcp=mcp-x",
)
// Not built: the classic page, which works, rather than a 404
setShellDir(null)
try {
expect(previewUrl(6002, "mcp-x", "shell")).toBe(
"http://localhost:6002?mcp=mcp-x",
)
} finally {
setShellDir(shellDir)
}
})
})
+20
View File
@@ -5,6 +5,7 @@
import { readFileSync } from "node:fs"
import { join } from "node:path"
import { describe, expect, it } from "vitest"
import { getDrawioSrc } from "@/lib/drawio/drawio-config"
import { i18n } from "@/lib/i18n/config"
import {
getShellDictionary,
@@ -59,6 +60,25 @@ describe("shell runtime config", () => {
})
})
describe("shell draw.io URL", () => {
it("offers draw.io's theme menu unless a theme is fixed", () => {
// Embedded draw.io shows Extras > Theme only with themes=1 and no ui
const free = new URL(
getDrawioSrc({ editorUrl: "/drawio/index.html" }, false),
)
expect(free.searchParams.get("themes")).toBe("1")
expect(free.searchParams.get("ui")).toBeNull()
const fixed = new URL(
getDrawioSrc(
{ editorUrl: "/drawio/index.html", ui: "kennedy" },
false,
),
)
expect(fixed.searchParams.get("ui")).toBe("kennedy")
expect(fixed.searchParams.get("themes")).toBeNull()
})
})
describe("shell language", () => {
it("maps language tags to the dictionaries we have", () => {
expect(matchLocale("en-US")).toBe("en")