Skip to main content

lean_ctx/tools/registered/
ctx_package.rs

1use 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}