Files
designcraft/docs/mcp.md
T
aiongg 7e4c75fce9 Merge pr/662: Commands: reject unknown spread values instead of falling back to page 1 (#662)
# Conflicts:
#	crates/engine/src/cmd/file.rs
#	crates/engine/src/cmd/guides.rs
#	crates/engine/src/cmd/object.rs
2026-10-12 23:59:28 +00:00

8.5 KiB

MCP server

designcraft-cli mcp is a Model Context Protocol server that lets Claude and other agents drive DesignCraft: create documents, lay out frames and text, apply styles, place images, and look at the result as PNG images. It speaks newline-delimited JSON-RPC 2.0 on stdin/stdout (protocol 2025-06-18, also 2025-03-26 and 2024-11-05); logs go to stderr. The implementation is the designcraft-mcp crate.

Register it

Build the CLI (cargo build --release -p designcraft-cli), then:

# Headless: an in-process engine, no window
claude mcp add designcraft -- /path/to/target/release/designcraft-cli mcp

# Drive the running desktop app (start it first with a control port)
designcraft --control 7979
claude mcp add designcraft-app -- /path/to/target/release/designcraft-cli mcp --connect 7979

Other MCP clients use the same command line, e.g. in a JSON config:

{"mcpServers": {"designcraft": {"command": "/path/to/designcraft-cli", "args": ["mcp"]}}}

From an installed release

The release packages ship designcraft-cli alongside the desktop app, so no build is needed:

Install CLI
Windows (MSI) C:\Program Files\DesignCraft\designcraft-cli.exe by default (wherever you installed it otherwise), not on PATH
Linux (deb, rpm) /usr/bin/designcraft-cli
macOS the separate designcraft-cli-<version>-macos-<arch>.zip release asset (the .app holds only the desktop app)
# Windows, default install folder
claude mcp add designcraft -- "C:\Program Files\DesignCraft\designcraft-cli.exe" mcp
# Linux, or macOS with the CLI unzipped onto PATH
claude mcp add designcraft -- designcraft-cli mcp

Modes

Headless (mcp) Connected (mcp --connect PORT or HOST:PORT)
Engine In-process designcraft_engine::Session, starts with an empty Letter document (--sample opens the sample magazine) The app's session, through its loopback control channel (control-protocol.md)
Rendering designcraft-render (CPU) The app's renderer
Window tools (screenshot, click, drag, menu_list, ui_inspect, ui_set, dialog_*) Return an error explaining how to connect Work
UI-only commands via execute (view.*, window.*, help.*, edit.dynamicSpelling, and app.* except app.links) Error Work

In connected mode every tool maps to a control-channel method (engine.execute, ui.render, ui.pointer, …), so what the agent does shows up live in the window. A failed request is not resent: the operation may have completed, so inspect the document before retrying. The next request re-establishes the connection.

Coordinates

Points (1/72 in), y down, in spread space: on a single-page spread the page's top-left is (0, 0); on a facing spread the right page starts at x = page width. inspect_document lists page bounds per spread. Page indices in render_page / export_png are 0-based. Commands that create items take spread: a body spread index, or {"kind": "parent", "index": 0} for a parent spread (its index in parents of inspect_document); any other value is an error. pointer events in connected mode may use "space": "screen" (egui points, see ui_inspect → canvasRect).

Text offsets

Positions in a story's text are UTF-8 byte offsets, not character counts: anchor / focus of text.select, start / end of story.replaceRange and of find.find matches, the pos that text.placeCaret, text.insert and text.move return, a story's length in get_story and inspect_document, and its overset position in get_story. A character outside ASCII takes 2 to 4 bytes (é 2, — and は 3), so the story はただ商店 has length 15 and its second character starts at 3. An offset inside a character snaps back to the start of that character.

Text and image fitting

Hyphenation is on by default for new text (hyphenate: true). To turn it off, select the text or a text frame, then call execute:

execute          {"command": "type.para", "params": {"attrs": {"hyphenate": false}}}

With a text selection, this formats the selected paragraphs; with selected text frames, it formats their whole stories.

Use execute with object.fit to fit a selected graphic frame (or pass ids):

mode What it does
fillProportionally Preserves aspect ratio, covers the frame and clips overflow.
fitProportionally Preserves aspect ratio, shows the whole image and may leave empty space.
fitContentToFrame Stretches the content to the frame's width and height.
centerContent Keeps the current size and positions content at the fitting alignment (center by default).
fitFrameToContent Resizes the frame to the content's bounds.

Tools

Tool What it does
list_commands Every command (id, label, menu path, shortcut, params, enabled), with filter / enabledOnly
execute Run any command: {command, params}
batch Run commands: [{command, params}] (or script text, one command.id {json} per line) in order, stopping at the first error (reports failedIndex); "$N.path" parameters use earlier results (agents.md)
inspect_document Pages, spreads, items (ids, bounds, fill, story), stories (overset), layers, styles, swatches, selection
get_story Text, frames, paragraphs, lines, overset of a story (story or text frame)
set_story_text Replace a story's text (\n = new paragraph)
new_document file.new (preset, width, height, pages, facingPages, columns, gutter, margins, bleed, title) or sample: true
open_document / save_document Native .designcraft files
place_image Image from path or base64 into frame, the selected frame, or a new frame at x, y, width
render_page Render a page → MCP image content (PNG, base64); optional path to also save it
export_png Write a page as PNG (or JPEG by extension)
screenshot Whole app window → image content (connected only; captured to a temp file and read back)
select_tool Activate a tool (selection, type, rectangleFrame, line, …)
pointer Mouse gesture events (down/drag/up/move/doubleclick) for the active tool, optional tool
key Key press with modifiers (headless: active tool → command shortcuts → tool shortcuts)
type_text Type at the text insertion point (or into the focused dialog field in the app)
ime Input method events at the Type tool's caret: preedit (marked text in place, active = converting clause in characters), commit (typed as one step)
click / drag Real pointer input at screen points (connected only)
menu_list, ui_inspect, ui_set Menu tree, UI state, change UI state (connected only)
dialog_set / dialog_confirm / dialog_cancel Fill in and close the open dialog (connected only)

Tool failures come back as results with isError: true and a message the model can act on; protocol errors use JSON-RPC error codes. Resources: designcraft://document (document.inspect) and designcraft://commands.

Example

new_document     {"preset": "A4", "margins": 36}
execute          {"command": "frame.create", "params": {"rect": [36, 36, 559, 200], "content": "text", "text": "Hello"}}
                 → {"id": 8, "story": 9}
set_story_text   {"story": 9, "text": "Summer Fair\nSaturday in the park"}
execute          {"command": "type.char", "params": {"size": 28}}
place_image      {"path": "/tmp/photo.jpg", "x": 36, "y": 220, "width": 523}
render_page      {"page": 0}       → PNG image

Or with the Type tool, the way a user would:

pointer    {"tool": "type", "events": [{"kind": "down", "x": 72, "y": 72}, {"kind": "drag", "x": 300, "y": 200}, {"kind": "up", "x": 300, "y": 200}]}
type_text  {"text": "Hello"}
key        {"key": "Enter"}
key        {"key": "Escape"}

app.links returns the ArtCraft Discord (https://discord.gg/artcraft), the website (https://getartcraft.com), DesignCraft's app page (https://getartcraft.com/apps/designcraft), its GitHub repository (https://github.com/storytold/designcraft) and the issue tracker. In the running app, the UI commands help.discord, help.appPage, help.github, help.issues, help.website, help.app {app} open them in the browser and help.about {tab?} shows the About window (tabs about, contributors, models). The server's serverInfo.websiteUrl is the app page.