Files
designcraft/docs/mcp.md
T
KnottyDyes 80c2904681 fix(mcp): prevent request replay after lost replies
Do not resend a request after a transport failure. Report the uncertain outcome and reconnect on the next explicit request.

Validation: all six cargo xtask ci gates passed on the merge candidate; 1,177 native tests passed and all 15 WASM packages checked. Lost-reply, invalid UTF-8 and timeout probes reproduced duplicate edits before the fix and one edit afterward.

Task: #514
2026-10-11 21:10:01 -06:00

123 lines
7.2 KiB
Markdown

# MCP server
`designcraft-cli mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) 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:
```sh
# 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:
```json
{"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) |
```sh
# 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](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. `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.
## 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](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) |
| `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
```text
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:
```text
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"}
```
## Community links
`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.