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