# Control protocol Start the app with a control port: `wordcraft --control 7981` (or `WORDCRAFT_CONTROL_PORT=7981`). It listens on `127.0.0.1` only. Send one JSON object per line, with the window's key (see [Keys](#keys)); each gets one reply line: ```json {"id": 1, "key": "…", "method": "engine.execute", "params": {"command": "text.insert", "params": {"text": "Hello"}}} {"id": 1, "ok": true, "result": {"anchor": {...}, "focus": {...}}} ``` Errors come back as `{"id": …, "ok": false, "error": "message"}`. A failed command leaves the document unchanged. A line that isn't valid JSON gets one error reply, then the server closes the connection (so an HTTP request sent to the port can't run a command); reconnect to continue. ## Keys Any local program (and possibly a web page) can reach a loopback port, so every request needs the window's key. Once the port is bound, the app writes a random key (256 bits, as hex) to `/control-key.` and removes the file when the window closes; a new start writes a new key. `` is the folder that holds the preferences: on Linux `$XDG_CONFIG_HOME/wordcraft` (by default `~/.config/wordcraft`), on macOS `~/Library/Application Support/WordCraft`, on Windows `%APPDATA%\WordCraft`. `` is the Flatpak instance id inside Flatpak, and `local-` otherwise. On Unix the file has mode 0600. ```sh cat ~/.config/wordcraft/control-key.local-7981 # Linux, outside Flatpak, port 7981 ``` - Send the key as top-level `"key"` with every request, or once per connection with `{"method": "auth", "key": "…"}` (reply `{"ok": true, "result": {}}`); later requests on that connection may leave `key` out. Only `auth` reads `params.key`; for other methods `params.key` is an ordinary parameter (`ui.key {"key": "Enter"}` presses Enter). - The key is checked on every request. A missing or wrong key gets `{"ok": false, "error": "unauthorized"}`, and the app closes the connection. - A line over 1 MiB gets `{"ok": false, "error": "line too long"}`, and the app closes the connection. - A connection that sends no authorised request within 30 s of being accepted is closed. That is one limit: blank lines and bad JSON do not extend it. After an authorised request the connection may stay idle. `wordcraft-cli mcp --connect` takes the key from `WORDCRAFT_CONTROL_KEY`, or else from the key file for the port it connects to, and reads it again on every new connection (the app may have restarted). It sends the key only to loopback addresses (`127.0.0.0/8`, `::1`, `localhost`). For any other host it sends no key, says so, and the app refuses its requests: to drive an app on another machine, forward its port to `127.0.0.1` (for example `ssh -L 7981:127.0.0.1:7981 host`) and set `WORDCRAFT_CONTROL_KEY` to that app's key. ## Methods | Method | Params | Result | |---|---|---| | `engine.execute` | `command`, `params` | the command's result (any id from `engine.commands`, including UI commands `ui.tab`, `ui.dialog`, `ui.backstage`, `ui.zotero.*` (see `docs/zotero.md`), `ui.language` (`{"value": "auto"|"en"|"zh-hans"|"zh-hant"|"ja"|"pt-br"|"es"|"uk"}`), `ui.theme` (`{"value": "system"|"light"|"dark"}`; `system` follows the OS appearance, light when it reports none)…) | | `engine.commands` | — | every command: id, label, location, shortcut, params, enabled | | `document.inspect` | `text?` | blocks (text, style, runs, lists, tables), parts, sections, selection, pages | | `ui.inspect` | — | UI state, view, dialog, window size, page rects on screen, caret, comment balloons (`balloons`: id + screen rect; `balloon`: the selected one), perf | | `ui.click` | `x`, `y`, `button?`, `count?`, `shift?`, `cmd?`, `alt?` | real pointer input at window coordinates | | `ui.clickText` | `page` (0-based), `x`, `y` (points from the page's top-left), `count?` | click inside a page | | `ui.move` / `ui.drag` | `x`,`y` / `x`,`y`,`toX`,`toY`,`steps?` | pointer move / drag | | `ui.press` / `ui.release` | `x`,`y`, modifiers | primary button down / up there (with `ui.move` between: a drag you can screenshot halfway) | | `ui.key` | `key` (`B`, `Enter`, `Left`…), `shift?`, `alt?`, `cmd?` | key press through egui (shortcuts apply) | | `ui.text` | `text` | typed text through egui | | `ui.screenshot` | `path?` | PNG of the window | | `ui.render` | `path`, `page?` (1-based), `scale?` | PNG of a page, rendered by the engine | | `ui.parity` | — | feature-catalog parity | | `ui.resize` / `ui.focus` / `app.quit` | | window control | Any other method name is treated as a command id: `{"method": "format.bold"}` works. Positions are `{"story": "body" | {"part": n}, "path": [block, row, cell, block, …], "off": byteOffset}`.