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}