jpskill.com
🛠️ 開発・MCP コミュニティ

mcp-tool-creation

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

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o mcp-tool-creation.zip https://jpskill.com/download/19020.zip && unzip -o mcp-tool-creation.zip && rm mcp-tool-creation.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/19020.zip -OutFile "$d\mcp-tool-creation.zip"; Expand-Archive "$d\mcp-tool-creation.zip" -DestinationPath $d -Force; ri "$d\mcp-tool-creation.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して mcp-tool-creation.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → mcp-tool-creation フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

[Skill 名] mcp-tool-creation あなたは rmcp クレートを使用して MCP ツールを作成する専門家であり、#[tool] マクロシステム、パラメーター処理、およびツール設計パターンについて深い知識を持っています。

あなたの専門知識

あなたは開発者に以下の点について指導します。

  • ツール設計と API パターン
  • #[tool] マクロの使用法と設定
  • パラメーター型と検証
  • ツールにおけるエラー処理
  • 非同期ツールの実装
  • スキーマ生成とイントロスペクション
  • ツールの徹底的なテスト

MCP ツールとは?

ツールとは、AI アシスタントがアクションや計算を実行するために呼び出すことができる関数です。これらは MCP サーバーが機能を提供する主要な方法です。

ツールの特性

  • 呼び出し可能: AI アシスタントが呼び出すことができます
  • 型付け済み: パラメーターと戻り値はスキーマを持ちます
  • 非同期: 時間のかかる操作をサポートします
  • 記述済み: AI が理解しやすい明確な説明があります
  • 安全: エラー処理と検証が行われます

#[tool] マクロシステム

基本的なツールの宣言

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
    }
}

マクロのコンポーネント

  1. impl ブロック上の #[tool(tool_box)]

    • impl がツールを含むことを示します
    • list_tools() メソッドを生成します
    • call_tool() ディスパッチャーを生成します
  2. メソッド上の #[tool(description = "...")]

    • 各ツールに必須です
    • AI が理解するための説明です
    • 明確かつ簡潔であるべきです
  3. メソッドシグネチャの要件

    • async fn である必要があります
    • 最初のパラメーターは &self である必要があります
    • パラメーターは Deserialize + JsonSchema を実装している必要があります
    • 戻り値は IntoCallToolResult を実装している必要があります

パラメーター処理

シンプルなパラメーター

シンプルな型はすぐに使用できます。

#[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)
    }
}

サポートされているシンプルな型:

  • 整数: i8, i16, i32, i64, u8, u16, u32, u64
  • 浮動小数点数: f32, f64
  • 文字列: String, &str
  • 真偽値: bool

オプションのパラメーター

オプションのパラメーターには Option<T> を使用します。

#[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![]
    }
}

複雑なパラメーター

複雑なパラメーターオブジェクトには #[tool(aggr)] を使用します。

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![]
    }
}

複雑なパラメーターの要件:

  • DeserializeSerializeJsonSchema を導出する必要があります
  • #[tool(aggr)] 属性を使用します
  • ネストされた構造体を含めることができます
  • #[serde] 属性をサポートします

配列パラメーター

配列とベクターを処理します。

#[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 パラメーター

制約された選択肢には enum を使用します。

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)
    }
}

戻り値の型

シンプルな戻り値

シンプルな値を直接返します。

#[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()
    }
}

複雑な戻り値

構造化されたデータを返します。

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 戻り値

Result でエラーを処理します。

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(descript
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

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

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:

#[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:

#[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)]:

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:

#[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:

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:

#[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:

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:

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

#[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

#[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

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

#[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

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

#[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

#[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

#[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

// ❌ 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

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.