feat(shell-core/mcp): GetDocumentInfo — first first-party tool
`get_document_info` reports page count + active page index + total
node count over the registry. Smallest of the ~20 tools TS pen-mcp
exposes; serves as the wire-format smoke test for real LLM clients
and demonstrates the registration shape future tools follow.
- `GetDocumentInfo { page_count, active_page_index, total_nodes }`
is a snapshot struct — pre-computed at registration so each
`dispatch` is O(1). Mutates as documents change requires
re-registering (the server binary will do this on every doc
edit; v1 ships a frozen snapshot).
- `document_info_snapshot(&Document)` walks every page +
recursively counts subtree nodes. Container nodes count as
themselves + their descendants (matches TS's
`flattenNodes(...).length`).
Tests (1):
- Build a 3-node doc (Frame + 2 children), snapshot, register,
dispatch, verify each field round-trips through the JSON-RPC
response.
#7 MCP now ~60% — types + registry + wire format + stdio listener +
first registered tool. Remaining: ~19 more first-party tools
(insert_node, batch_design, design_skeleton, ...) — each a focused
follow-up using the same `McpTool` impl shape.
Tests total: 246 shell-core (+1). Wasm32 build clean.
This commit is contained in:
parent
cf48a006de
commit
afadde7231
|
|
@ -268,6 +268,53 @@ fn extract_field<'a>(line: &'a str, key: &str) -> Option<&'a str> {
|
|||
Some(line[val_start..val_start + end_rel].trim())
|
||||
}
|
||||
|
||||
/// First-party `get_document_info` tool — reports the active
|
||||
/// page index, page count, and total node count. The simplest of
|
||||
/// the MCP tools TS `pen-mcp` exposes; serves as a wire-format
|
||||
/// smoke test for real LLM clients.
|
||||
pub struct GetDocumentInfo {
|
||||
pub page_count: usize,
|
||||
pub active_page_index: usize,
|
||||
pub total_nodes: usize,
|
||||
}
|
||||
|
||||
impl McpTool for GetDocumentInfo {
|
||||
fn name(&self) -> &str {
|
||||
"get_document_info"
|
||||
}
|
||||
fn call(&self, _args: &BTreeMap<String, String>) -> ToolOutcome {
|
||||
let mut out = BTreeMap::new();
|
||||
out.insert("page_count".into(), self.page_count.to_string());
|
||||
out.insert(
|
||||
"active_page_index".into(),
|
||||
self.active_page_index.to_string(),
|
||||
);
|
||||
out.insert("total_nodes".into(), self.total_nodes.to_string());
|
||||
ToolOutcome::Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
/// Snapshot the document into a `GetDocumentInfo` tool. Counts all
|
||||
/// nodes recursively across every page. The MCP server registers
|
||||
/// one of these per document; replays on every `get_document_info`
|
||||
/// call without re-walking the tree.
|
||||
pub fn document_info_snapshot(doc: &crate::document::Document) -> GetDocumentInfo {
|
||||
let total_nodes: usize = doc
|
||||
.pages
|
||||
.iter()
|
||||
.map(|p| p.children.iter().map(count_subtree).sum::<usize>())
|
||||
.sum();
|
||||
GetDocumentInfo {
|
||||
page_count: doc.pages.len(),
|
||||
active_page_index: doc.active_page_index,
|
||||
total_nodes,
|
||||
}
|
||||
}
|
||||
|
||||
fn count_subtree(n: &crate::document::Node) -> usize {
|
||||
1 + n.children.iter().map(count_subtree).sum::<usize>()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -439,6 +486,41 @@ mod tests {
|
|||
assert!(j.contains(r#""line1\nline2""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_document_info_reports_snapshot_via_registry() {
|
||||
use crate::document::{Document, Node, NodeKind};
|
||||
let mut doc = Document::empty();
|
||||
let page = doc.pages.get_mut(0).unwrap();
|
||||
page.children.clear();
|
||||
page.children.push(Node::with_children(
|
||||
10,
|
||||
NodeKind::Frame,
|
||||
"F",
|
||||
vec![
|
||||
Node::leaf(11, NodeKind::Rect, "a"),
|
||||
Node::leaf(12, NodeKind::Rect, "b"),
|
||||
],
|
||||
));
|
||||
let info = document_info_snapshot(&doc);
|
||||
// Frame + 2 children = 3 nodes total.
|
||||
assert_eq!(info.total_nodes, 3);
|
||||
let mut r = ToolRegistry::default();
|
||||
r.register(Box::new(info));
|
||||
let call = ToolCall {
|
||||
id: RequestId::Num(1),
|
||||
tool: "get_document_info".into(),
|
||||
arguments: BTreeMap::new(),
|
||||
};
|
||||
match r.dispatch(call) {
|
||||
ToolResponse::Ok { result, .. } => {
|
||||
assert_eq!(result.get("total_nodes"), Some(&"3".to_string()));
|
||||
assert_eq!(result.get("page_count"), Some(&"1".to_string()));
|
||||
assert_eq!(result.get("active_page_index"), Some(&"0".to_string()));
|
||||
}
|
||||
_ => panic!("expected Ok"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registry_errors_on_unknown_tool() {
|
||||
let r = ToolRegistry::default();
|
||||
|
|
|
|||
Loading…
Reference in a new issue