レッスン 5 15分

MCPサーバーを自作しよう:FastMCPで30行から

Python FastMCPでMCPサーバーを自作 — @mcp.tool()と@mcp.resource()でツールとリソースを実装、Claude Desktopに接続するまで。

🔄 レッスン4でMCPの3大プリミティブ(ツール・リソース・プロンプト)を学びました。既存サーバーの使い方がわかったところで、次は自分で作ってみましょう。

なぜ自作するのか

8,600以上のMCPサーバーがありますが、次のケースではカスタムサーバーが必要です:

  • 社内システム(社内Wiki、チケット管理、独自DB)との連携
  • 会社固有のビジネスロジックの実装
  • 既存サーバーにない機能の追加

FastMCP(Python SDK)を使えば、30行のコードから始められます

FastMCPのセットアップ

# FastMCPをインストール
pip install fastmcp

# 確認
python -c "import mcp; print('OK')"

最小限のサーバー:Todoリスト

まずは動くものを作りましょう。

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("todo-server")

# インメモリのTodoリスト
todos = []

@mcp.tool()
def add_todo(text: str) -> str:
    """Todoを追加する"""
    todos.append({"text": text, "done": False})
    return f"追加しました: {text}"

@mcp.tool()
def list_todos() -> str:
    """すべてのTodoを一覧表示する"""
    if not todos:
        return "Todoはありません"
    return "\n".join(
        f"{'✅' if t['done'] else '⬜'} {t['text']}"
        for t in todos
    )

@mcp.tool()
def complete_todo(index: int) -> str:
    """指定番号のTodoを完了にする"""
    if 0 <= index < len(todos):
        todos[index]["done"] = True
        return f"完了: {todos[index]['text']}"
    return "無効な番号です"

if __name__ == "__main__":
    mcp.run()

これだけで、3つのツール(追加・一覧・完了)を持つMCPサーバーが完成です。

Quick Check: 上のコードで @mcp.tool() を使っている理由は?リソースではダメ?(Todoの追加や完了は「状態を変更する」副作用のあるアクションだから、ツールが適切。リソースは読み取り専用データの提供に使います。)

リソースを追加する

ツールだけでなく、リソースも追加してみましょう。

@mcp.resource("todo://summary")
def todo_summary() -> str:
    """Todoリストのサマリーを返す"""
    total = len(todos)
    done = sum(1 for t in todos if t["done"])
    return f"合計: {total}件 / 完了: {done}件 / 未完了: {total - done}件"

リソースはURIで識別されます。todo://summary というURIで、Todoの統計情報をAIに提供します。

実用例:天気APIサーバー

もう少し実用的な例。外部APIと連携するサーバーです。

import httpx
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather-server")

@mcp.tool()
def get_weather(city: str) -> str:
    """指定した都市の天気を取得する"""
    # 実際にはAPIキーが必要
    url = f"https://api.example.com/weather?city={city}"
    response = httpx.get(url)
    data = response.json()
    return f"{city}: {data['temp']}°C, {data['condition']}"

@mcp.tool()
def get_forecast(city: str, days: int = 3) -> str:
    """指定した都市の天気予報を取得する"""
    url = f"https://api.example.com/forecast?city={city}&days={days}"
    response = httpx.get(url)
    return response.json()

Pythonの型ヒント(city: str, days: int = 3)とdocstringから、FastMCPがツール定義(名前、説明、パラメータスキーマ)を自動生成します。

エラーハンドリング

本番で使うなら、エラー処理は必須です。

@mcp.tool()
def query_database(sql: str) -> str:
    """SQLクエリを実行する(読み取り専用)"""
    if any(kw in sql.upper() for kw in ["DROP", "DELETE", "INSERT", "UPDATE"]):
        return "エラー: 読み取り専用です。SELECT文のみ実行可能です。"
    try:
        result = db.execute(sql)
        return str(result.fetchall())
    except Exception as e:
        return f"クエリエラー: {str(e)}"

SQL文の危険なキーワードをチェックして、読み取り専用を強制しています。

Claude Desktopに接続する

自作サーバーを claude_desktop_config.json に追加します。

{
  "mcpServers": {
    "todo": {
      "command": "python",
      "args": ["/Users/yourname/projects/todo_server.py"],
      "env": {}
    }
  }
}

uvxを使う場合(パッケージ化済みの場合):

{
  "mcpServers": {
    "todo": {
      "command": "uvx",
      "args": ["todo-server"],
      "env": {}
    }
  }
}

設定後、Claude Desktopを再起動してツールアイコンを確認してください。

ローカルでテストする

Claude Desktopに接続する前に、ローカルでテストできます。

# MCP Inspector(ブラウザでツールをテスト)
fastmcp dev todo_server.py

fastmcp dev コマンドでMCP Inspectorが起動し、ブラウザ上でツールの呼び出しと結果を確認できます。

Quick Check: fastmcp dev の用途は?(Claude Desktopに接続する前に、ブラウザ上のMCP Inspectorでツールの動作をテストする開発用コマンドです。)

まとめ

  • FastMCP(Python SDK)で30行からMCPサーバーを作れる
  • @mcp.tool() は副作用のあるアクション、@mcp.resource() は読み取り専用データ
  • 型ヒントとdocstringからツール定義を自動生成
  • エラーハンドリングで安全性を確保(危険なSQLのブロック等)
  • claude_desktop_config.json に追加してClaude Desktopに接続
  • fastmcp dev でローカルテスト

次のレッスン

サーバー1つを自作できるようになりました。レッスン6では 複数のMCPサーバーを組み合わせるアーキテクチャ を学びます。Claude Codeとの連携、Tool Searchによるトークン最適化、そしてエージェンティックループの実践パターンを見ていきましょう。

理解度チェック

1. FastMCPで最小限のMCPサーバーを作るのに必要なコード量は?

2. @mcp.tool()と@mcp.resource()の使い分けは?

3. 自作MCPサーバーをClaude Desktopに接続する際、commandフィールドに何を指定する?

すべての問題に答えてから確認できます

まず上のクイズを完了してください

関連スキル