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によるトークン最適化、そしてエージェンティックループの実践パターンを見ていきましょう。
理解度チェック
まず上のクイズを完了してください
レッスン完了!