Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Mcp Tools Guide

ASecurity

Master creating MCP tools with type-safe parameters, automatic schema generation, and best practices

416 stars
0 votes
0 copies
1 views
Added 2/7/2026
developmentrustgosqltestingapidatabasesecurityperformancedocumentation

Works with

cliapimcp

Security Analysis

A100/100

Scanned 2/12/2026

Install to Claude Code

$npx -y skills add aiskillstore/marketplace --skill mcp-tools-guide --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Mcp Tools Guide?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Mcp Tools Guide
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/aiskillstore-mcp-tools-guide/badge)](https://www.skillsdirectory.com/skills/aiskillstore-mcp-tools-guide)

More formats (shields.io, HTML) on the badges page.

Download with Pro
Files
SKILL.md
---
name: mcp-tool-creation
description: Master creating MCP tools with type-safe parameters, automatic schema generation, and best practices
---

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.

Attribution

aiskillstoreaiskillstore
View sourceMore from aiskillstore →
SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

284722 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2192 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

9881 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →