1use rmcp::model::Tool;
2use rmcp::ErrorData;
3use serde_json::{json, Map, Value};
4
5use crate::server::tool_trait::{
6 get_bool, get_int, get_str, get_str_array, McpTool, ToolContext, ToolOutput,
7};
8use crate::tool_defs::tool_def;
9
10pub struct CtxPackTool;
11
12impl McpTool for CtxPackTool {
13 fn name(&self) -> &'static str {
14 "ctx_pack"
15 }
16
17 fn tool_def(&self) -> Tool {
18 tool_def(
19 "ctx_pack",
20 "Context Package Manager. Actions: pr (PR context), create (build package from project), list, info, remove, install, export, import, auto_load, summary.",
21 json!({
22 "type": "object",
23 "properties": {
24 "action": {
25 "type": "string",
26 "enum": ["pr", "create", "list", "info", "remove", "install", "export", "import", "auto_load", "summary"],
27 "description": "Pack action to perform"
28 },
29 "project_root": {
30 "type": "string",
31 "description": "Project root (default: session project root)"
32 },
33 "name": {
34 "type": "string",
35 "description": "Package name (required for create, info, remove, install, export, auto_load)"
36 },
37 "version": {
38 "type": "string",
39 "description": "Package version (default: latest or '1.0.0' for create)"
40 },
41 "description": {
42 "type": "string",
43 "description": "Package description (for create)"
44 },
45 "author": {
46 "type": "string",
47 "description": "Package author (for create)"
48 },
49 "tags": {
50 "type": "array",
51 "items": { "type": "string" },
52 "description": "Tags (for create)"
53 },
54 "layers": {
55 "type": "array",
56 "items": { "type": "string" },
57 "description": "Layers to include: knowledge, graph, session, patterns, gotchas (for create)"
58 },
59 "level": {
60 "type": "integer",
61 "description": "Conformance level 1-3 (for create, default: 1)"
62 },
63 "scope": {
64 "type": "string",
65 "description": "Package scope like @org (for create)"
66 },
67 "base": {
68 "type": "string",
69 "description": "Git base ref (for pr action, default: auto-detect)"
70 },
71 "format": {
72 "type": "string",
73 "enum": ["markdown", "json"],
74 "description": "Output format (for pr action, default: markdown)"
75 },
76 "depth": {
77 "type": "integer",
78 "description": "Impact depth (for pr action, default: 3)"
79 },
80 "diff": {
81 "type": "string",
82 "description": "Git diff --name-status text (for pr action; if omitted, computed via git)"
83 },
84 "file": {
85 "type": "string",
86 "description": "File path (for import/export)"
87 },
88 "apply": {
89 "type": "boolean",
90 "description": "Apply package after import (for import action, default: false)"
91 },
92 "enable": {
93 "type": "boolean",
94 "description": "Enable or disable auto-load (for auto_load action, default: true)"
95 }
96 },
97 "required": ["action"]
98 }),
99 )
100 }
101
102 fn handle(
103 &self,
104 args: &Map<String, Value>,
105 ctx: &ToolContext,
106 ) -> Result<ToolOutput, ErrorData> {
107 let action = get_str(args, "action")
108 .ok_or_else(|| ErrorData::invalid_params("action is required", None))?;
109
110 let project_root = if let Some(p) = ctx
111 .resolved_path("project_root")
112 .or(ctx.resolved_path("root"))
113 {
114 p.to_string()
115 } else if let Some(err) = ctx.path_error("project_root").or(ctx.path_error("root")) {
116 return Err(ErrorData::invalid_params(
117 format!("project_root: {err}"),
118 None,
119 ));
120 } else {
121 ctx.project_root.clone()
122 };
123
124 let result = match action.as_str() {
125 "pr" => {
126 let base = get_str(args, "base");
127 let format = get_str(args, "format");
128 let depth = get_int(args, "depth").map(|d| d as usize);
129 let diff = get_str(args, "diff");
130 crate::tools::ctx_pack::handle(
131 "pr",
132 &project_root,
133 base.as_deref(),
134 format.as_deref(),
135 depth,
136 diff.as_deref(),
137 )
138 }
139 "create" => {
140 let name = get_str(args, "name")
141 .ok_or_else(|| ErrorData::invalid_params("name is required for create", None))?;
142 let version = get_str(args, "version");
143 let description = get_str(args, "description");
144 let author = get_str(args, "author");
145 let tags = get_str_array(args, "tags");
146 let layers = get_str_array(args, "layers");
147 let level = get_int(args, "level").map(|l| l as u32);
148 let scope = get_str(args, "scope");
149 crate::tools::ctx_pack::handle_create(
150 &project_root,
151 &name,
152 version.as_deref(),
153 description.as_deref(),
154 author.as_deref(),
155 tags.as_deref(),
156 layers.as_deref(),
157 level,
158 scope.as_deref(),
159 )
160 }
161 "list" => crate::tools::ctx_pack::handle_list(),
162 "info" => {
163 let name = get_str(args, "name")
164 .ok_or_else(|| ErrorData::invalid_params("name is required for info", None))?;
165 let version = get_str(args, "version");
166 crate::tools::ctx_pack::handle_info(&name, version.as_deref())
167 }
168 "remove" => {
169 let name = get_str(args, "name")
170 .ok_or_else(|| ErrorData::invalid_params("name is required for remove", None))?;
171 let version = get_str(args, "version");
172 crate::tools::ctx_pack::handle_remove(&name, version.as_deref())
173 }
174 "install" => {
175 let name = get_str(args, "name").ok_or_else(|| {
176 ErrorData::invalid_params("name is required for install", None)
177 })?;
178 let version = get_str(args, "version");
179 crate::tools::ctx_pack::handle_install(&name, version.as_deref(), &project_root)
180 }
181 "export" => {
182 let name = get_str(args, "name").ok_or_else(|| {
183 ErrorData::invalid_params("name is required for export", None)
184 })?;
185 let version = get_str(args, "version");
186 let file = get_str(args, "file");
187 crate::tools::ctx_pack::handle_export(&name, version.as_deref(), file.as_deref())
188 }
189 "import" => {
190 let file = get_str(args, "file")
191 .ok_or_else(|| ErrorData::invalid_params("file is required for import", None))?;
192 let apply = get_bool(args, "apply").unwrap_or(false);
193 crate::tools::ctx_pack::handle_import(&file, apply, &project_root)
194 }
195 "auto_load" => {
196 let name = get_str(args, "name");
197 let version = get_str(args, "version");
198 let enable = get_bool(args, "enable").unwrap_or(true);
199 crate::tools::ctx_pack::handle_auto_load(
200 name.as_deref(),
201 version.as_deref(),
202 enable,
203 )
204 }
205 "summary" => crate::tools::ctx_pack::handle_summary(&project_root),
206 _ => "Unknown action. Use: pr, create, list, info, remove, install, export, import, auto_load, summary".to_string(),
207 };
208
209 Ok(ToolOutput::simple(result))
210 }
211}