//! MCP tool definitions (names, descriptions, JSON Schemas) and their implementations on top of a //! [`Backend`]. use serde_json::{Map, Value, json}; use vectorcraft_engine::cmd::fileio::{ARTBOARD_PARAMS, FORMATS, OPEN_EXTS, SAVE_FORMATS, format, writable_format}; use crate::backend::Backend; mod definitions; mod dispatch; mod handlers; pub use definitions::tool_definitions; #[cfg(test)] pub(crate) use dispatch::MAX_COMMAND_BATCH_STEPS; pub use dispatch::call_tool; pub(crate) use dispatch::check_tool_arguments; /// The result of `tools/call`: MCP content blocks plus the `isError` flag. #[derive(Clone, Debug, PartialEq)] pub struct ToolResult { pub content: Vec, pub is_error: bool, } impl ToolResult { pub fn text(t: impl Into) -> Self { Self { content: vec![json!({"type": "text", "text": t.into()})], is_error: false } } pub fn json(v: &Value) -> Self { Self::text(serde_json::to_string_pretty(v).unwrap_or_default()) } pub fn error(t: impl Into) -> Self { Self { content: vec![json!({"type": "text", "text": t.into()})], is_error: true } } pub fn to_value(&self) -> Value { json!({"content": self.content, "isError": self.is_error}) } /// The error's text, when the engine refused the call because a script is running. pub(crate) fn script_busy(&self) -> Option { let text = self.content.first()?.get("text")?.as_str()?; (self.is_error && text.ends_with(vectorcraft_engine::SCRIPT_BUSY)).then(|| text.to_string()) } }