fix(mcp): exclude debug tools from release builds

This commit is contained in:
Kayshen-X 2026-06-06 23:16:26 +08:00
parent c66e4d9a1e
commit 93ac65ab43
6 changed files with 64 additions and 18 deletions

View file

@ -13,6 +13,10 @@ description = "OpenPencil desktop host — winit + skia-safe binary that drives
name = "openpencil-desktop"
path = "src/main.rs"
[features]
default = []
mcp-debug-tools = ["op-mcp/debug-tools"]
# File-association metadata for `cargo-bundle` (`cargo bundle`). This
# declares OpenPencil as the OS-level handler for `.op` / `.pen`
# documents: macOS writes `CFBundleDocumentTypes` into the `.app`

View file

@ -21,22 +21,21 @@ use op_mcp::{
codegen_clean_snapshot, codegen_plan_snapshot, codegen_submit_chunk_snapshot,
copy_node_snapshot, copy_selected_snapshot, count_nodes_snapshot, create_component_snapshot,
create_variable_snapshot, cut_selected_snapshot, cycle_active_axis_value_snapshot,
debug_logs_tail_snapshot, debug_screenshot_snapshot, debug_tools_enabled,
debug_validation_report_snapshot, delete_component_snapshot, delete_node_snapshot,
delete_page_snapshot, delete_selected_snapshot, delete_variable_snapshot,
design_content_snapshot, design_refine_snapshot, design_skeleton_snapshot,
document_info_snapshot, duplicate_page_snapshot, duplicate_selected_snapshot,
export_design_md_snapshot, find_empty_space_snapshot, find_node_by_name_snapshot,
get_active_theme_snapshot, get_canvas_bounds_snapshot, get_component_snapshot,
get_design_md_snapshot, get_design_prompt_snapshot, get_history_depth_snapshot,
get_node_children_snapshot, get_node_parent_snapshot, get_node_snapshot,
get_selection_set_snapshot, get_style_guide_snapshot, get_style_guide_tags_snapshot,
get_variables_snapshot, get_viewport_snapshot, group_selected_snapshot, import_svg_snapshot,
insert_node_snapshot, instantiate_component_snapshot, list_components_snapshot,
list_node_kinds_snapshot, list_pages_snapshot, list_theme_presets_snapshot,
list_variables_snapshot, load_theme_preset_snapshot, move_node_snapshot,
nudge_selected_snapshot, open_document_snapshot, paste_clipboard_snapshot, read_nodes_snapshot,
redo_snapshot, remove_node_effect_snapshot, remove_page_snapshot, rename_component_snapshot,
debug_tools_enabled, delete_component_snapshot, delete_node_snapshot, delete_page_snapshot,
delete_selected_snapshot, delete_variable_snapshot, design_content_snapshot,
design_refine_snapshot, design_skeleton_snapshot, document_info_snapshot,
duplicate_page_snapshot, duplicate_selected_snapshot, export_design_md_snapshot,
find_empty_space_snapshot, find_node_by_name_snapshot, get_active_theme_snapshot,
get_canvas_bounds_snapshot, get_component_snapshot, get_design_md_snapshot,
get_design_prompt_snapshot, get_history_depth_snapshot, get_node_children_snapshot,
get_node_parent_snapshot, get_node_snapshot, get_selection_set_snapshot,
get_style_guide_snapshot, get_style_guide_tags_snapshot, get_variables_snapshot,
get_viewport_snapshot, group_selected_snapshot, import_svg_snapshot, insert_node_snapshot,
instantiate_component_snapshot, list_components_snapshot, list_node_kinds_snapshot,
list_pages_snapshot, list_theme_presets_snapshot, list_variables_snapshot,
load_theme_preset_snapshot, move_node_snapshot, nudge_selected_snapshot,
open_document_snapshot, paste_clipboard_snapshot, read_nodes_snapshot, redo_snapshot,
remove_node_effect_snapshot, remove_page_snapshot, rename_component_snapshot,
rename_page_snapshot, rename_variable_snapshot, reorder_page_snapshot,
reorder_selected_snapshot, replace_all_matching_properties_snapshot, replace_node_snapshot,
run_stdio_with_applier, save_document_snapshot, save_theme_preset_snapshot,
@ -53,6 +52,10 @@ use op_mcp::{
toggle_node_selection_snapshot, undo_snapshot, ungroup_selected_snapshot, update_node_snapshot,
McpTool, ToolRegistry,
};
#[cfg(feature = "mcp-debug-tools")]
use op_mcp::{
debug_logs_tail_snapshot, debug_screenshot_snapshot, debug_validation_report_snapshot,
};
pub(crate) mod file_path;
@ -512,6 +515,7 @@ fn rebuild_registry(doc: &EditorState, requested_tool: Option<&str>) -> ToolRegi
register_tool!("get_history_depth", get_history_depth_snapshot(doc));
register_tool!("get_viewport", get_viewport_snapshot(doc));
register_tool!("get_selection_set", get_selection_set_snapshot(doc));
#[cfg(feature = "mcp-debug-tools")]
if debug_tools_enabled() {
register_tool!(
"debug_validation_report",
@ -775,6 +779,9 @@ pub(crate) use doc_sync::*;
fn tools_list_response(id_raw: &str, state: &EditorState, debug_enabled: bool) -> String {
let mut entries: Vec<String> = TOOL_SCHEMAS.iter().map(|s| (*s).to_string()).collect();
entries.extend(op_mcp::element_tools::element_tool_schemas(state));
#[cfg(not(feature = "mcp-debug-tools"))]
let _ = debug_enabled;
#[cfg(feature = "mcp-debug-tools")]
if debug_enabled {
entries.extend(DEBUG_TOOL_SCHEMAS.iter().map(|s| (*s).to_string()));
}
@ -785,6 +792,9 @@ fn tools_list_response(id_raw: &str, state: &EditorState, debug_enabled: bool) -
}
mod schemas;
#[cfg(not(feature = "mcp-debug-tools"))]
pub(crate) use schemas::TOOL_SCHEMAS;
#[cfg(feature = "mcp-debug-tools")]
pub(crate) use schemas::{DEBUG_TOOL_SCHEMAS, TOOL_SCHEMAS};
#[cfg(test)]

View file

@ -112,6 +112,7 @@ pub(crate) const TOOL_SCHEMAS: &[&str] = &[
r#"{"name":"replace_node","description":"Swap an existing node at the same parent slot with a freshly-built leaf. Accepts Rust flat fields or TS-style nodeId + data object. Set drop_children=true to discard a container's subtree.","inputSchema":{"type":"object","properties":{"filePath":{"type":"string","description":"Optional target .op file path; omit to use the server document"},"node_id":{"type":"string"},"nodeId":{"type":"string"},"kind":{"type":"string","enum":["frame","group","rect","ellipse","polygon","line","text","path"]},"name":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"width":{"type":"string"},"height":{"type":"string"},"fill_hex":{"type":"string"},"data":{"type":"object","description":"TS-style PenNode data; rich fields are preserved as a subtree"},"postProcess":{"type":"boolean","description":"Accepted for TS compatibility; Rust replace path ignores post-processing"},"canvasWidth":{"type":"number","description":"Accepted for TS compatibility; Rust replace path ignores post-processing width"},"drop_children":{"type":"string","enum":["true","false"]},"dropChildren":{"type":"string","enum":["true","false"]},"pageId":{"type":"string","description":"optional target page id or legacy page index; omitted = active page"}}}}"#,
];
#[cfg(feature = "mcp-debug-tools")]
pub(crate) const DEBUG_TOOL_SCHEMAS: &[&str] = &[
r#"{"name":"debug_validation_report","description":"Run the op-design-lint detectors over the active page and return the design-issue list. Read-only, no parameters. Result: count + categories (`;`-separated `category|count`) + issues (JSON-serialized Issue array). Gated behind the OPENPENCIL_DEBUG_TOOLS=1 env flag.","inputSchema":{"type":"object","properties":{},"additionalProperties":false}}"#,
r#"{"name":"debug_logs_tail","description":"Read the tail of ~/.openpencil/logs/server-YYYY-MM-DD.log with API keys and Authorization headers redacted. Gated behind OPENPENCIL_DEBUG_TOOLS=1.","inputSchema":{"type":"object","properties":{"tailLines":{"type":"number","description":"Maximum lines to return (default 100, max 500)."},"sinceMs":{"type":"number","description":"Unix ms timestamp; only return lines newer than this."},"grep":{"type":"string","description":"Regex to filter lines by content after redaction."}}}}"#,

View file

@ -28,6 +28,20 @@ fn tools_list_response_includes_all_registered_tools() {
!r.contains("debug_validation_report"),
"production tools/list must not advertise the debug tool: {r}"
);
#[cfg(not(feature = "mcp-debug-tools"))]
{
let r_forced_debug = tools_list_response("3", &state, true);
for name in [
"debug_validation_report",
"debug_logs_tail",
"debug_screenshot",
] {
assert!(
!r_forced_debug.contains(name),
"formal release catalog must exclude {name} even if debug listing is requested: {r_forced_debug}"
);
}
}
// UIKit element tools are appended dynamically — one per
// built-in starter-kit component (6) — and ride alongside
// the static schemas in the tools/list response.
@ -172,8 +186,10 @@ fn tools_list_response_includes_all_registered_tools() {
assert!(r.contains(name), "tools/list must include {name}: {r}");
}
// Gate open (debug_enabled = true) — the debug tools join the catalog.
// Gate open (debug_enabled = true) — internal debug builds can opt in
// to the debug tools catalog.
let r_debug = tools_list_response("3", &state, true);
#[cfg(feature = "mcp-debug-tools")]
for name in [
"debug_validation_report",
"debug_logs_tail",
@ -184,6 +200,11 @@ fn tools_list_response_includes_all_registered_tools() {
"debug tools/list must advertise {name}: {r_debug}"
);
}
#[cfg(not(feature = "mcp-debug-tools"))]
assert!(
!r_debug.contains("debug_validation_report"),
"default release feature set must not include debug tools: {r_debug}"
);
}
#[test]

View file

@ -10,10 +10,14 @@ description = "OpenPencil MCP server — JSON-RPC tool registry over op-editor-c
name = "op_mcp"
path = "src/lib.rs"
[features]
default = []
debug-tools = ["dep:op-design-lint"]
[dependencies]
jian-ops-schema = { path = "../../vendor/jian/crates/jian-ops-schema" }
op-ai-skills = { path = "../op-ai-skills" }
op-design-lint = { path = "../op-design-lint" }
op-design-lint = { path = "../op-design-lint", optional = true }
op-editor-core = { path = "../op-editor-core" }
op-editor-ui = { path = "../op-editor-ui" }
op-pen-loader = { path = "../op-pen-loader" }

View file

@ -43,6 +43,7 @@ pub mod component_tools;
mod component_tools_tests;
#[cfg(test)]
mod copy_node_tests;
#[cfg(feature = "debug-tools")]
pub mod debug_tools;
pub mod design_md_tools;
#[cfg(test)]
@ -180,10 +181,15 @@ pub use component_tools::{
rename_component_snapshot, set_node_collapsed_snapshot, CreateComponent, DeleteComponent,
InstantiateComponent, RenameComponent, SetNodeCollapsed,
};
#[cfg(feature = "debug-tools")]
pub use debug_tools::{
debug_logs_tail_snapshot, debug_screenshot_snapshot, debug_tools_enabled,
debug_validation_report_snapshot, DebugLogsTail, DebugScreenshot, DebugValidationReport,
};
#[cfg(not(feature = "debug-tools"))]
pub fn debug_tools_enabled() -> bool {
false
}
pub use design_md_tools::{
export_design_md_snapshot, get_design_md_snapshot, set_design_md_snapshot, ExportDesignMd,
GetDesignMd, SetDesignMd,