mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 10:09:01 -06:00
crates/mcp (lightcraft-mcp, L5): hand-written MCP over stdio (JSON-RPC 2.0, protocol 2025-06-18 with 2025-03-26/2024-11-05 negotiation): initialize, ping, tools/list, tools/call, resources/list/read. Two backends behind one trait: Headless (in-process engine Session; answers engine.execute, engine.commands, ui.render, app.export) and Remote (the desktop app's loopback JSON-lines control channel; lazy connect + reconnect). Tools: helpers (list_commands, run_command, import, query_photos, select_photos, list_controls, get/set_develop, apply_preset, crop, render_photo -> image content, export), UI tools when connected (screenshot -> image content, inspect_ui, set_ui, list_widgets, click, press_key, type_text, pointer_gesture), plus one cmd_<id> tool generated per command in the registry (--compact omits them). apps/lightcraft-cli: `mcp [--connect [ADDR]] [--demo] [--compact] [FILES]`, `render <in> -o <out> [--set control=value]... [--settings] [--preset] [--size] [--quality]`, `commands`, `controls`. Acceptance e2e: crates/mcp/tests/e2e.rs sets exposure over MCP and renders (headless and through the TCP transport); apps/lightcraft-cli/tests/cli.rs does the same through the real binary's stdio. Docs: docs/mcp.md, docs/control-protocol.md. Also rustfmt of one line in pipeline/finish.rs (main was not fmt-clean). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2.1 KiB
2.1 KiB
Control protocol
lightcraft --control 7980 (or LIGHTCRAFT_CONTROL_PORT=7980) starts a JSON-lines server on
127.0.0.1:7980 (loopback only). One request per line, one reply per line, in order:
→ {"id": 1, "method": "engine.execute", "params": {"command": "photo.rate", "params": {"rating": 4}}}
← {"id": 1, "ok": true, "result": null}
← {"id": 2, "ok": false, "error": "unknown command `nope`"}
Requests are answered on the UI thread between frames (timeout 60 s). The MCP server's connect
mode (mcp.md) is a thin layer over this channel. Implementation:
crates/ui-egui/src/control.rs (methods) and apps/lightcraft/src/control_server.rs (transport).
Methods
| Method | Params | Result |
|---|---|---|
engine.execute (alias ui.menu.invoke) |
{command, params?} |
Run any engine or UI command (see engine.commands) |
engine.commands |
— | Engine + UI commands: id, label, menu, shortcut, params doc, enabled |
ui.menu.list |
— | Menu entries |
ui.inspect |
— | UI state, window, canvas/image rects, active photo, selection, perf, status |
ui.widgets |
{filter?} |
On-screen widgets {id, rect: [x, y, w, h]} (screen points) |
ui.clickWidget / ui.dragWidget |
{id, count?, fx?, fy?} / {id, toX?, toY?, dx?, dy?, steps?} |
Real egui input on a widget |
ui.move / ui.click / ui.drag |
{x, y, count?, button?} / {x, y, toX, toY, steps?} |
Raw pointer input, screen points |
ui.pointer |
{events: [{kind: down|drag|up, x, y}], alt?, shift?, cmd?} |
Gesture in normalized image coordinates (Detail view) |
ui.key |
{key, cmd?, shift?, alt?, ctrl?} |
Key press |
ui.text |
{text} |
Text input |
ui.scroll |
{dx, dy} |
Mouse wheel |
ui.set |
partial UI state, e.g. {"view": "detail"} |
Resulting UI state |
ui.dialog.confirm / ui.dialog.cancel |
— | Close the open dialog |
ui.resize |
{width, height} |
Resize the window |
ui.screenshot |
{path?} |
{path, width, height} once the frame (with finished renders) is captured |
ui.render |
{id?, size?, path?} |
Render a photo (PNG to path), {width, height} |
app.quit |
— | Close the app |