refactor: extract op-mcp crate

Relocate mcp.rs + mcp/* + mcp_tests.rs out of openpencil-shell-core
into a dedicated op-mcp crate. The MCP tool registry + JSON-RPC stdio
layer only depends on jian-ops-schema + op-editor-core (its tools
build on EditorState / EditorCommand). mcp.rs becomes the crate's
lib.rs; the mcp/ submodule files flatten to src/ so the `pub mod`
declarations resolve unchanged, and intra-module `super::` paths stay
valid. mcp_tests.rs becomes a #[cfg(test)] mod of lib.rs with its
`super::mcp::` paths rewritten to `crate::`. openpencil-desktop's
mcp_serve.rs now imports `op_mcp::*`. Pure relocation, no behaviour
change; 138 tests move with their code.
This commit is contained in:
Kayshen-X 2026-05-16 23:21:17 +08:00
parent 9a0112b9a9
commit e2e287c008
29 changed files with 30 additions and 13 deletions

15
crates/op-mcp/Cargo.toml Normal file
View file

@ -0,0 +1,15 @@
[package]
name = "op-mcp"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
description = "OpenPencil MCP server — JSON-RPC tool registry over op-editor-core EditorState / EditorCommand"
[lib]
name = "op_mcp"
path = "src/lib.rs"
[dependencies]
jian-ops-schema = { path = "../../vendor/jian/crates/jian-ops-schema" }
op-editor-core = { path = "../op-editor-core" }

View file

@ -8,10 +8,7 @@
//! `op_editor_core::EditorState` / `op_editor_core::EditorCommand`.
//! Read tools now snapshot an `EditorState` (canonical `PenDocument`);
//! write tools emit an `op_editor_core::EditorCommand` the host applies
//! via `EditorState::apply`. The module physically still lives inside
//! `openpencil-shell-core` — an `op-mcp` crate extraction is a later
//! Phase-7 task. shell-core's own `Document` (used by the widgets) is
//! untouched.
//! via `EditorState::apply`.
//!
//! ## Component-command gap
//!
@ -47,6 +44,9 @@ pub mod selected_ops_tools;
#[cfg(test)] mod replace_node_tests;
#[cfg(test)] mod batch_design_tests;
#[cfg(test)] mod scalar_vars_tests;
// Cross-cutting tests for the crate spine — stdio dispatch + parser
// invariants + a few read-tool registry round-trips.
#[cfg(test)] mod mcp_tests;
// The MCP command DTO is now `op_editor_core::EditorCommand` — the
// faithful port of the old shell-core `McpCommand`. Re-exported here

View file

@ -4,8 +4,8 @@
//! Ported off the old shell-core `Document` onto `op_editor_core::
//! EditorState`.
use super::mcp::test_fixtures::{add_variable, sample, state_with};
use super::mcp::*;
use crate::test_fixtures::{add_variable, sample, state_with};
use crate::*;
use jian_ops_schema::variable::{VariableKind, VariableScalar};
use std::collections::BTreeMap;
@ -280,10 +280,10 @@ fn json_escape_handles_special_chars() {
#[test]
fn get_document_info_reports_snapshot_via_registry() {
use super::mcp::test_fixtures::frame;
use crate::test_fixtures::frame;
let f = frame("n10", "F", 0.0, 0.0, 200.0, 100.0, vec![
super::mcp::test_fixtures::rect("n11", "a", 0.0, 0.0, 10.0, 10.0),
super::mcp::test_fixtures::rect("n12", "b", 20.0, 0.0, 10.0, 10.0),
crate::test_fixtures::rect("n11", "a", 0.0, 0.0, 10.0, 10.0),
crate::test_fixtures::rect("n12", "b", 20.0, 0.0, 10.0, 10.0),
]);
let s = state_with(vec![f]);
let info = document_info_snapshot(&s);

View file

@ -179,7 +179,7 @@ fn replace_node_rejects_malformed_drop_children() {
#[test]
fn parser_refuses_structured_drop_children_at_wire_layer() {
use crate::mcp::parse_tool_call;
use crate::parse_tool_call;
for bad_json in [
r#"{"id":1,"method":"replace_node","params":{"node_id":"n10","kind":"rect","name":"X","x":"0","y":"0","width":"5","height":"5","drop_children":{}}}"#,
r#"{"id":1,"method":"replace_node","params":{"node_id":"n10","kind":"rect","name":"X","x":"0","y":"0","width":"5","height":"5","drop_children":[true]}}"#,

View file

@ -21,6 +21,10 @@ openpencil-shell-native = { path = "../openpencil-shell-native", version = "0.1.
# `op_editor_core::EditorState` (canonical `.op` document) instead of the
# old shell-core `Document`.
op-editor-core = { path = "../op-editor-core" }
# Phase 7 strangler reorg: the MCP tool registry + JSON-RPC stdio layer
# (`mcp.rs` + `mcp/*`) was extracted out of openpencil-shell-core into the
# op-mcp crate; `mcp_serve.rs` registers tools through `op_mcp::*`.
op-mcp = { path = "../op-mcp" }
# Canonical `.op` (PenDocument) → shell `Document` loader / adapter.
# Extracted out of this binary into a shared library crate so library
# crates (openpencil-shell-native) can reuse the conversion;

View file

@ -29,7 +29,7 @@ use std::io::{BufRead, BufReader, BufWriter, Write};
use std::path::PathBuf;
use op_editor_core::EditorState;
use openpencil_shell_core::mcp::{
use op_mcp::{
batch_design_snapshot, copy_node_snapshot, delete_node_snapshot,
design_content_snapshot, design_refine_snapshot, design_skeleton_snapshot,
add_page_snapshot, clear_selection_snapshot, create_component_snapshot,

View file

@ -30,11 +30,9 @@ pub mod layout_scene;
// Canvas hit-test (input path) over `LayoutScene` — replaces the
// `&Document`-bound `node_at_doc_point` / `nodes_intersecting_doc_rect`.
pub mod layout_scene_hit;
pub mod mcp;
// Paint-time design-variable aggregation consumed by the
// `LayoutScene` scene builder (`$ref` fill / stroke resolution).
pub mod scene_vars;
#[cfg(test)] mod mcp_tests;
// Phase 4 strangler reorg: the wasm-clean RenderBackend trait moved into the
// op-editor-core crate. Re-exported as `render_backend` so `crate::render_backend::*`
// paths still resolve.