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}