Claude Codeに「自分専用スキル」を追加する方法(MCP活用術)

はじめに

Anthropic社のClaude 3、特にコーディング能力に優れたclaude-3-opus-20240229モデルは、多くの開発者にとって強力な相棒となりつつあります。コード生成、リファクタリング、デバッグ支援など、その能力は多岐にわたります。

しかし、こんな風に感じたことはありませんか?

  • 「Claudeに社内のデータベースへアクセスさせて、最新の売上データを分析させたい…」
  • 「GitHubリポジトリの最新のIssueを要約させたり、新しいチケットをJIRAに起票させたりできたら最高なのに…」
  • 「特定の計算や、自作のコマンドラインツールを実行するよう指示できれば、作業が劇的に効率化されるはずだ…」

標準のClaudeは、学習データに含まれる一般的な知識しか持っておらず、あなたの会社のプライベートな情報や、リアルタイムの外部情報にアクセスすることはできません。これまでは、開発者が手動でAPIを叩き、その結果をコピー&ペーストしてClaudeに与える、という手間のかかる作業が必要でした。

もし、Claudeに**「あなた専用のスキル」**を自由に追加できたらどうでしょう?まるで優秀なアシスタントに新しい能力を教え込むように、独自のツールやAPIと連携させることができたら。

この記事では、Anthropicが提供する**「Tool Use(ツール使用)」機能を活用し、Claudeをあなただけの最強コーディング・アシスタントへと進化させる具体的な方法を、詳細なコード例とともに解説します。この記事で言うMCP(Multi-turn Conversational Platform)活用術**とは、まさにこのTool Use機能を駆使して、対話型AIの能力を最大限に引き出す実践的なテクニックを指します。

この記事を読み終える頃には、あなたはClaudeに独自の「スキル」を授け、開発ワークフローを根本から変革する力を手に入れているはずです。

なぜ今、「Tool Use」が重要なのか?

LLM(大規模言語モデル)は、単に質問に答えたり文章を生成したりするだけの存在から、外部の世界と対話し、能動的にタスクを実行する**「エージェント」**へと進化しつつあります。その中核をなす技術が、ChatGPTにおける「Function Calling」であり、Claudeにおける「Tool Use」です。

この技術がなぜゲームチェンジャーとなり得るのか、その背景にある課題と合わせて見ていきましょう。

従来のLLMが抱えていた課題

  1. 知識の陳腐化と非公開情報へのアクセス不可: LLMの知識は、学習データがカットオフされた時点のものであり、リアルタイムの情報(今日の天気、最新の株価など)を知りません。また、社内DBや個人のファイルなど、インターネット上に公開されていない情報には当然アクセスできません。
  2. 手動による連携の非効率性: 開発者が外部APIから取得したデータをLLMに渡す場合、手動でのコピー&ペーストが必要でした。このプロセスは手間がかかるだけでなく、ヒューマンエラーの温床にもなります。
  3. プロンプトの複雑化と再現性の低さ: 複雑なタスクを指示しようとすると、プロンプトが非常に長大かつ複雑になりがちです。「まずこのAPIを叩いてJSONを取得し、その中のitems配列をループして、各namepriceを抽出し、マークダウンのテーブル形式で出力して…」といった詳細な手順を毎回プロンプトに記述するのは現実的ではありません。

「Tool Use」がもたらす解決策

ClaudeのTool Use機能は、これらの課題をエレガントに解決します。その仕組みは、LLMと開発者が協調して動作するフレームワークです。

Tool Useの基本的なフロー

sequenceDiagram
    participant User as ユーザー
    participant App as 開発者のアプリケーション
    participant Claude as Claude
    participant Tool as 外部ツール/API

    User->>App: 「東京の天気は?」
    App->>Claude: ユーザーの質問と利用可能なツール定義を渡す
    Claude->>Claude: 質問を解釈し、「get_weather」ツールが使えると判断
    Claude->>App: 「get_weather(city='Tokyo')」を実行してほしい、とJSON形式で要求
    App->>Tool: 実際に天気APIを呼び出す
    Tool-->>App: 天気データ(JSON)を返す
    App->>Claude: ツール実行結果(天気データ)を渡す
    Claude->>Claude: 実行結果を解釈し、自然言語の回答を生成
    Claude->>App: 「東京の天気は晴れです。」
    App->>User: 最終的な回答を表示

このフローが示すように、Tool Useは以下の役割分担によって成り立っています。

  • Claudeの役割: ユーザーの意図を汲み取り、どのツールを、どのような引数で使うべきかを判断し、JSON形式で開発者に伝達する。そして、ツールの実行結果を受け取り、それを基に最終的な回答を生成する。
  • 開発者の役割: Claudeが利用可能な「スキル」(ツール)の仕様を定義し、Claudeからツール実行の要求を受け取ったら、実際にそのツール(関数やAPIコール)を実行し、結果をClaudeに返す。

これにより、LLMは思考や判断に集中し、実際の「行動」は開発者が用意した信頼性の高いコードが担う、という強力な分業体制が実現します。これは、LLMを単なる「知識ベース」から、具体的なタスクをこなす「実行エンジン」へと昇華させる、きわめて重要なパラダイムシフトなのです。

具体的な実装方法:Claudeに天気を尋ねるスキルを追加する

それでは、実際に手を動かして、Claudeに「天気を尋ねるスキル」を追加してみましょう。ここではPython用のAnthropic SDKを使用します。

Step 1: 準備

まず、必要なライブラリをインストールし、APIキーを設定します。

1
pip install anthropic

APIキーは環境変数に設定しておくのが一般的です。

1
export ANTHROPIC_API_KEY="YOUR_ANTHROPIC_API_KEY"

Step 2: 「スキル」(ツール)の定義

最初に、Claudeに「こんなツールが使えますよ」と教えるための定義を作成します。今回は、特定の都市の天気を取得するget_weatherというツールを定義しましょう。

このツール定義は、Claudeがツールの機能、目的、そして必要な引数を理解するために非常に重要です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import anthropic
import json

# Anthropicクライアントの初期化
client = anthropic.Anthropic()

# --- ここからがツールの定義 ---
tools = [
    {
        "name": "get_weather",
        "description": "特定の都市の現在の天気を取得します。ユーザーが天気について尋ねた場合に使用します。",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "天気を知りたい都市名。例: '東京', 'サンフランシスコ'"
                }
            },
            "required": ["city"]
        }
    }
]
# --- ここまでがツールの定義 ---

# 開発者側で実際にツールを実行する関数を定義
# (この例では実際のAPIは叩かず、ダミーデータを返す)
def get_weather(city: str):
    """指定された都市の天気を取得する(ダミー関数)"""
    print(f"--- ツール実行: get_weather(city='{city}') ---")
    if "東京" in city:
        return json.dumps({"city": "東京", "temperature": 25, "weather": "晴れ"})
    elif "大阪" in city:
        return json.dumps({"city": "大阪", "temperature": 22, "weather": "くもり"})
    else:
        return json.dumps({"city": city, "temperature": "不明", "weather": "不明"})

ここで重要なのはtoolsリストの中身です。

  • name: ツールの名前です。Claudeがどのツールを呼び出すか指定するのに使います。
  • description: 最も重要な要素の一つです。 Claudeはこの説明を読んで、いつ、何のためにこのツールを使うべきかを判断します。具体的で分かりやすい説明を心がけましょう。
  • input_schema: ツールが必要とする引数をJSON Schema形式で定義します。これにより、Claudeは正しい引数名とデータ型でツールを呼び出そうとします。

Step 3: Claudeにツール使用を促すリクエストを送信

次に、ユーザーからの質問と、先ほど定義したツールリストをClaudeに渡します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# ユーザーからの質問
user_message = "今日の東京の天気はどうですか?"

print(f"ユーザー: {user_message}")

# Claudeに最初のメッセージを送信
response = client.messages.create(
    model="claude-3-opus-20240229",
    max_tokens=1024,
    messages=[{"role": "user", "content": user_message}],
    tools=tools, # ここで定義したツールを渡す
)

print("\n--- Claudeからの初回応答 ---")
print(response)

このコードを実行すると、Claudeからの応答は通常のテキストではなく、ツールを使用するべきだという特別な形式で返ってきます。

Step 4: Claudeからの応答(ツール使用要求)を処理する

responseの中身を確認してみましょう。特にstop_reasoncontentに注目です。

# responseの出力例
Message(
    id='...',
    content=[
        ToolUseBlock(
            id='toolu_01A09q90e9QyqG7LLVf9i4t4',  # ツール使用ごとのユニークID
            input={'city': '東京'},
            name='get_weather',
            type='tool_use'
        )
    ],
    model='claude-3-opus-20240229',
    role='assistant',
    stop_reason='tool_use', # 停止理由が 'tool_use' になっている!
    stop_sequence=None,
    type='message',
    usage=Usage(input_tokens=..., output_tokens=...)
)

stop_reason'tool_use'になっているのが分かります。これは、「私は会話を一旦停止します。なぜなら、あなたが定義したツールを使いたいからです」というClaudeからの合図です。

contentリストには、ToolUseBlockオブジェクトが含まれており、どのツール(name)を、どの引数(input)で呼び出してほしいかが具体的に示されています。

この情報を使って、実際にツールを実行します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 会話の履歴を保持するリスト
conversation_history = [{"role": "user", "content": user_message}]

# Claudeの応答を履歴に追加
assistant_response_content = response.content
conversation_history.append({"role": "assistant", "content": assistant_response_content})

# ツール使用要求があるかチェック
if response.stop_reason == "tool_use":
    tool_use = next((block for block in response.content if block.type == "tool_use"), None)
    if tool_use:
        tool_name = tool_use.name
        tool_input = tool_use.input
        tool_use_id = tool_use.id

        print(f"\n--- Claudeがツール '{tool_name}' の使用を要求 ---")
        print(f"引数: {tool_input}")

        # ツール名に応じて適切な関数を実行
        if tool_name == "get_weather":
            tool_result = get_weather(city=tool_input["city"])
        else:
            # 未知のツールが要求された場合のエラーハンドリング
            tool_result = json.dumps({"error": f"Unknown tool: {tool_name}"})

Step 5: ツール実行結果をClaudeに返す

ツールを実行したら、その結果をClaudeにフィードバックする必要があります。これにより、Claudeは次のステップに進むことができます。

結果を返す際は、「どのツール実行要求に対する結果なのか」を明確にするために、Step 4で取得したtool_use_idを使います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
        print(f"ツール実行結果: {tool_result}")

        # ツール実行結果を会話履歴に追加
        conversation_history.append(
            {
                "role": "user",
                "content": [
                    {
                        "type": "tool_result",
                        "tool_use_id": tool_use_id,
                        "content": tool_result,
                    }
                ],
            }
        )

Step 6: Claudeからの最終的な回答を得る

最後に、ツール実行結果を含む更新された会話履歴を、再度Claudeに送信します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
        print("\n--- ツール実行結果をClaudeに送信し、最終回答を要求 ---")

        # ツール実行結果を踏まえた最終的な応答をClaudeに生成させる
        final_response = client.messages.create(
            model="claude-3-opus-20240229",
            max_tokens=2048,
            messages=conversation_history, # 更新された会話履歴を渡す
            tools=tools,
        )

        print("\n--- Claudeからの最終回答 ---")
        final_answer = final_response.content[0].text
        print(final_answer)

# 実行結果の例:
#
# ユーザー: 今日の東京の天気はどうですか?
#
# --- Claudeからの初回応答 ---
# ... (ToolUseBlockを含むMessageオブジェクト) ...
#
# --- Claudeがツール 'get_weather' の使用を要求 ---
# 引数: {'city': '東京'}
# --- ツール実行: get_weather(city='東京') ---
# ツール実行結果: {"city": "東京", "temperature": 25, "weather": "晴れ"}
#
# --- ツール実行結果をClaudeに送信し、最終回答を要求 ---
#
# --- Claudeからの最終回答 ---
# 東京の今日の天気は晴れで、気温は25度です。

見事にClaudeが外部ツール(今回はダミー関数)と連携し、ユーザーの質問に答えることができました。これがTool Useの基本的な流れです。

メリットとデメリット

この強力なTool Use機能ですが、もちろん良い点ばかりではありません。導入を検討する上で、メリットとデメリットを正しく理解しておくことが重要です。

メリット

  1. 無限の拡張性: Claudeの能力を、事実上無限に拡張できます。社内API、外部のSaaS、ローカルのスクリプト実行、データベース操作など、APIや関数として呼び出せるものなら何でも連携可能です。
  2. タスクの自動化と生産性向上: JIRAチケットの作成、GitHubのIssue要約、デプロイメントのキックなど、これまで手作業で行っていた定型的なタスクを自然言語の指示で自動化でき、開発者の生産性を劇的に向上させます。
  3. 対話の自然さ: ユーザーはツールの存在やAPIの仕様を意識する必要がありません。ただ自然言語で「〜して」と頼むだけで、裏側で適切なツールが呼び出されます。
  4. 信頼性の向上: LLM単体では事実に基づかない回答(ハルシネーション)を生成することがありますが、Tool Useを使えば、信頼できる情報源(APIの実行結果など)に基づいた回答を生成させることができます。
  5. プロンプトの抽象化: 複雑な手順を長々とプロンプトに書く代わりに、それらを一つのツールとしてカプセル化できます。プロンプトは簡潔になり、メンテナンス性も向上します。

デメリット

  1. 実装コスト: ツール定義の作成、API連携ロジックの実装、エラーハンドリングなど、開発者側でのコーディングが必要です。手軽に試せるものではなく、システムへの組み込みには設計と実装のコストがかかります。
  2. APIコストの増加: 一つの質問応答フローの中で、ClaudeのAPIを複数回(ツール使用判断で1回、最終回答生成で1回)呼び出すことになります。これにより、APIの利用料金が増加する可能性があります。
  3. レイテンシの増加: ツール実行(特に外部APIの呼び出し)には時間がかかるため、応答全体のレイテンシは長くなる傾向にあります。ユーザー体験を損なわないよう、パフォーマンスへの配慮が必要です。
  4. セキュリティリスク: データベースの更新やファイルの削除など、破壊的な操作を伴うツールを連携させる場合は、細心の注意が必要です。Claudeに意図しない操作を実行させないよう、厳格な権限管理、入力値のサニタイズ、実行前の人間による確認ステップなどを設けるべきです。

現場で使える実践的なTips

基本的な使い方をマスターしたところで、より実践的なシナリオでTool Useを使いこなすためのヒントをいくつか紹介します。

1. 良いツール設計の原則:descriptionが命

Claudeがツールを正しく使いこなせるかどうかは、ツールのdescription(説明文)の品質に大きく依存します。

悪い例 ❌: "description": "DBからデータを取得する" → これでは、いつ、何のデータを、どのように取得するのかが全く分かりません。

良い例 ✅: "description": "指定されたSQLクエリを読み取り専用の分析データベースで実行し、結果をJSON形式で返します。主に売上データやユーザー行動ログの集計・分析に使用します。データの更新や削除はできません。" → この説明により、Claudeは「売上分析の依頼が来たらこのツールを使おう」「SQLを生成して引数に渡せばいいんだな」「データを書き換えることはできないな」といったことを正確に理解できます。

引数の説明も同様に重要です。 ユーザーが曖昧な指示をした際に、Claudeがどの情報を追加でヒアリングすべきかの判断材料になります。

2. 堅牢なエラーハンドリング

ツールの実行は常に成功するとは限りません。APIがタイムアウトしたり、データベース接続に失敗したり、不正な引数が渡されたりすることもあります。

このような場合、エラー情報を適切にClaudeに返すことで、Claudeがユーザーに対して状況を説明したり、リトライを促したりできるようになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def query_database(sql_query: str):
    try:
        # ... データベース接続とクエリ実行のロジック ...
        result = db.execute(sql_query)
        return json.dumps({"status": "success", "data": result})
    except Exception as e:
        # エラーが発生したら、エラー情報を返す
        print(f"--- ツール実行エラー: {e} ---")
        return json.dumps({
            "status": "error",
            "error_message": f"データベースクエリの実行に失敗しました: {str(e)}"
        })

# ...

# ツール実行結果をClaudeに返す部分
tool_result = query_database(sql_query=tool_input["sql_query"])
conversation_history.append({
    "role": "user",
    "content": [{
        "type": "tool_result",
        "tool_use_id": tool_use_id,
        "content": tool_result, # エラー情報が含まれたJSONをそのまま渡す
        "is_error": "error" in json.loads(tool_result) # (Optional) エラーであることを明示
    }]
})

こうすることで、Claudeは「データベースクエリの実行に失敗しました。SQLの構文が間違っている可能性があります。クエリを確認してください: SELECT * FRM sales」のように、具体的なフィードバックを返すことができます。

3. 複数のツールを組み合わせる(思考の連鎖)

より複雑なタスクでは、複数のツールを段階的に実行する必要があります。

例: 「先月の売上トップだった商品の担当営業に、Slackでお祝いメッセージを送って」

このタスクを達成するには、おそらく2つのツールが必要です。

  1. get_top_sales_product(month: str): 指定した月の売上トップ商品と担当者を取得するツール。
  2. send_slack_message(user_id: str, message: str): 指定したユーザーにSlackメッセージを送信するツール。

このようなシナリオでは、Claudeは自律的に思考の連鎖(Chain of Thought)を行い、ツールを順番に呼び出します。

  1. ユーザーの指示を解釈し、まずget_top_sales_productを呼び出す必要があると判断する。
  2. 開発者側でこのツールを実行し、結果({"product": "商品A", "sales_person_id": "U12345"})を返す。
  3. Claudeは結果を受け取り、次にsend_slack_messageを、引数 user_id='U12345', message='...お祝いメッセージ...' で呼び出す必要があると判断する。
  4. 開発者側でSlack送信ツールを実行し、成功した旨を返す。
  5. Claudeはすべてのステップが完了したことを確認し、「先月の売上トップ商品Aの担当者である山田さんに、お祝いのSlackメッセージを送信しました。」と最終報告を行う。

このように、複数のツールを適切に設計・定義することで、Claudeをより高度な自律エージェントとして機能させることができます。

まとめ

本記事では、Claudeの「Tool Use」機能を用いて、Claudeに独自の「スキル」を追加し、あなた専用の最強アシスタントへと進化させる方法を解説しました。

Tool Useは、単なる機能追加ではありません。それは、LLMという強力な「脳」に、あなたの世界のツールやシステムという「手足」を与えることに他なりません。これまで分断されていたLLMの対話能力と、既存のソフトウェア資産を繋ぐ、きわめて重要な架け橋です。

今回紹介した基本的なフローと実践的なTipsを参考に、まずは身近なタスクを自動化する簡単なツールから試してみてください。天気予報の取得、簡単な計算、テキストファイルの読み書きなど、小さな成功体験を積み重ねることで、Tool Useの持つ無限の可能性を実感できるはずです。

LLMをワークフローに統合する動きは、今後ますます加速していくでしょう。Tool Useを使いこなし、Claudeを真の「相棒」として活用できるかどうかは、これからの開発者にとって大きなアドバンテージとなります。ぜひ、あなただけの「最強のClaude」を育て上げてみてください。