feat(mcp): list_components read tool — surface registered components
Surfaces the existing Document.components ComponentLibrary via
MCP so LLM clients can discover what reusable Frame / Group
subtrees the user has saved. 22nd tool in the catalog (was 21).
Wire shape:
count — total component count
components — `;`-records of `name|id`, with the standard
2-level escape on each side (matches list_variables /
list_pages wire convention).
Closes the read-only MCP surface for components. Instance
insertion via MCP (the matching write tool) requires a JSON
Node descriptor and is the natural follow-up; the data model +
`Document::instantiate_component` already exist.
Desktop --mcp registry + tools/list schema updated; test
renamed to all_twenty_two_tools with exact-count guard intact.
This commit is contained in:
parent
cf5b300714
commit
83fa496124
|
|
@ -26,7 +26,7 @@ use openpencil_shell_core::document::Document;
|
|||
use openpencil_shell_core::mcp::{
|
||||
batch_design_snapshot, copy_node_snapshot, delete_node_snapshot,
|
||||
design_content_snapshot, design_refine_snapshot, design_skeleton_snapshot,
|
||||
document_info_snapshot,
|
||||
document_info_snapshot, list_components_snapshot,
|
||||
get_active_theme_snapshot, get_node_snapshot, insert_node_snapshot,
|
||||
list_pages_snapshot, list_variables_snapshot, move_node_snapshot,
|
||||
replace_node_snapshot, run_stdio_with_applier, selection_snapshot,
|
||||
|
|
@ -139,6 +139,7 @@ fn rebuild_registry(doc: &Document) -> ToolRegistry {
|
|||
r.register(Box::new(list_pages_snapshot(doc)));
|
||||
r.register(Box::new(list_variables_snapshot(doc)));
|
||||
r.register(Box::new(get_active_theme_snapshot(doc)));
|
||||
r.register(Box::new(list_components_snapshot(doc)));
|
||||
r.register(Box::new(set_variable_color_snapshot(doc)));
|
||||
r.register(Box::new(set_active_axis_value_snapshot(doc)));
|
||||
r.register(Box::new(insert_node_snapshot()));
|
||||
|
|
@ -366,6 +367,7 @@ const TOOL_SCHEMAS: &[&str] = &[
|
|||
r#"{"name":"list_pages","description":"List page ids + names.","inputSchema":{"type":"object","properties":{},"additionalProperties":false}}"#,
|
||||
r#"{"name":"list_variables","description":"List design variables with kinds.","inputSchema":{"type":"object","properties":{},"additionalProperties":false}}"#,
|
||||
r#"{"name":"get_active_theme","description":"Return the active theme axis pinning per axis.","inputSchema":{"type":"object","properties":{},"additionalProperties":false}}"#,
|
||||
r#"{"name":"list_components","description":"List registered components (saved Frames / Groups promoted via Save as Component). Returns count + a `;`-separated record of `name|id` pairs.","inputSchema":{"type":"object","properties":{},"additionalProperties":false}}"#,
|
||||
// --- write tools ---
|
||||
r##"{"name":"set_variable_color","description":"Set a Color-kind variable's value.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"hex":{"type":"string","description":"#rgb / #rrggbb / #rrggbbaa"}},"required":["name","hex"]}}"##,
|
||||
r#"{"name":"batch_design","description":"Insert N leaf nodes on the active page in one atomic shot. nodes_json must be a JSON array string of {kind,name,x,y,width,height,fill_hex?} descriptors.","inputSchema":{"type":"object","properties":{"nodes_json":{"type":"string","description":"JSON array of node descriptors"}},"required":["nodes_json"]}}"#,
|
||||
|
|
@ -428,7 +430,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn tools_list_response_includes_all_twenty_one_tools() {
|
||||
fn tools_list_response_includes_all_twenty_two_tools() {
|
||||
let r = tools_list_response("3");
|
||||
// Exact-count assertion: any tool added without
|
||||
// updating this test will trip the count first. Codex
|
||||
|
|
@ -437,7 +439,7 @@ mod tests {
|
|||
// without being added to the list below.
|
||||
assert_eq!(
|
||||
TOOL_SCHEMAS.len(),
|
||||
21,
|
||||
22,
|
||||
"tools/list catalog count must match the registered tools — add the new tool to this test"
|
||||
);
|
||||
for name in [
|
||||
|
|
@ -447,6 +449,7 @@ mod tests {
|
|||
"list_pages",
|
||||
"list_variables",
|
||||
"get_active_theme",
|
||||
"list_components",
|
||||
"set_variable_color",
|
||||
"set_active_axis_value",
|
||||
"insert_node",
|
||||
|
|
|
|||
|
|
@ -25,9 +25,10 @@ pub mod scalar_vars;
|
|||
// split. Mirrors the `widgets::*` re-export pattern.
|
||||
pub use parser::parse_tool_call;
|
||||
pub use tools::{
|
||||
document_info_snapshot, get_active_theme_snapshot, get_node_snapshot, list_pages_snapshot,
|
||||
list_variables_snapshot, selection_snapshot, GetActiveTheme, GetDocumentInfo, GetNode,
|
||||
GetSelection, ListPages, ListVariables, NodeRecord, VariableRecord,
|
||||
document_info_snapshot, get_active_theme_snapshot, get_node_snapshot,
|
||||
list_components_snapshot, list_pages_snapshot, list_variables_snapshot, selection_snapshot,
|
||||
GetActiveTheme, GetDocumentInfo, GetNode, GetSelection, ListComponents, ListPages,
|
||||
ListVariables, NodeRecord, VariableRecord,
|
||||
};
|
||||
pub use write_tools::{
|
||||
copy_node_snapshot, delete_node_snapshot, insert_node_snapshot, move_node_snapshot,
|
||||
|
|
|
|||
|
|
@ -523,3 +523,47 @@ pub fn get_active_theme_snapshot(doc: &crate::document::Document) -> GetActiveTh
|
|||
.collect();
|
||||
GetActiveTheme { active, options }
|
||||
}
|
||||
|
||||
/// First-party `list_components` tool — reports the document's
|
||||
/// registered components (saved Frames / Groups promoted via
|
||||
/// "Save as Component"). LLM clients use this to discover what
|
||||
/// reusable subtrees they can instantiate (instance insertion is
|
||||
/// still UI-only in the Rust shell; an MCP write tool to spawn
|
||||
/// instances is a future patch).
|
||||
///
|
||||
/// Wire shape:
|
||||
/// count — total number of components in the library.
|
||||
/// components — `;`-records of `name|id` pairs, with the legacy
|
||||
/// 2-level escape (\;|) on each side. Matches the
|
||||
/// list_variables / list_pages wire convention so clients
|
||||
/// can reuse their existing decoder.
|
||||
pub struct ListComponents {
|
||||
pub items: Vec<(String, u64)>,
|
||||
}
|
||||
|
||||
impl McpTool for ListComponents {
|
||||
fn name(&self) -> &str {
|
||||
"list_components"
|
||||
}
|
||||
fn call(&self, _args: &BTreeMap<String, String>) -> ToolOutcome {
|
||||
let encoded: Vec<String> = self
|
||||
.items
|
||||
.iter()
|
||||
.map(|(name, id)| format!("{}|{}", escape_record_field(name), id))
|
||||
.collect();
|
||||
let mut out = BTreeMap::new();
|
||||
out.insert("count".into(), self.items.len().to_string());
|
||||
out.insert("components".into(), encoded.join(";"));
|
||||
ToolOutcome::Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_components_snapshot(doc: &crate::document::Document) -> ListComponents {
|
||||
let items = doc
|
||||
.components
|
||||
.components
|
||||
.iter()
|
||||
.map(|c| (c.name.clone(), c.id.raw()))
|
||||
.collect();
|
||||
ListComponents { items }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue