A render of a large drawing (a set of sheets in model space) fits every sheet in one image, too small to read. `render` takes `window` [[x0,y0],[x1,y1]]: just that area, 1600 px wide with the window's shape unless sized (the app's and the headless `ui.render` both; the display list is built for that view only). `View::of_window` sizes it. `find_text` (engine command `texts`, read-only) returns the readable text of TEXT, MTEXT (formatting removed), block attributes and multileaders with insertion point, height, rotation, style, layer and handle, filtered by content, layer and a window. FIND selects and can replace; this only reports, so an agent can find room names, sheet numbers and titles without reading all the geometry. On a 39,600-object permit set: 5,861 texts in 0.02 s; one 90 x 59.4 sheet rendered at 2400 x 1584 in 0.5 s. Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: Brandon Thomas <[email protected]>
3.9 KiB
CADCraft and AI agents (MCP)
CADCraft speaks the Model Context Protocol over stdio.
Start it
# Headless: an in-process drawing session, no window.
cadcraft-cli mcp
# Bridged to the running app (watch the agent draw):
cadcraft --control 7979 &
cadcraft-cli mcp --connect 127.0.0.1:7979
An MCP client usually starts its servers when it starts, often before the app is open. With
--lazy the bridge starts anyway and reaches the app at the first tool call; until the app answers,
each call fails with a message saying how to open it. --launch also opens CADCraft (macOS) with
that control port at a call that finds nothing, and waits for it:
{ "mcpServers": { "cadcraft": { "command": "cadcraft-cli", "args": ["mcp", "--connect", "127.0.0.1:7979", "--launch"] } } }
--connect needs the app's HOST:PORT. Without one (or with an unknown argument) cadcraft-cli mcp
exits with an error instead of falling back to a headless session, so a client meant for the app
never edits a separate drawing by mistake. If the app can't be reached, startup fails too.
Example client configuration (Claude Code / Claude Desktop):
{ "mcpServers": { "cadcraft": { "command": "cadcraft-cli", "args": ["mcp"] } } }
Tools
| Tool | What it does |
|---|---|
command_line {text} |
Type at the command line exactly like a person: line 0,0 @10,0 @0,5 c, circle 5,2 1, offset 0.5, zoom e. Spaces act as Enter; empty text is Enter. Returns the new prompt, keywords and output. |
script {text} |
A multi-line script (like .scr files). |
execute {command, params} |
Any command with JSON parameters, never a dialog. list_commands shows ids and parameter docs. |
list_commands {filter?} |
The command catalog; filter matches part of a command's id, label or an alias. |
inspect_drawing {entities?, limit?} |
Counts, extents, layers, styles, blocks, layouts, selection, undo history and entities. |
query_entities {type?, layer?, window?, crossing?, limit?, offset?} |
Filtered entities with handles and geometry. window [[x0,y0],[x1,y1]] finds objects whose geometry is inside it or crosses it (crossing: false: entirely inside). count is every match, returned the page (offset, limit, default 500). |
render {width?, height?, fit?, window?} |
A PNG image of the drawing, or of just the area window [[x0,y0],[x1,y1]] (one sheet or room of a large drawing; 1600 px wide with the window's shape unless sized). |
find_text {contains?, layer?, window?, limit?, offset?} |
The readable texts — TEXT, MTEXT without formatting codes, block attributes, multileaders — with text, insertion point, height, rotation, style, layer and handle; filtered by content (any case), layer and a window around the insertion points. Read-only (the texts command). |
new_drawing {metric?}, open {path}, save {path?}, cancel |
Files and Escape. save to a .svg, .png or .pdf path exports: the drawing keeps its name and unsaved changes. |
screenshot, ui_inspect, ui_click {x, y} |
Only when connected to the app. |
Resources: cadcraft://drawing (inspect JSON) and cadcraft://commands (catalog).
Arguments are checked against each tool's input schema. An unknown tool, arguments that aren't an
object, a missing required argument or one of the wrong type is a JSON-RPC error -32602 (invalid
params), so command_line {} never presses Enter by accident. A tool that runs and fails (a bad
point, an unknown command) returns a result with isError: true and the message.
A typical session
command_line {"text": "rectang 0,0 100,60"}execute {"command": "circle", "params": {"center": [10, 10], "radius": 4}}command_line {"text": "dimlinear 0,0 100,0 50,-8"}inspect_drawing {}— verify counts and geometry.render {}— look at it.
The end-to-end test in crates/mcp/src/tests.rs runs a session like this and checks the result
only through MCP.