lean-ctx-server 3.12.2

The LeanCTX MCP server and its tools on top of LeanCTX | OSS.
Documentation
// SPDX-License-Identifier: Apache-2.0
use rmcp::ErrorData;
use rmcp::model::Tool;
use serde_json::{Map, Value, json};

use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput, get_str};
use lean_ctx::tool_defs::tool_def;

pub struct CtxPackageTool;

impl McpTool for CtxPackageTool {
    fn name(&self) -> &'static str {
        "ctx_package"
    }

    fn tool_def(&self) -> Tool {
        tool_def(
            "ctx_package",
            "WORKFLOW: save -> resume in new session for agent handoff.\n\
            ANTIPATTERN: NOT for internal session persistence (use ctx_session).\n\
            Self-contained JSON bundles: session state, summaries,\n\
            knowledge. Actions: save, resume, list, info.\n\
            Saves tokens: portable across sessions/agents.",
            json!({
                "type": "object",
                "properties": {
                    "action": {
                        "type": "string",
                        "enum": ["save", "resume", "list", "info"],
                        "description": "save|resume|list|info"
                    },
                    "path": {
                        "type": "string",
                        "description": "File path for save/resume JSON bundle"
                    },
                    "description": {
                        "type": "string",
                        "description": "Package description (for save action)"
                    }
                },
                "allOf": [
                    {
                        "if": { "properties": { "action": { "const": "resume" } }, "required": ["action"] },
                        "then": { "required": ["action", "path"] }
                    },
                    {
                        "if": { "properties": { "action": { "const": "info" } }, "required": ["action"] },
                        "then": { "required": ["action", "path"] }
                    }
                ],
                "required": []
            }),
        )
    }

    fn handle(
        &self,
        args: &Map<String, Value>,
        ctx: &ToolContext,
    ) -> Result<ToolOutput, ErrorData> {
        let action = get_str(args, "action").unwrap_or_else(|| "save".to_string());
        let path = get_str(args, "path")
            .map(|raw| {
                lean_ctx::core::workspace_authority::confine(
                    &raw,
                    &ctx.project_root,
                    action == "save",
                )
            })
            .transpose()
            .map_err(|error| ErrorData::invalid_params(error, None))?;
        let description = get_str(args, "description");

        let guard = ctx
            .session
            .as_ref()
            .and_then(|s| crate::server::bounded_lock::read(s, "ctx_package:session"));
        let session_ref = guard.as_deref();
        let root = session_ref
            .and_then(|s| s.project_root.clone())
            .unwrap_or_else(|| ctx.project_root.clone());

        let agent_id_guard = ctx.agent_id.as_ref().map(|a| a.blocking_read());
        let agent_id = agent_id_guard.as_ref().and_then(|g| g.as_deref());
        let result = crate::tools::ctx_package::handle(
            &root,
            session_ref,
            &action,
            path.as_deref(),
            agent_id,
            description.as_deref(),
        );
        Ok(ToolOutput::simple(result))
    }
}