diff --git a/crates/CLAUDE.md b/crates/CLAUDE.md index eab79bbb1..77b9d6304 100644 --- a/crates/CLAUDE.md +++ b/crates/CLAUDE.md @@ -401,26 +401,29 @@ Every host close path clears its respective hover state so reopening starts un-h Eight first-party tools registered today (v0.8.0+): -| Tool | Kind | Args | Command emitted | File | -| ----------------------- | ----- | -------------------------------------------------------------------------------------------------- | -------------------- | --------------------- | -| `get_document_info` | read | — | — | `mcp/tools.rs` | -| `get_selection` | read | — | — | `mcp/tools.rs` | -| `get_node` | read | `node_id` | — | `mcp/tools.rs` | -| `list_pages` | read | — | — | `mcp/tools.rs` | -| `list_variables` | read | — | — | `mcp/tools.rs` | -| `get_active_theme` | read | — | — | `mcp/tools.rs` | -| `set_variable_color` | write | `name`, `hex` | `SetVariableColor` | `mcp/write_tools.rs` | -| `set_active_axis_value` | write | `axis`, `value` | `SetActiveAxisValue` | `mcp/write_tools.rs` | -| `insert_node` | write | `kind`, `name`, `x`/`y`/`width`/`height`, optional `fill_hex` | `InsertNode` | `mcp/write_tools.rs` | -| `update_node` | write | `node_id` + any of `x`/`y`/`width`/`height`/`name`/`fill_hex` | `UpdateNode` | `mcp/write_tools.rs` | -| `delete_node` | write | `node_id` | `DeleteNode` | `mcp/write_tools.rs` | -| `move_node` | write | `node_id`, `target_parent_id` (0 = page root) | `MoveNode` | `mcp/write_tools.rs` | -| `copy_node` | write | `node_id`, `target_parent_id` (0 = page root) | `CopyNode` | `mcp/write_tools.rs` | -| `replace_node` | write | `node_id`, `kind`, `name`, `x`/`y`/`width`/`height`, optional `fill_hex`, optional `drop_children` | `ReplaceNode` | `mcp/write_tools.rs` | -| `batch_design` | write | `nodes_json` (JSON array of `{kind,name,x,y,width,height,fill_hex?}`) | `BatchInsert` | `mcp/batch_design.rs` | -| `set_variable_number` | write | `name`, `value` (finite f64) | `SetVariableScalar` | `mcp/scalar_vars.rs` | -| `set_variable_string` | write | `name`, `value` (free-form text) | `SetVariableScalar` | `mcp/scalar_vars.rs` | -| `set_variable_boolean` | write | `name`, `value` (`"true"`/`"false"`) | `SetVariableScalar` | `mcp/scalar_vars.rs` | +| Tool | Kind | Args | Command emitted | File | +| ----------------------- | ----- | -------------------------------------------------------------------------------------------------- | ------------------------------ | --------------------- | +| `get_document_info` | read | — | — | `mcp/tools.rs` | +| `get_selection` | read | — | — | `mcp/tools.rs` | +| `get_node` | read | `node_id` | — | `mcp/tools.rs` | +| `list_pages` | read | — | — | `mcp/tools.rs` | +| `list_variables` | read | — | — | `mcp/tools.rs` | +| `get_active_theme` | read | — | — | `mcp/tools.rs` | +| `set_variable_color` | write | `name`, `hex` | `SetVariableColor` | `mcp/write_tools.rs` | +| `set_active_axis_value` | write | `axis`, `value` | `SetActiveAxisValue` | `mcp/write_tools.rs` | +| `insert_node` | write | `kind`, `name`, `x`/`y`/`width`/`height`, optional `fill_hex` | `InsertNode` | `mcp/write_tools.rs` | +| `update_node` | write | `node_id` + any of `x`/`y`/`width`/`height`/`name`/`fill_hex` | `UpdateNode` | `mcp/write_tools.rs` | +| `delete_node` | write | `node_id` | `DeleteNode` | `mcp/write_tools.rs` | +| `move_node` | write | `node_id`, `target_parent_id` (0 = page root) | `MoveNode` | `mcp/write_tools.rs` | +| `copy_node` | write | `node_id`, `target_parent_id` (0 = page root) | `CopyNode` | `mcp/write_tools.rs` | +| `replace_node` | write | `node_id`, `kind`, `name`, `x`/`y`/`width`/`height`, optional `fill_hex`, optional `drop_children` | `ReplaceNode` | `mcp/write_tools.rs` | +| `batch_design` | write | `nodes_json` (JSON array of `{kind,name,x,y,width,height,fill_hex?}`) | `BatchInsert` | `mcp/batch_design.rs` | +| `set_variable_number` | write | `name`, `value` (finite f64) | `SetVariableScalar` | `mcp/scalar_vars.rs` | +| `set_variable_string` | write | `name`, `value` (free-form text) | `SetVariableScalar` | `mcp/scalar_vars.rs` | +| `set_variable_boolean` | write | `name`, `value` (`"true"`/`"false"`) | `SetVariableScalar` | `mcp/scalar_vars.rs` | +| `design_skeleton` | write | `nodes_json` (same as batch_design) | `BatchInsert` (phase=skeleton) | `mcp/batch_design.rs` | +| `design_content` | write | `nodes_json` (same as batch_design) | `BatchInsert` (phase=content) | `mcp/batch_design.rs` | +| `design_refine` | write | `nodes_json` (same as batch_design) | `BatchInsert` (phase=refine) | `mcp/batch_design.rs` | Read tools snapshot `Document` state at registration time. Write tools stay `&self`: they validate args and return `ToolOutcome::OkWithCommand(result, command)` for the host to apply via `Document::apply_mcp_command(command)`. The apply path follows pre-validate-then-mutate discipline (id space, target existence, geometry, hex, container-children consent) so a bad arg never leaves the document half-mutated. @@ -457,12 +460,13 @@ mcp_tests.rs (in crate root) Cross-cutting: stdio dispatch, parser invariants, w `openpencil-desktop --mcp ` (`crates/openpencil-desktop/src/mcp_serve.rs`) runs a JSON-RPC stdio MCP server backed by the .op file at ``. External CLIs (Claude Code / Codex / Gemini / Copilot) spawn the binary in this mode to drive the Rust editor the same way they drive TS pen-mcp. -- Handshake: `initialize` returns protocol version + capabilities + serverInfo; `tools/list` enumerates all 18 tools with JSON inputSchemas; `notifications/initialized` + `ping` handled inline. +- Handshake: `initialize` returns protocol version + capabilities + serverInfo; `tools/list` enumerates all 21 tools with JSON inputSchemas; `notifications/initialized` + `ping` handled inline. - Per-call lifecycle: re-build the `ToolRegistry` against the live document (so read-tool snapshots reflect prior writes) → dispatch through `run_stdio_with_applier` → applier closure mutates the doc + saves to disk on each successful write. - Top-level method / id sniffing uses the same key-walker discipline as the wire parser so nested keys can't shadow the real top-level fields. ### Pending -- Layered design workflow tools (`design_skeleton`, `design_content`, `design_refine`) — currently `batch_design` covers single-shot leaf generation; the layered variants need either intent-phase metadata or richer subtree shapes. -- A real JSON Node parser would unlock `replace_node`'s subtree path + grow batch_design beyond leaf-only. +- A real JSON Node parser would unlock `replace_node`'s subtree path + grow batch_design / design_skeleton beyond leaf-only. +- Per-phase apply semantics for the design\_\* workflow (e.g. design_refine emitting UpdateNode batches against existing nodes instead of fresh inserts). +- HttpServer / streamable-http MCP transport (lifecycle scaffold exists in `chat_http_server.rs`; wire protocol unverified). - HttpServer / streamable-http transport (currently the spawn-from-IPC scaffold has the lifecycle but not the wire format).