From e71aa319bb6223cd0e854dfdd0c96d26a7690428 Mon Sep 17 00:00:00 2001 From: Kayshen-X Date: Thu, 14 May 2026 23:20:34 +0800 Subject: [PATCH] docs(crates): document batch_design + scalar_vars + --mcp host wiring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates the MCP server section in crates/CLAUDE.md to reflect today's shipped work: - Tool catalog grew from 8 → 18 (added batch_design + 3 scalar variable tools + host wiring closes BLOCKER #1) - File layout includes the new mcp/batch_design.rs + mcp/ scalar_vars.rs + their sibling test files - New "Host wiring" subsection explains `openpencil-desktop --mcp ` lifecycle + handshake - Pending list updated: layered design workflow tools (design_skeleton/content/refine) + JSON Node parser remain --- crates/CLAUDE.md | 54 +++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/crates/CLAUDE.md b/crates/CLAUDE.md index c9a444465..eab79bbb1 100644 --- a/crates/CLAUDE.md +++ b/crates/CLAUDE.md @@ -401,22 +401,26 @@ 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` | +| 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` | 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. @@ -438,15 +442,27 @@ mcp.rs Spine: types + ToolRegistry + run_stdio* + JSON ser mcp/parser.rs Wire parse — tri-state arguments_field, top-level walker mcp/tools.rs Read tools (6) mcp/tools_tests.rs Read-tool tests -mcp/write_tools.rs Write tools (8) + snapshot factories +mcp/write_tools.rs Core write tools (insert/update/delete/move/copy/replace + 2 var tools) mcp/write_tools_tests.rs Write-tool tests (SetVariableColor / SetActiveAxisValue / Insert / Update / Delete / Move) +mcp/batch_design.rs BatchDesign tool + hand-rolled nodes_json parser +mcp/batch_design_tests.rs BatchDesign + BatchInsert apply tests +mcp/scalar_vars.rs SetVariableNumber / SetVariableString / SetVariableBoolean +mcp/scalar_vars_tests.rs Scalar tools + Color-rejection regression mcp/copy_node_tests.rs Sibling — CopyNode tests mcp/replace_node_tests.rs Sibling — ReplaceNode tests (incl. destructive-swap guard) mcp_tests.rs (in crate root) Cross-cutting: stdio dispatch, parser invariants, wire-shape regression ``` +### Host wiring + +`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. +- 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 -- Wire `pen-server` (or the desktop binary) to register the `ToolRegistry` + spawn `run_stdio_with_applier` against the live document. -- Outstanding TS-parity write tools: `batch_design`, `design_skeleton`, `design_content`, `design_refine` — all require a JSON Node parser to accept full subtrees. +- 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. - HttpServer / streamable-http transport (currently the spawn-from-IPC scaffold has the lifecycle but not the wire format).