Whiteboard Canvas#
The Chat page shows an Excalidraw whiteboard beside the conversation. It works in both directions: you draw and send the result into the chat, and the assistant draws onto the same board.
Setup#
The canvas needs its MCP server running.
make docker-rebuild-canvas # under compose (recommended)
make run-canvas-mcp # or standalone, port 8766
Then enable it. SKIP_MCP=true disables the canvas as well as the printer, so
both have to be off:
SKIP_MCP=false SKIP_CANVAS=false make run-ui
Compose already sets both, so make docker-up needs nothing further.
Note
If the canvas server is not reachable, the assistant is not offered the whiteboard at all — the agent is dropped rather than advertised, so it will never claim to have drawn something it could not. Chat is unaffected.
That covers both ways it can be off: disabled by SKIP_CANVAS/SKIP_MCP, and
enabled but unreachable when the app started. The second case loads no tools
rather than failing outright, so it is checked by asking whether the agent has
any tools, not merely whether it exists.
Using it#
Send a drawing to the chat. Draw, optionally add a message, and press
📤 Send to Chat (or Ctrl/Cmd + Enter). The drawing enters the conversation
as an image, and its elements are sent to the canvas server so the assistant can
read them as data rather than only look at a picture.
Ask for a drawing. “Draw a simply supported beam with two supports and a downward load.” The assistant draws on the board and describes what it drew.
Iterate. “Add a label to the beam”, “remove the arrow”, “what’s on the whiteboard?” — it reads the current scene before editing, so it works with what is actually there, including your own strokes.
Important
Press Send to Chat before asking the assistant to modify something you drew. The UI only learns what is on the canvas when you export it, so an unsent drawing is replaced by whatever the assistant draws next. Sending first makes it both visible to the assistant and safe.
What persists, and where#
Two copies of every scene, for different reasons:
Where |
Lifetime |
|---|---|
Canvas MCP server |
Survives a container restart with |
EngiAI database |
Durable, per conversation, deleted with the conversation |
The database copy is the one that matters. It is restored when you reopen a conversation, so a drawing outlives both eviction and a server that was running without a state directory.
Each conversation has its own whiteboard. Switching chats switches boards.
Configuration#
Variable |
Default |
Purpose |
|---|---|---|
|
|
Disable the whiteboard agent |
|
|
Where the agent reaches the server |
|
|
Bind address and port |
|
see below |
Host-header allowlist for the SSE transport |
|
unset locally, |
Where the server persists scenes |
|
|
LRU bound on concurrent canvases |
|
|
Per-canvas element cap |
|
|
Per-call payload cap |
.env.example ships SKIP_CANVAS=True while the code default is enabled. Not a
contradiction: compose sets it explicitly and runs the server, whereas a fresh
local checkout has neither.
CANVAS_MCP_ALLOWED_HOSTS replaces the default list rather than extending
it — localhost, 127.0.0.1 and excalidraw-mcp-server, each in bare and
:PORT form. The SDK matches the Host header exactly, so omitting the
port-qualified form answers 421. The un-namespaced MCP_ALLOWED_HOSTS is
deliberately not read here; see Prusa transport
security for why that name is shared.
How it fits together#
Excalidraw component <--elements/scene_version-- Streamlit UI
|
pull / push (MCP over SSE)
v
ExcalidrawAgent --draw_elements, get_canvas--> Canvas MCP server
|
scene per canvas_id
canvas_id is the conversation’s thread_id, injected by the supervisor rather
than chosen by the model — so the assistant cannot draw into another
conversation’s whiteboard, nor claim you synced a drawing you did not. See
services/excalidraw_mcp_server/README.md
for the server’s own documentation and its security posture.
Troubleshooting#
The assistant says it cannot draw. The canvas server is unreachable. Check
docker compose ps excalidraw-mcp-server and that SKIP_MCP and SKIP_CANVAS
are both false.
A drawing did not appear. The whiteboard is written only when the scene version changes, which happens after a turn that called a drawing tool. If the assistant answered in prose without calling one, there is nothing to show — ask it to draw explicitly.
My drawing vanished when the assistant drew. It was not sent first; see the note above.
Nothing renders but chat works. That is the intended failure mode — every
canvas call is fail-soft, so a server that is down costs a drawing, never an
answer. ENGIAI_LOG_LEVEL=DEBUG shows the reason.