mcp_execution_server/types.rs
1//! Type definitions for MCP server tools.
2//!
3//! This module defines all parameter and result types for the three main tools:
4//! - `introspect_server`: Connect to and introspect an MCP server
5//! - `save_categorized_tools`: Generate TypeScript files with categorization
6//! - `list_generated_servers`: List all servers with generated files
7
8use chrono::{DateTime, Utc};
9use mcp_execution_core::{ServerConfig, ServerId};
10use mcp_execution_introspector::ServerInfo;
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::path::PathBuf;
15use uuid::Uuid;
16
17// ============================================================================
18// introspect_server types
19// ============================================================================
20
21/// Parameters for introspecting an MCP server.
22///
23/// # Examples
24///
25/// ```
26/// use mcp_execution_server::types::IntrospectServerParams;
27/// use std::collections::HashMap;
28///
29/// let params = IntrospectServerParams {
30/// server_id: "github".to_string(),
31/// command: "npx".to_string(),
32/// args: vec!["-y".to_string(), "@anthropic/mcp-server-github".to_string()],
33/// env: HashMap::new(),
34/// output_dir: None,
35/// };
36/// ```
37#[derive(Debug, Clone, Deserialize, JsonSchema)]
38pub struct IntrospectServerParams {
39 /// Unique identifier for the server (e.g., "github", "filesystem")
40 pub server_id: String,
41
42 /// Command to start the server (e.g., "npx", "docker")
43 pub command: String,
44
45 /// Arguments to pass to the command
46 #[serde(default)]
47 pub args: Vec<String>,
48
49 /// Environment variables for the server process
50 #[serde(default)]
51 pub env: HashMap<String, String>,
52
53 /// Custom output directory (default: `~/.claude/servers/{server_id}`)
54 pub output_dir: Option<PathBuf>,
55}
56
57/// Result from introspecting an MCP server.
58///
59/// Contains tool metadata for Claude to categorize and a session ID
60/// for use with `save_categorized_tools`.
61#[derive(Debug, Clone, Serialize, JsonSchema)]
62pub struct IntrospectServerResult {
63 /// Server identifier
64 pub server_id: String,
65
66 /// Human-readable server name
67 pub server_name: String,
68
69 /// Number of tools discovered
70 pub tools_found: usize,
71
72 /// List of tools for categorization
73 pub tools: Vec<ToolMetadata>,
74
75 /// Session ID for `save_categorized_tools` call
76 pub session_id: Uuid,
77
78 /// Session expiration time (ISO 8601)
79 pub expires_at: DateTime<Utc>,
80}
81
82/// Metadata about a tool for categorization by Claude.
83///
84/// Includes the tool name, description, and parameter names to help
85/// Claude understand the tool's purpose and assign appropriate categories.
86#[derive(Debug, Clone, Serialize, JsonSchema)]
87pub struct ToolMetadata {
88 /// Original tool name
89 pub name: String,
90
91 /// Tool description from server
92 pub description: String,
93
94 /// Parameter names for context
95 pub parameters: Vec<String>,
96}
97
98// ============================================================================
99// save_categorized_tools types
100// ============================================================================
101
102/// Parameters for saving categorized tools.
103///
104/// # Examples
105///
106/// ```
107/// use mcp_execution_server::types::{SaveCategorizedToolsParams, CategorizedTool};
108/// use uuid::Uuid;
109///
110/// let params = SaveCategorizedToolsParams {
111/// session_id: Uuid::new_v4(),
112/// categorized_tools: vec![
113/// CategorizedTool {
114/// name: "create_issue".to_string(),
115/// category: "issues".to_string(),
116/// keywords: "create,issue,new,bug,feature".to_string(),
117/// short_description: "Create a new issue in a repository".to_string(),
118/// },
119/// ],
120/// };
121/// ```
122#[derive(Debug, Clone, Deserialize, JsonSchema)]
123pub struct SaveCategorizedToolsParams {
124 /// Session ID from `introspect_server` call
125 pub session_id: Uuid,
126
127 /// Tools with Claude's categorization
128 pub categorized_tools: Vec<CategorizedTool>,
129}
130
131/// A tool with categorization metadata from Claude.
132///
133/// Claude analyzes the tool's purpose and provides:
134/// - A category for grouping related tools
135/// - Keywords for discovery via grep/search
136/// - A concise description for file headers
137#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
138pub struct CategorizedTool {
139 /// Original tool name (must match introspected tool)
140 pub name: String,
141
142 /// Category assigned by Claude (e.g., "issues", "repos", "users")
143 pub category: String,
144
145 /// Comma-separated keywords for discovery
146 pub keywords: String,
147
148 /// Concise description (max 80 chars) for header comment
149 pub short_description: String,
150}
151
152/// Result from saving categorized tools.
153///
154/// Reports success status, number of files generated, and any errors
155/// that occurred during generation.
156#[derive(Debug, Clone, Serialize, JsonSchema)]
157pub struct SaveCategorizedToolsResult {
158 /// Whether generation succeeded
159 pub success: bool,
160
161 /// Number of TypeScript files created
162 pub files_generated: usize,
163
164 /// Directory where files were written
165 pub output_dir: String,
166
167 /// Count of tools per category
168 pub categories: HashMap<String, usize>,
169
170 /// Any tools that failed to generate
171 #[serde(skip_serializing_if = "Vec::is_empty")]
172 pub errors: Vec<ToolGenerationError>,
173}
174
175/// Error that occurred while generating a specific tool.
176#[derive(Debug, Clone, Serialize, JsonSchema)]
177pub struct ToolGenerationError {
178 /// Name of the tool that failed
179 pub tool_name: String,
180
181 /// Error message
182 pub error: String,
183}
184
185// ============================================================================
186// list_generated_servers types
187// ============================================================================
188
189/// Parameters for listing generated servers.
190#[derive(Debug, Clone, Deserialize, JsonSchema)]
191pub struct ListGeneratedServersParams {
192 /// Base directory to scan (default: `~/.claude/servers`)
193 pub base_dir: Option<String>,
194}
195
196/// Result from listing generated servers.
197#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
198pub struct ListGeneratedServersResult {
199 /// List of servers with generated files
200 pub servers: Vec<GeneratedServerInfo>,
201
202 /// Total number of servers found
203 pub total_servers: usize,
204}
205
206/// Information about a server with generated progressive loading files.
207#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
208pub struct GeneratedServerInfo {
209 /// Server identifier
210 pub id: String,
211
212 /// Number of tool files (excluding runtime)
213 pub tool_count: usize,
214
215 /// Last generation timestamp
216 pub generated_at: Option<DateTime<Utc>>,
217
218 /// Directory path
219 pub output_dir: String,
220}
221
222// ============================================================================
223// State management types
224// ============================================================================
225
226/// Pending generation session.
227///
228/// Stores introspection data between `introspect_server` and
229/// `save_categorized_tools` calls.
230#[derive(Debug, Clone)]
231pub struct PendingGeneration {
232 /// Server identifier
233 pub server_id: ServerId,
234
235 /// Full server introspection data
236 pub server_info: ServerInfo,
237
238 /// Server configuration for regeneration if needed
239 pub config: ServerConfig,
240
241 /// Output directory
242 pub output_dir: PathBuf,
243
244 /// Session creation time
245 pub created_at: DateTime<Utc>,
246
247 /// Session expiration time (30 minutes default)
248 pub expires_at: DateTime<Utc>,
249}
250
251impl PendingGeneration {
252 /// Default session timeout: 30 minutes.
253 pub const DEFAULT_TIMEOUT_MINUTES: i64 = 30;
254
255 /// Creates a new pending generation session.
256 ///
257 /// # Examples
258 ///
259 /// ```
260 /// use mcp_execution_server::types::PendingGeneration;
261 /// use mcp_execution_core::{ServerId, ServerConfig};
262 /// use mcp_execution_introspector::ServerInfo;
263 /// use std::path::PathBuf;
264 ///
265 /// # fn example(server_info: ServerInfo) {
266 /// let server_id = ServerId::new("github");
267 /// let config = ServerConfig::builder()
268 /// .command("npx".to_string())
269 /// .arg("-y".to_string())
270 /// .arg("@anthropic/mcp-server-github".to_string())
271 /// .build();
272 /// let output_dir = PathBuf::from("/tmp/output");
273 ///
274 /// let pending = PendingGeneration::new(
275 /// server_id,
276 /// server_info,
277 /// config,
278 /// output_dir,
279 /// );
280 /// # }
281 /// ```
282 #[must_use]
283 pub fn new(
284 server_id: ServerId,
285 server_info: ServerInfo,
286 config: ServerConfig,
287 output_dir: PathBuf,
288 ) -> Self {
289 let now = Utc::now();
290 Self {
291 server_id,
292 server_info,
293 config,
294 output_dir,
295 created_at: now,
296 expires_at: now + chrono::Duration::minutes(Self::DEFAULT_TIMEOUT_MINUTES),
297 }
298 }
299
300 /// Checks if this session has expired.
301 ///
302 /// # Examples
303 ///
304 /// ```
305 /// use mcp_execution_server::types::PendingGeneration;
306 /// # use mcp_execution_core::{ServerId, ServerConfig};
307 /// # use mcp_execution_introspector::ServerInfo;
308 /// # use std::path::PathBuf;
309 ///
310 /// # fn example(server_info: ServerInfo) {
311 /// let pending = PendingGeneration::new(
312 /// ServerId::new("test"),
313 /// server_info,
314 /// ServerConfig::builder().command("echo".to_string()).build(),
315 /// PathBuf::from("/tmp"),
316 /// );
317 ///
318 /// assert!(!pending.is_expired());
319 /// # }
320 /// ```
321 #[must_use]
322 pub fn is_expired(&self) -> bool {
323 Utc::now() > self.expires_at
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330
331 #[test]
332 fn test_pending_generation_not_expired() {
333 let pending = create_test_pending();
334 assert!(!pending.is_expired());
335 }
336
337 #[test]
338 fn test_categorized_tool_serialization() {
339 let tool = CategorizedTool {
340 name: "create_issue".to_string(),
341 category: "issues".to_string(),
342 keywords: "create,issue,new".to_string(),
343 short_description: "Create a new issue".to_string(),
344 };
345
346 let json = serde_json::to_string(&tool).unwrap();
347 let _deserialized: CategorizedTool = serde_json::from_str(&json).unwrap();
348 }
349
350 // Test helper
351 fn create_test_pending() -> PendingGeneration {
352 use mcp_execution_core::ToolName;
353 use mcp_execution_introspector::{ServerCapabilities, ToolInfo};
354
355 let server_id = ServerId::new("test");
356 let server_info = ServerInfo {
357 id: server_id.clone(),
358 name: "Test Server".to_string(),
359 version: "1.0.0".to_string(),
360 capabilities: ServerCapabilities {
361 supports_tools: true,
362 supports_resources: false,
363 supports_prompts: false,
364 },
365 tools: vec![ToolInfo {
366 name: ToolName::new("test_tool"),
367 description: "Test tool description".to_string(),
368 input_schema: serde_json::json!({}),
369 output_schema: None,
370 }],
371 };
372 let config = ServerConfig::builder().command("echo".to_string()).build();
373 let output_dir = PathBuf::from("/tmp/test");
374
375 PendingGeneration::new(server_id, server_info, config, output_dir)
376 }
377}