lean_ctx_server/tools/registered/
ctx_doc.rs1use rmcp::ErrorData;
4use rmcp::model::Tool;
5use serde_json::{Map, Value, json};
6
7use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput, get_str, get_str_array};
8use crate::tools::ctx_doc::{Args, handle};
9use lean_ctx::tool_defs::tool_def;
10
11pub struct CtxDocTool;
12
13impl McpTool for CtxDocTool {
14 fn name(&self) -> &'static str {
15 "ctx_doc"
16 }
17
18 fn tool_def(&self) -> Tool {
19 tool_def(
20 "ctx_doc",
21 "Project documentation in the linked Hive vault: pages of sections, bound to code.\n\
22 WORKFLOW: action=list or search first, then show slug (outline=true, section=<heading-slug>).\n\
23 Reads are local (offline). action=write slug content='# Title\\n…' creates or revises a page;\n\
24 action=edit slug find replace changes one unique stretch; action=note adds an addendum.\n\
25 anchors=[file:…] binds a page to code; action=status lists stale pages, action=confirm re-pins after a check.\n\
26 Writes are queued and sent in the background. Link pages as [text](/slug#heading).",
27 json!({
28 "type": "object",
29 "properties": {
30 "action": { "type": "string", "description": "list|show|search|status|write|edit|note|confirm" },
31 "anchors": { "type": "array", "items": { "type": "string" }, "description": "write: code the page derives from (file:src/x.rs, dir:src/core, glob:src/**/*.rs, shape:docs); a change makes the page stale" },
32 "slug": { "type": "string", "description": "Page slug, e.g. guide/retry (list: a prefix)" },
33 "section": { "type": "string", "description": "show: one section by its heading slug" },
34 "outline": { "type": "boolean", "description": "show: headings only" },
35 "query": { "type": "string", "description": "search words" },
36 "content": { "type": "string", "description": "write: the whole page markdown; note: the note" },
37 "find": { "type": "string", "description": "edit: exact text that occurs once" },
38 "replace": { "type": "string", "description": "edit: its replacement" },
39 "type": { "type": "string", "description": "write (new page): reference|guide|design|…" },
40 "status": { "type": "string", "description": "write (new page): draft|canonical" }
41 },
42 "required": ["action"]
43 }),
44 )
45 }
46
47 fn handle(
48 &self,
49 args: &Map<String, Value>,
50 ctx: &ToolContext,
51 ) -> Result<ToolOutput, ErrorData> {
52 let owned = |k: &str| get_str(args, k);
53 let action = owned("action").unwrap_or_else(|| "list".to_owned());
54 let (slug, section, query, content, find, replace, kind, status) = (
55 owned("slug"),
56 owned("section"),
57 owned("query"),
58 owned("content"),
59 owned("find"),
60 owned("replace"),
61 owned("type"),
62 owned("status"),
63 );
64 let anchors = get_str_array(args, "anchors").unwrap_or_default();
65 let call = Args {
66 anchors: &anchors,
67 action: &action,
68 slug: slug.as_deref(),
69 section: section.as_deref(),
70 query: query.as_deref(),
71 content: content.as_deref(),
72 find: find.as_deref(),
73 replace: replace.as_deref(),
74 kind: kind.as_deref(),
75 status: status.as_deref(),
76 outline: args
77 .get("outline")
78 .and_then(Value::as_bool)
79 .unwrap_or(false),
80 };
81 Ok(ToolOutput::simple(handle(&ctx.project_root, &call)))
82 }
83}