Skip to main content

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, Deserialize, 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    /// Non-fatal drift warnings, e.g. `.ts` files on disk excluded from
89    /// `categories`/`tool_count` because `_meta.json` has no matching entry
90    /// for them. Empty when the scanned directory has no drift.
91    #[serde(default)]
92    pub warnings: Vec<String>,
93}
94
95/// A category of tools for the skill.
96#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
97pub struct SkillCategory {
98    /// Category name (e.g., "issues", "repositories").
99    pub name: String,
100
101    /// Human-readable display name.
102    pub display_name: String,
103
104    /// Tools in this category.
105    pub tools: Vec<SkillTool>,
106}
107
108/// Tool information for skill generation.
109#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
110pub struct SkillTool {
111    /// Original tool name.
112    pub name: String,
113
114    /// TypeScript function name.
115    pub typescript_name: String,
116
117    /// Short description.
118    pub description: String,
119
120    /// Keywords for discovery.
121    pub keywords: Vec<String>,
122
123    /// Required parameters.
124    pub required_params: Vec<String>,
125
126    /// Optional parameters.
127    pub optional_params: Vec<String>,
128}
129
130/// Example tool usage for documentation.
131#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
132pub struct ToolExample {
133    /// Tool name.
134    pub tool_name: String,
135
136    /// Natural language description of what this does.
137    pub description: String,
138
139    /// Example CLI command.
140    pub cli_command: String,
141
142    /// Example parameters as JSON.
143    pub params_json: String,
144}
145
146// ============================================================================
147// save_skill types
148// ============================================================================
149
150/// Parameters for saving a skill.
151///
152/// # Examples
153///
154/// ```
155/// use mcp_execution_skill::types::SaveSkillParams;
156///
157/// let params = SaveSkillParams {
158///     server_id: "github".to_string(),
159///     content: "---\nname: github\n---\n# GitHub".to_string(),
160///     output_path: None,
161///     overwrite: false,
162/// };
163/// ```
164#[derive(Debug, Clone, Deserialize, JsonSchema)]
165pub struct SaveSkillParams {
166    /// Server identifier.
167    pub server_id: String,
168
169    /// SKILL.md content (markdown with YAML frontmatter).
170    pub content: String,
171
172    /// Custom output path.
173    ///
174    /// Default: `~/.claude/skills/{server_id}/SKILL.md`
175    pub output_path: Option<PathBuf>,
176
177    /// Overwrite if exists.
178    #[serde(default)]
179    pub overwrite: bool,
180}
181
182/// Result from saving a skill.
183#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
184pub struct SaveSkillResult {
185    /// Whether save was successful.
186    pub success: bool,
187
188    /// Path where skill was saved.
189    pub output_path: String,
190
191    /// Whether an existing file was overwritten.
192    pub overwritten: bool,
193
194    /// Skill metadata extracted from content.
195    pub metadata: SkillMetadata,
196}
197
198/// Metadata extracted from saved skill.
199#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
200pub struct SkillMetadata {
201    /// Skill name from frontmatter.
202    pub name: String,
203
204    /// Description from frontmatter.
205    pub description: String,
206
207    /// Section count (H2 headers).
208    pub section_count: usize,
209
210    /// Approximate word count.
211    pub word_count: usize,
212}
213
214// ============================================================================
215// Validation functions
216// ============================================================================
217
218/// Validate `server_id` format and length.
219///
220/// # Arguments
221///
222/// * `server_id` - Server identifier to validate
223///
224/// # Returns
225///
226/// `Ok(())` if valid.
227///
228/// # Errors
229///
230/// Returns `Err` with descriptive message if:
231/// - Length exceeds 64 characters
232/// - Contains characters other than lowercase letters, digits, and hyphens
233///
234/// # Validation Rules
235///
236/// - Length must not exceed 64 characters
237/// - Must contain only lowercase letters, digits, and hyphens
238///
239/// # Examples
240///
241/// ```
242/// use mcp_execution_skill::validate_server_id;
243///
244/// assert!(validate_server_id("github").is_ok());
245/// assert!(validate_server_id("my-server-123").is_ok());
246/// assert!(validate_server_id("GitHub").is_err()); // uppercase
247/// assert!(validate_server_id("my_server").is_err()); // underscore
248/// ```
249pub fn validate_server_id(server_id: &str) -> Result<(), String> {
250    // Check length
251    if server_id.len() > MAX_SERVER_ID_LENGTH {
252        return Err(format!(
253            "server_id too long: {} chars exceeds {} limit",
254            server_id.len(),
255            MAX_SERVER_ID_LENGTH
256        ));
257    }
258
259    // Check format
260    if !server_id
261        .chars()
262        .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
263    {
264        return Err(
265            "server_id must contain only lowercase letters, digits, and hyphens".to_string(),
266        );
267    }
268
269    Ok(())
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275
276    #[test]
277    fn test_validate_server_id_valid() {
278        assert!(validate_server_id("github").is_ok());
279        assert!(validate_server_id("my-server").is_ok());
280        assert!(validate_server_id("server123").is_ok());
281        assert!(validate_server_id("my-server-123").is_ok());
282    }
283
284    #[test]
285    fn test_validate_server_id_uppercase() {
286        let result = validate_server_id("GitHub");
287        assert!(result.is_err());
288        assert!(result.unwrap_err().contains("lowercase"));
289    }
290
291    #[test]
292    fn test_validate_server_id_underscore() {
293        let result = validate_server_id("my_server");
294        assert!(result.is_err());
295        assert!(result.unwrap_err().contains("lowercase"));
296    }
297
298    #[test]
299    fn test_validate_server_id_special_chars() {
300        let result = validate_server_id("my@server");
301        assert!(result.is_err());
302        assert!(result.unwrap_err().contains("lowercase"));
303    }
304
305    #[test]
306    fn test_validate_server_id_too_long() {
307        let long_id = "a".repeat(65);
308        let result = validate_server_id(&long_id);
309        assert!(result.is_err());
310        assert!(result.unwrap_err().contains("too long"));
311    }
312
313    #[test]
314    fn test_validate_server_id_max_length() {
315        let max_id = "a".repeat(64);
316        assert!(validate_server_id(&max_id).is_ok());
317    }
318}