Claude
Skills
Sign in
Back

mcp-tool-creation

Included with Lifetime
$97 forever

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

AI Agents

What this skill does


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: "[email protected]".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(|| fo

Related in AI Agents