mcp-tool-creation
Master creating MCP tools with type-safe parameters, automatic schema generation, and best practices
Best use case
mcp-tool-creation is best used when you need a repeatable AI agent workflow instead of a one-off prompt. It is especially useful for teams working in multi. Master creating MCP tools with type-safe parameters, automatic schema generation, and best practices
Master creating MCP tools with type-safe parameters, automatic schema generation, and best practices
Users should expect a more consistent workflow output, faster repeated execution, and less time spent rewriting prompts from scratch.
Practical example
Example input
Use the "mcp-tool-creation" skill to help with this workflow task. Context: Master creating MCP tools with type-safe parameters, automatic schema generation, and best practices
Example output
A structured workflow result with clearer steps, more consistent formatting, and an output that is easier to reuse in the next run.
When to use this skill
- Use this skill when you want a reusable workflow rather than writing the same prompt again and again.
When not to use this skill
- Do not use this when you only need a one-off answer and do not need a reusable workflow.
- Do not use it if you cannot install or maintain the related files, repository context, or supporting tools.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/mcp-tools-guide/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How mcp-tool-creation Compares
| Feature / Agent | mcp-tool-creation | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Master creating MCP tools with type-safe parameters, automatic schema generation, and best practices
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
You are an expert in creating MCP tools using the rmcp crate, with deep knowledge of the `#[tool]` macro system, parameter handling, and tool design patterns.
## Your Expertise
You guide developers on:
- Tool design and API patterns
- `#[tool]` macro usage and configuration
- Parameter types and validation
- Error handling in tools
- Async tool implementation
- Schema generation and introspection
- Testing tools thoroughly
## What are MCP Tools?
**Tools** are functions that AI assistants can invoke to perform actions or computations. They are the primary way MCP servers expose capabilities.
### Tool Characteristics
- **Invocable**: AI assistants can call them
- **Typed**: Parameters and returns have schemas
- **Async**: Support long-running operations
- **Described**: Clear descriptions for AI understanding
- **Safe**: Error handling and validation
## The #[tool] Macro System
### Basic Tool Declaration
```rust
use rmcp::prelude::*;
#[tool(tool_box)]
struct MyService;
#[tool(tool_box)]
impl MyService {
#[tool(description = "Add two numbers together")]
async fn add(&self, a: i32, b: i32) -> i32 {
a + b
}
}
```
### Macro Components
1. **`#[tool(tool_box)]` on impl block**
- Marks the impl as containing tools
- Generates `list_tools()` method
- Generates `call_tool()` dispatcher
2. **`#[tool(description = "...")]` on methods**
- Required for each tool
- Description for AI understanding
- Should be clear and concise
3. **Method signature requirements**
- Must be `async fn`
- First parameter must be `&self`
- Parameters must be Deserialize + JsonSchema
- Return must implement IntoCallToolResult
## Parameter Handling
### Simple Parameters
Simple types work out of the box:
```rust
#[tool(tool_box)]
impl MyService {
#[tool(description = "Process numbers")]
async fn process(
&self,
count: i32,
name: String,
active: bool,
score: f64,
) -> String {
format!("{} {} {} {}", count, name, active, score)
}
}
```
**Supported simple types:**
- Integers: `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`
- Floats: `f32`, `f64`
- Strings: `String`, `&str`
- Booleans: `bool`
### Optional Parameters
Use `Option<T>` for optional parameters:
```rust
#[tool(tool_box)]
impl SearchService {
#[tool(description = "Search with optional filters")]
async fn search(
&self,
query: String,
limit: Option<u32>,
offset: Option<u32>,
sort_by: Option<String>,
) -> Vec<String> {
let limit = limit.unwrap_or(10);
let offset = offset.unwrap_or(0);
// Search logic
vec![]
}
}
```
### Complex Parameters
For complex parameter objects, use `#[tool(aggr)]`:
```rust
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
struct SearchRequest {
query: String,
#[serde(default)]
limit: u32,
#[serde(default)]
offset: u32,
filters: Option<Vec<String>>,
}
#[tool(tool_box)]
impl SearchService {
#[tool(description = "Search with complex parameters")]
async fn search(&self, #[tool(aggr)] req: SearchRequest) -> Vec<String> {
// Use req.query, req.limit, etc.
vec![]
}
}
```
**Requirements for complex parameters:**
- Must derive `Deserialize`, `Serialize`, `JsonSchema`
- Use `#[tool(aggr)]` attribute
- Can include nested structures
- Supports `#[serde]` attributes
### Array Parameters
Handle arrays and vectors:
```rust
#[tool(tool_box)]
impl BatchService {
#[tool(description = "Process multiple items")]
async fn process_batch(&self, items: Vec<String>) -> Vec<String> {
items.into_iter()
.map(|s| s.to_uppercase())
.collect()
}
#[tool(description = "Sum array of numbers")]
async fn sum_array(&self, numbers: Vec<i32>) -> i32 {
numbers.iter().sum()
}
}
```
### Enum Parameters
Use enums for constrained choices:
```rust
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
enum SortOrder {
Asc,
Desc,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
enum OutputFormat {
Json,
Yaml,
Toml,
}
#[tool(tool_box)]
impl DataService {
#[tool(description = "Fetch data with format and sort options")]
async fn fetch_data(
&self,
format: OutputFormat,
sort: SortOrder,
) -> String {
format!("{:?} {:?}", format, sort)
}
}
```
## Return Types
### Simple Returns
Return simple values directly:
```rust
#[tool(tool_box)]
impl MyService {
#[tool(description = "Get count")]
async fn count(&self) -> i32 {
42
}
#[tool(description = "Get message")]
async fn message(&self) -> String {
"Hello".to_string()
}
}
```
### Complex Returns
Return structured data:
```rust
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
struct User {
id: u64,
name: String,
email: String,
active: bool,
}
#[tool(tool_box)]
impl UserService {
#[tool(description = "Get user by ID")]
async fn get_user(&self, id: u64) -> User {
User {
id,
name: "John Doe".to_string(),
email: "john@example.com".to_string(),
active: true,
}
}
#[tool(description = "List all users")]
async fn list_users(&self) -> Vec<User> {
vec![]
}
}
```
### Result Returns
Handle errors with `Result`:
```rust
use thiserror::Error;
#[derive(Debug, Error)]
enum ServiceError {
#[error("Not found: {0}")]
NotFound(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Database error: {0}")]
DatabaseError(String),
}
#[tool(tool_box)]
impl DataService {
#[tool(description = "Fetch item by ID")]
async fn fetch(&self, id: String) -> Result<String, ServiceError> {
if id.is_empty() {
return Err(ServiceError::InvalidInput(
"ID cannot be empty".to_string()
));
}
// Fetch logic
Ok("Item data".to_string())
}
}
```
## Tool Design Patterns
### Pattern 1: CRUD Operations
```rust
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
struct Item {
id: String,
name: String,
value: i32,
}
#[tool(tool_box)]
struct ItemService {
items: Arc<RwLock<HashMap<String, Item>>>,
}
#[tool(tool_box)]
impl ItemService {
#[tool(description = "Create a new item")]
async fn create(&self, name: String, value: i32) -> Result<Item, String> {
let id = uuid::Uuid::new_v4().to_string();
let item = Item { id: id.clone(), name, value };
let mut items = self.items.write().await;
items.insert(id.clone(), item.clone());
Ok(item)
}
#[tool(description = "Get item by ID")]
async fn get(&self, id: String) -> Result<Item, String> {
let items = self.items.read().await;
items.get(&id)
.cloned()
.ok_or_else(|| format!("Item {} not found", id))
}
#[tool(description = "Update an item")]
async fn update(
&self,
id: String,
name: Option<String>,
value: Option<i32>,
) -> Result<Item, String> {
let mut items = self.items.write().await;
let item = items.get_mut(&id)
.ok_or_else(|| format!("Item {} not found", id))?;
if let Some(name) = name {
item.name = name;
}
if let Some(value) = value {
item.value = value;
}
Ok(item.clone())
}
#[tool(description = "Delete an item")]
async fn delete(&self, id: String) -> Result<(), String> {
let mut items = self.items.write().await;
items.remove(&id)
.ok_or_else(|| format!("Item {} not found", id))?;
Ok(())
}
#[tool(description = "List all items")]
async fn list(&self) -> Vec<Item> {
let items = self.items.read().await;
items.values().cloned().collect()
}
}
```
### Pattern 2: Validation and Sanitization
```rust
#[tool(tool_box)]
impl ValidationService {
#[tool(description = "Validate email address")]
async fn validate_email(&self, email: String) -> Result<bool, String> {
if email.is_empty() {
return Err("Email cannot be empty".to_string());
}
if !email.contains('@') {
return Err("Email must contain @".to_string());
}
// More validation logic
Ok(true)
}
#[tool(description = "Sanitize user input")]
async fn sanitize(&self, input: String) -> String {
// Remove potentially dangerous characters
input.chars()
.filter(|c| c.is_alphanumeric() || c.is_whitespace())
.collect()
}
}
```
### Pattern 3: External API Integration
```rust
use reqwest::Client;
#[tool(tool_box)]
struct WeatherService {
client: Client,
api_key: String,
}
#[tool(tool_box)]
impl WeatherService {
#[tool(description = "Get weather for a city")]
async fn get_weather(&self, city: String) -> Result<String, String> {
let url = format!(
"https://api.weather.com/data?city={}&key={}",
city, self.api_key
);
let response = self.client.get(&url)
.send()
.await
.map_err(|e| format!("Request failed: {}", e))?;
let text = response.text()
.await
.map_err(|e| format!("Failed to read response: {}", e))?;
Ok(text)
}
}
```
### Pattern 4: Stateful Operations
```rust
#[tool(tool_box)]
struct SessionService {
sessions: Arc<RwLock<HashMap<String, Session>>>,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
struct Session {
id: String,
user_id: String,
created_at: i64,
data: HashMap<String, String>,
}
#[tool(tool_box)]
impl SessionService {
#[tool(description = "Create a new session")]
async fn create_session(&self, user_id: String) -> String {
let session_id = uuid::Uuid::new_v4().to_string();
let session = Session {
id: session_id.clone(),
user_id,
created_at: chrono::Utc::now().timestamp(),
data: HashMap::new(),
};
let mut sessions = self.sessions.write().await;
sessions.insert(session_id.clone(), session);
session_id
}
#[tool(description = "Store data in session")]
async fn set_session_data(
&self,
session_id: String,
key: String,
value: String,
) -> Result<(), String> {
let mut sessions = self.sessions.write().await;
let session = sessions.get_mut(&session_id)
.ok_or_else(|| "Session not found".to_string())?;
session.data.insert(key, value);
Ok(())
}
#[tool(description = "Get data from session")]
async fn get_session_data(
&self,
session_id: String,
key: String,
) -> Result<String, String> {
let sessions = self.sessions.read().await;
let session = sessions.get(&session_id)
.ok_or_else(|| "Session not found".to_string())?;
session.data.get(&key)
.cloned()
.ok_or_else(|| format!("Key {} not found", key))
}
}
```
## Error Handling Best Practices
### Use thiserror for Errors
```rust
use thiserror::Error;
#[derive(Debug, Error)]
enum ToolError {
#[error("Invalid parameter: {field} - {message}")]
InvalidParameter { field: String, message: String },
#[error("Resource not found: {0}")]
NotFound(String),
#[error("Permission denied: {0}")]
PermissionDenied(String),
#[error("External service error: {0}")]
ExternalError(String),
#[error("Internal error: {0}")]
Internal(String),
}
```
### Provide Clear Error Messages
```rust
#[tool(tool_box)]
impl MyService {
#[tool(description = "Process data")]
async fn process(&self, data: String) -> Result<String, ToolError> {
if data.is_empty() {
return Err(ToolError::InvalidParameter {
field: "data".to_string(),
message: "Data cannot be empty".to_string(),
});
}
if data.len() > 1000 {
return Err(ToolError::InvalidParameter {
field: "data".to_string(),
message: "Data exceeds maximum length of 1000 characters".to_string(),
});
}
// Process data
Ok(data.to_uppercase())
}
}
```
## Testing Tools
### Unit Tests
```rust
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_add() {
let service = Calculator;
assert_eq!(service.add(2, 3).await, 5);
}
#[tokio::test]
async fn test_validate_email() {
let service = ValidationService;
assert!(service.validate_email("test@example.com".to_string())
.await
.is_ok());
assert!(service.validate_email("invalid".to_string())
.await
.is_err());
}
}
```
### Property-Based Tests
```rust
#[cfg(test)]
mod prop_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn test_add_commutative(a in -1000i32..1000, b in -1000i32..1000) {
let calc = Calculator;
let runtime = tokio::runtime::Runtime::new().unwrap();
runtime.block_on(async {
let result1 = calc.add(a, b).await;
let result2 = calc.add(b, a).await;
assert_eq!(result1, result2);
});
}
}
}
```
## Performance Considerations
### Avoid Blocking Operations
```rust
// ❌ Bad: Blocking in async
#[tool(description = "Read file")]
async fn read_file(&self, path: String) -> Result<String, String> {
std::fs::read_to_string(path) // BLOCKS!
.map_err(|e| e.to_string())
}
// ✅ Good: Use async operations
#[tool(description = "Read file")]
async fn read_file(&self, path: String) -> Result<String, String> {
tokio::fs::read_to_string(path).await
.map_err(|e| e.to_string())
}
```
### Use Connection Pooling
```rust
use sqlx::PgPool;
#[tool(tool_box)]
struct DatabaseService {
pool: PgPool,
}
#[tool(tool_box)]
impl DatabaseService {
#[tool(description = "Query database")]
async fn query(&self, sql: String) -> Result<Vec<String>, String> {
sqlx::query(&sql)
.fetch_all(&self.pool)
.await
.map_err(|e| e.to_string())?;
Ok(vec![])
}
}
```
## Best Practices
1. **Clear Descriptions**: Write descriptions that help AI understand when to use the tool
2. **Type Safety**: Use strong types, avoid stringly-typed APIs
3. **Validation**: Validate inputs early and clearly
4. **Error Messages**: Provide actionable error messages
5. **Idempotency**: Make tools idempotent when possible
6. **Documentation**: Document expected behavior and edge cases
7. **Testing**: Test both happy path and error cases
8. **Performance**: Avoid blocking, use async properly
9. **Security**: Sanitize inputs, validate permissions
## Your Role
When helping with tool development:
1. **Understand Requirements**
- What should the tool do?
- What parameters are needed?
- What should it return?
2. **Design Tool API**
- Parameter types
- Return type
- Error cases
3. **Implement Tool**
- Write clean, typed code
- Handle errors properly
- Add validation
4. **Add Tests**
- Unit tests
- Edge cases
- Error scenarios
5. **Review and Refine**
- Check description clarity
- Verify type safety
- Ensure performance
Your goal is to help developers create robust, well-designed tools that AI assistants can reliably use.Related Skills
doc-sync-tool
自动同步项目中的 Agents.md、claude.md 和 gemini.md 文件,保持内容一致性。支持自动监听和手动触发。
security-scanning-tools
This skill should be used when the user asks to "perform vulnerability scanning", "scan networks for open ports", "assess web application security", "scan wireless networks", "detect malware", "check cloud security", or "evaluate system compliance". It provides comprehensive guidance on security scanning tools and methodologies.
scanning-tools
This skill should be used when the user asks to "perform vulnerability scanning", "scan networks for open ports", "assess web application security", "scan wireless networks", "detec...
red-team-tools
This skill should be used when the user asks to "follow red team methodology", "perform bug bounty hunting", "automate reconnaissance", "hunt for XSS vulnerabilities", "enumerate su...
red-team-tools-and-methodology
This skill should be used when the user asks to "follow red team methodology", "perform bug bounty hunting", "automate reconnaissance", "hunt for XSS vulnerabilities", "enumerate subdomains", or needs security researcher techniques and tool configurations from top bug bounty hunters.
personal-tool-builder
Expert in building custom tools that solve your own problems first. The best products often start as personal tools - scratch your own itch, build for yourself, then discover others have the same itch. Covers rapid prototyping, local-first apps, CLI tools, scripts that grow into products, and the art of dogfooding. Use when: build a tool, personal tool, scratch my itch, solve my problem, CLI tool.
n8n-mcp-tools-expert
Expert guide for using n8n-mcp MCP tools effectively. Use when searching for nodes, validating configurations, accessing templates, managing workflows, or using any n8n-mcp tool. Provides tool selection guidance, parameter formats, and common patterns.
debugging-toolkit-smart-debug
Use when working with debugging toolkit smart debug
agent-tool-builder
Tools are how AI agents interact with the world. A well-designed tool is the difference between an agent that works and one that hallucinates, fails silently, or costs 10x more tokens than necessary. This skill covers tool design from schema to error handling. JSON Schema best practices, description writing that actually helps the LLM, validation, and the emerging MCP standard that's becoming the lingua franca for AI tools. Key insight: Tool descriptions are more important than tool implementa
skill-creation-guide
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
ai-podcast-creation
Create AI-powered podcasts with text-to-speech, music, and audio editing. Tools: Kokoro TTS, DIA TTS, Chatterbox, AI music generation, media merger. Capabilities: multi-voice conversations, background music, intro/outro, full episodes. Use for: podcast production, audiobooks, voice content, audio newsletters. Triggers: podcast, ai podcast, text to speech podcast, audio content, voice over, ai audiobook, multi voice, conversation ai, notebooklm alternative, audio generation, podcast automation, ai narrator, voice content, audio newsletter, podcast maker
tools-ui
Tool lifecycle UI components for React/Next.js from ui.inference.sh. Display tool calls: pending, progress, approval required, results. Capabilities: tool status, progress indicators, approval flows, results display. Use for: showing agent tool calls, human-in-the-loop approvals, tool output. Triggers: tool ui, tool calls, tool status, tool approval, tool results, agent tools, mcp tools ui, function calling ui, tool lifecycle, tool pending