lean_ctx_server/tools/registered/
ctx_expand.rs1use rmcp::ErrorData;
3use rmcp::model::Tool;
4use serde_json::{Map, Value, json};
5
6use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput};
7use lean_ctx::tool_defs::tool_def;
8
9pub struct CtxExpandTool;
10
11impl McpTool for CtxExpandTool {
12 fn name(&self) -> &'static str {
13 "ctx_expand"
14 }
15
16 fn tool_def(&self) -> Tool {
17 tool_def(
18 "ctx_expand",
19 "Retrieve archived tool output: see [Archived:ID] → ctx_expand id=ID \
20 (zero-loss, original preserved). head/tail/search filter lines; \
21 action=list|search_all browses/queries archives.\n\
22 ANTIPATTERN: not for project files — use ctx_read or ctx_compose.",
23 json!({
24 "type": "object",
25 "properties": {
26 "id": { "type": "string", "description": "Archive ID, shell_* job ID, or @F1 ref" },
27 "action": { "type": "string", "description": "retrieve|list|search_all" },
28 "start_line": { "type": "integer" },
29 "end_line": { "type": "integer" },
30 "head": { "type": "integer" },
31 "tail": { "type": "integer" },
32 "search": { "type": "string" },
33 "json_keys": { "type": "boolean" },
34 "json_path": { "type": "string", "description": "e.g. data.items.0" },
35 "query": { "type": "string" },
36 "session_id": { "type": "string" }
37 },
38 "allOf": [
39 { "if": { "properties": { "action": { "const": "search_all" } }, "required": ["action"] }, "then": { "required": ["query"] } },
40 { "if": { "not": { "properties": { "action": { "enum": ["list", "search_all"] } }, "required": ["action"] } }, "then": { "required": ["id"] } }
41 ]
42 }),
43 )
44 }
45
46 fn handle(
47 &self,
48 args: &Map<String, Value>,
49 _ctx: &ToolContext,
50 ) -> Result<ToolOutput, ErrorData> {
51 let args_val = Value::Object(args.clone());
52 let result = crate::tools::ctx_expand::handle_outcome(&args_val)
55 .map_err(crate::server::tool_trait::tool_execution_error)?;
56 Ok(ToolOutput::simple(result))
57 }
58
59 fn publishes_every_source(&self, args: Option<&Map<String, Value>>) -> bool {
67 !matches!(
68 args.and_then(|args| args.get("action"))
69 .and_then(Value::as_str),
70 Some("list" | "search_all")
71 )
72 }
73}