lean_ctx/tools/registered/
ctx_artifacts.rs1use std::path::Path;
2
3use rmcp::ErrorData;
4use rmcp::model::Tool;
5use serde_json::{Map, Value, json};
6
7use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput, get_str, get_usize};
8use crate::tool_defs::tool_def;
9
10pub struct CtxArtifactsTool;
11
12impl McpTool for CtxArtifactsTool {
13 fn name(&self) -> &'static str {
14 "ctx_artifacts"
15 }
16
17 fn tool_def(&self) -> Tool {
18 tool_def(
19 "ctx_artifacts",
20 "Context artifact registry with BM25 search — manage and query indexed code artifacts.\n\
21 WORKFLOW: index artifacts first (index/reindex), then search with query for semantic retrieval.\n\
22 Actions: list|status|index|reindex|search|remove.\n\
23 ANTIPATTERN: NOT for general code search — use ctx_semantic_search for codebase queries.",
24 json!({
25 "type": "object",
26 "properties": {
27 "action": {
28 "type": "string",
29 "enum": ["list", "status", "index", "reindex", "search", "remove"],
30 "description": "list|status|index|reindex|search|remove"
31 },
32 "project_root": {
33 "type": "string",
34 "description": "Project root"
35 },
36 "query": {
37 "type": "string",
38 "description": "Search query"
39 },
40 "name": {
41 "type": "string",
42 "description": "Artifact name"
43 },
44 "top_k": {
45 "type": "integer",
46 "description": "Max results (default depends on action, capped at 1000)"
47 },
48 "format": {
49 "type": "string",
50 "enum": ["json", "markdown"],
51 "description": "json|markdown"
52 }
53 },
54 "required": ["action"]
55 }),
56 )
57 }
58
59 fn handle(
60 &self,
61 args: &Map<String, Value>,
62 ctx: &ToolContext,
63 ) -> Result<ToolOutput, ErrorData> {
64 let action = get_str(args, "action")
65 .ok_or_else(|| ErrorData::invalid_params("action is required", None))?;
66 let format = get_str(args, "format");
67 let query = get_str(args, "query");
68 let name = get_str(args, "name");
69 let top_k = get_usize(args, "top_k").map(|d| d.min(1000));
70 let root = if let Some(p) = ctx
71 .resolved_path("project_root")
72 .or(ctx.resolved_path("root"))
73 {
74 p
75 } else if let Some(err) = ctx.path_error("project_root").or(ctx.path_error("root")) {
76 return Err(ErrorData::invalid_params(format!("root: {err}"), None));
77 } else {
78 &ctx.project_root
79 };
80
81 let result = crate::tools::ctx_artifacts::handle(
82 &action,
83 Path::new(root),
84 name.as_deref().or(query.as_deref()),
85 top_k,
86 format.as_deref(),
87 );
88
89 Ok(ToolOutput::simple(result))
90 }
91}