mcp_execution_skill/
types.rs

1//! Type definitions for skill generation.
2//!
3//! This module defines all parameter and result types for skill generation:
4//! - `GenerateSkillParams`: Parameters for generating a skill
5//! - `GenerateSkillResult`: Result from skill generation
6//! - `SaveSkillParams`: Parameters for saving a skill
7//! - `SaveSkillResult`: Result from saving a skill
8
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11use std::path::PathBuf;
12
13/// Maximum `server_id` length (denial-of-service protection).
14const MAX_SERVER_ID_LENGTH: usize = 64;
15
16// ============================================================================
17// generate_skill types
18// ============================================================================
19
20/// Parameters for generating a skill.
21///
22/// # Examples
23///
24/// ```
25/// use mcp_execution_skill::types::GenerateSkillParams;
26///
27/// let params = GenerateSkillParams {
28///     server_id: "github".to_string(),
29///     servers_dir: None,
30///     skill_name: None,
31///     use_case_hints: None,
32/// };
33/// ```
34#[derive(Debug, Clone, Deserialize, JsonSchema)]
35pub struct GenerateSkillParams {
36    /// Server identifier (e.g., "github").
37    ///
38    /// Must contain only lowercase letters, digits, and hyphens.
39    pub server_id: String,
40
41    /// Base directory for generated servers.
42    ///
43    /// Default: `~/.claude/servers`
44    pub servers_dir: Option<PathBuf>,
45
46    /// Custom skill name.
47    ///
48    /// Default: `{server_id}-progressive`
49    pub skill_name: Option<String>,
50
51    /// Additional context about intended use cases.
52    ///
53    /// Helps generate more relevant documentation.
54    pub use_case_hints: Option<Vec<String>>,
55}
56
57/// Result from `generate_skill` tool.
58///
59/// Contains all context Claude needs to generate optimal SKILL.md content.
60#[derive(Debug, Clone, Serialize, JsonSchema)]
61pub struct GenerateSkillResult {
62    /// Server identifier.
63    pub server_id: String,
64
65    /// Suggested skill name.
66    pub skill_name: String,
67
68    /// Server description (inferred from tools).
69    pub server_description: Option<String>,
70
71    /// Tools grouped by category.
72    pub categories: Vec<SkillCategory>,
73
74    /// Total tool count.
75    pub tool_count: usize,
76
77    /// Example tool usages (for documentation).
78    pub example_tools: Vec<ToolExample>,
79
80    /// Prompt template for skill generation.
81    ///
82    /// Claude uses this prompt to generate SKILL.md content.
83    pub generation_prompt: String,
84
85    /// Output path for the skill file.
86    pub output_path: String,
87}
88
89/// A category of tools for the skill.
90#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
91pub struct SkillCategory {
92    /// Category name (e.g., "issues", "repositories").
93    pub name: String,
94
95    /// Human-readable display name.
96    pub display_name: String,
97
98    /// Tools in this category.
99    pub tools: Vec<SkillTool>,
100}
101
102/// Tool information for skill generation.
103#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
104pub struct SkillTool {
105    /// Original tool name.
106    pub name: String,
107
108    /// TypeScript function name.
109    pub typescript_name: String,
110
111    /// Short description.
112    pub description: String,
113
114    /// Keywords for discovery.
115    pub keywords: Vec<String>,
116
117    /// Required parameters.
118    pub required_params: Vec<String>,
119
120    /// Optional parameters.
121    pub optional_params: Vec<String>,
122}
123
124/// Example tool usage for documentation.
125#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
126pub struct ToolExample {
127    /// Tool name.
128    pub tool_name: String,
129
130    /// Natural language description of what this does.
131    pub description: String,
132
133    /// Example CLI command.
134    pub cli_command: String,
135
136    /// Example parameters as JSON.
137    pub params_json: String,
138}
139
140// ============================================================================
141// save_skill types
142// ============================================================================
143
144/// Parameters for saving a skill.
145///
146/// # Examples
147///
148/// ```
149/// use mcp_execution_skill::types::SaveSkillParams;
150///
151/// let params = SaveSkillParams {
152///     server_id: "github".to_string(),
153///     content: "---\nname: github\n---\n# GitHub".to_string(),
154///     output_path: None,
155///     overwrite: false,
156/// };
157/// ```
158#[derive(Debug, Clone, Deserialize, JsonSchema)]
159pub struct SaveSkillParams {
160    /// Server identifier.
161    pub server_id: String,
162
163    /// SKILL.md content (markdown with YAML frontmatter).
164    pub content: String,
165
166    /// Custom output path.
167    ///
168    /// Default: `~/.claude/skills/{server_id}/SKILL.md`
169    pub output_path: Option<PathBuf>,
170
171    /// Overwrite if exists.
172    #[serde(default)]
173    pub overwrite: bool,
174}
175
176/// Result from saving a skill.
177#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
178pub struct SaveSkillResult {
179    /// Whether save was successful.
180    pub success: bool,
181
182    /// Path where skill was saved.
183    pub output_path: String,
184
185    /// Whether an existing file was overwritten.
186    pub overwritten: bool,
187
188    /// Skill metadata extracted from content.
189    pub metadata: SkillMetadata,
190}
191
192/// Metadata extracted from saved skill.
193#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
194pub struct SkillMetadata {
195    /// Skill name from frontmatter.
196    pub name: String,
197
198    /// Description from frontmatter.
199    pub description: String,
200
201    /// Section count (H2 headers).
202    pub section_count: usize,
203
204    /// Approximate word count.
205    pub word_count: usize,
206}
207
208// ============================================================================
209// Validation functions
210// ============================================================================
211
212/// Validate `server_id` format and length.
213///
214/// # Arguments
215///
216/// * `server_id` - Server identifier to validate
217///
218/// # Returns
219///
220/// `Ok(())` if valid.
221///
222/// # Errors
223///
224/// Returns `Err` with descriptive message if:
225/// - Length exceeds 64 characters
226/// - Contains characters other than lowercase letters, digits, and hyphens
227///
228/// # Validation Rules
229///
230/// - Length must not exceed 64 characters
231/// - Must contain only lowercase letters, digits, and hyphens
232///
233/// # Examples
234///
235/// ```
236/// use mcp_execution_skill::validate_server_id;
237///
238/// assert!(validate_server_id("github").is_ok());
239/// assert!(validate_server_id("my-server-123").is_ok());
240/// assert!(validate_server_id("GitHub").is_err()); // uppercase
241/// assert!(validate_server_id("my_server").is_err()); // underscore
242/// ```
243pub fn validate_server_id(server_id: &str) -> Result<(), String> {
244    // Check length
245    if server_id.len() > MAX_SERVER_ID_LENGTH {
246        return Err(format!(
247            "server_id too long: {} chars exceeds {} limit",
248            server_id.len(),
249            MAX_SERVER_ID_LENGTH
250        ));
251    }
252
253    // Check format
254    if !server_id
255        .chars()
256        .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
257    {
258        return Err(
259            "server_id must contain only lowercase letters, digits, and hyphens".to_string(),
260        );
261    }
262
263    Ok(())
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269
270    #[test]
271    fn test_validate_server_id_valid() {
272        assert!(validate_server_id("github").is_ok());
273        assert!(validate_server_id("my-server").is_ok());
274        assert!(validate_server_id("server123").is_ok());
275        assert!(validate_server_id("my-server-123").is_ok());
276    }
277
278    #[test]
279    fn test_validate_server_id_uppercase() {
280        let result = validate_server_id("GitHub");
281        assert!(result.is_err());
282        assert!(result.unwrap_err().contains("lowercase"));
283    }
284
285    #[test]
286    fn test_validate_server_id_underscore() {
287        let result = validate_server_id("my_server");
288        assert!(result.is_err());
289        assert!(result.unwrap_err().contains("lowercase"));
290    }
291
292    #[test]
293    fn test_validate_server_id_special_chars() {
294        let result = validate_server_id("my@server");
295        assert!(result.is_err());
296        assert!(result.unwrap_err().contains("lowercase"));
297    }
298
299    #[test]
300    fn test_validate_server_id_too_long() {
301        let long_id = "a".repeat(65);
302        let result = validate_server_id(&long_id);
303        assert!(result.is_err());
304        assert!(result.unwrap_err().contains("too long"));
305    }
306
307    #[test]
308    fn test_validate_server_id_max_length() {
309        let max_id = "a".repeat(64);
310        assert!(validate_server_id(&max_id).is_ok());
311    }
312}