lean_ctx/tools/registered/
ctx_package.rs1use rmcp::ErrorData;
2use rmcp::model::Tool;
3use serde_json::{Map, Value, json};
4
5use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput, get_str};
6use crate::tool_defs::tool_def;
7
8pub struct CtxPackageTool;
9
10impl McpTool for CtxPackageTool {
11 fn name(&self) -> &'static str {
12 "ctx_package"
13 }
14
15 fn tool_def(&self) -> Tool {
16 tool_def(
17 "ctx_package",
18 "WORKFLOW: save -> resume in new session for agent handoff.\n\
19 ANTIPATTERN: NOT for internal session persistence (use ctx_session).\n\
20 Self-contained JSON bundles: session state, summaries,\n\
21 knowledge. Actions: save, resume, list, info.\n\
22 Saves tokens: portable across sessions/agents.",
23 json!({
24 "type": "object",
25 "properties": {
26 "action": {
27 "type": "string",
28 "enum": ["save", "resume", "list", "info"],
29 "description": "save|resume|list|info"
30 },
31 "path": {
32 "type": "string",
33 "description": "File path for save/resume JSON bundle"
34 },
35 "description": {
36 "type": "string",
37 "description": "Package description (for save action)"
38 }
39 },
40 "allOf": [
41 {
42 "if": { "properties": { "action": { "const": "resume" } }, "required": ["action"] },
43 "then": { "required": ["action", "path"] }
44 },
45 {
46 "if": { "properties": { "action": { "const": "info" } }, "required": ["action"] },
47 "then": { "required": ["action", "path"] }
48 }
49 ],
50 "required": []
51 }),
52 )
53 }
54
55 fn handle(
56 &self,
57 args: &Map<String, Value>,
58 ctx: &ToolContext,
59 ) -> Result<ToolOutput, ErrorData> {
60 let action = get_str(args, "action").unwrap_or_else(|| "save".to_string());
61 let path = get_str(args, "path");
62 let description = get_str(args, "description");
63
64 let guard = ctx
65 .session
66 .as_ref()
67 .and_then(|s| crate::server::bounded_lock::read(s, "ctx_package:session"));
68 let session_ref = guard.as_deref();
69 let root = session_ref
70 .and_then(|s| s.project_root.clone())
71 .unwrap_or_else(|| ctx.project_root.clone());
72
73 let agent_id_guard = ctx.agent_id.as_ref().map(|a| a.blocking_read());
74 let agent_id = agent_id_guard.as_ref().and_then(|g| g.as_deref());
75 let result = crate::tools::ctx_package::handle(
76 &root,
77 session_ref,
78 &action,
79 path.as_deref(),
80 agent_id,
81 description.as_deref(),
82 );
83 Ok(ToolOutput::simple(result))
84 }
85}