Skip to main content

lean_ctx_server/tools/registered/
ctx_package.rs

1// SPDX-License-Identifier: Apache-2.0
2use 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}