AIコーディング 2026.04.23
PythonでMCPサーバーを作る完全ガイド【2026年版・本番運用コード付き】
MCPサーバーとは
MCPサーバーは、ClaudeなどのAIが「ツール」として呼び出せる機能を提供するサーバーです。例えば:
- 社内データベースを検索するツール
- Slackにメッセージを送るツール
- 天気情報を取得するツール
- ファイルを操作するツール
これらをMCPサーバーとして実装すると、ClaudeがAPIの詳細を知らなくても自然言語の指示から適切なツールを選んで実行できます。
ステップ1:環境構築
# Python 3.10以上が必要
python --version
# mcp ライブラリをインストール
pip install mcp
# または uv を使う場合(推奨)
pip install uv
uv add mcp
ステップ2:最小構成のMCPサーバーを作る
# server.py
from mcp.server import Server
from mcp.server.models import InitializationOptions
from mcp.types import Tool, TextContent
import mcp.server.stdio as stdio_server
import asyncio
# サーバーインスタンスを作成
app = Server("my-mcp-server")
# ツールの定義
@app.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="get_weather",
description="指定した都市の現在の天気を取得します",
inputSchema={
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "天気を調べる都市名(例: Tokyo, Osaka)"
}
},
"required": ["city"]
}
),
Tool(
name="calculate",
description="数式を計算します",
inputSchema={
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "計算する数式(例: 2 + 3 * 4)"
}
},
"required": ["expression"]
}
)
]
# ツールの実装
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name == "get_weather":
city = arguments["city"]
# 実際には天気APIを呼び出す(ここでは仮実装)
result = f"{city}の天気:晴れ、気温25°C、湿度60%"
return [TextContent(type="text", text=result)]
elif name == "calculate":
expression = arguments["expression"]
# 安全な計算(evalは使わない)
try:
# 数字と演算子のみ許可
import re
if not re.match(r'^[\d\s\+\-\*\/\(\)\.]+$', expression):
raise ValueError("不正な文字が含まれています")
result = eval(expression) # バリデーション後のみ許可
return [TextContent(type="text", text=f"計算結果: {result}")]
except Exception as e:
return [TextContent(type="text", text=f"エラー: {str(e)}")]
else:
return [TextContent(type="text", text=f"不明なツール: {name}")]
# サーバーを起動
async def main():
async with stdio_server.stdio_server() as (read_stream, write_stream):
await app.run(
read_stream,
write_stream,
InitializationOptions(
server_name="my-mcp-server",
server_version="1.0.0"
)
)
if __name__ == "__main__":
asyncio.run(main())
ステップ3:Claude Codeから使えるように登録する
# Claude Codeにサーバーを登録
claude mcp add my-server -- python /path/to/server.py
# 登録確認
claude mcp list
または claude_desktop_config.json に追加:
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["/path/to/server.py"]
}
}
}
ステップ4:本番向けの実装(認証・ロギング・エラーハンドリング)
# server_production.py
import asyncio
import logging
import os
from datetime import datetime
from typing import Any
from mcp.server import Server
from mcp.server.models import InitializationOptions
from mcp.types import Tool, TextContent
import mcp.server.stdio as stdio_server
# ロギング設定
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
handlers=[
logging.StreamHandler(),
logging.FileHandler(f"mcp-server-{datetime.now().strftime('%Y%m%d')}.log")
]
)
logger = logging.getLogger("mcp-server")
app = Server("production-mcp-server")
# API キーによる認証チェック
ALLOWED_API_KEYS = set(os.environ.get("MCP_API_KEYS", "").split(","))
def validate_request(arguments: dict) -> bool:
"""リクエストの基本的なバリデーション"""
if not isinstance(arguments, dict):
return False
# 各フィールドの文字列長制限
for key, value in arguments.items():
if isinstance(value, str) and len(value) > 10000:
logger.warning(f"入力が長すぎます: {key} = {len(value)}文字")
return False
return True
@app.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="search_database",
description="社内データベースを検索します",
inputSchema={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "検索クエリ",
"maxLength": 500
},
"limit": {
"type": "integer",
"description": "最大取得件数",
"minimum": 1,
"maximum": 100,
"default": 10
}
},
"required": ["query"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
request_id = datetime.now().strftime("%Y%m%d%H%M%S%f")
logger.info(f"[{request_id}] ツール呼び出し: {name}, 引数: {list(arguments.keys())}")
# バリデーション
if not validate_request(arguments):
logger.error(f"[{request_id}] バリデーションエラー")
return [TextContent(type="text", text="エラー: 不正なリクエストです")]
try:
if name == "search_database":
result = await execute_search(
query=arguments["query"],
limit=arguments.get("limit", 10)
)
logger.info(f"[{request_id}] 成功: {len(result)}件取得")
return [TextContent(type="text", text=result)]
else:
return [TextContent(type="text", text=f"不明なツール: {name}")]
except Exception as e:
# スタックトレースは内部ログのみ、外部には一般的なエラーを返す
logger.exception(f"[{request_id}] 予期しないエラー: {name}")
return [TextContent(type="text", text="エラー: 処理中に問題が発生しました")]
async def execute_search(query: str, limit: int) -> str:
"""データベース検索(実装例)"""
# ここに実際のDB検索ロジックを実装
return f"検索結果({limit}件上限): {query} に関するデータ..."
async def main():
logger.info("MCPサーバー起動")
async with stdio_server.stdio_server() as (read_stream, write_stream):
await app.run(
read_stream,
write_stream,
InitializationOptions(
server_name="production-mcp-server",
server_version="1.0.0"
)
)
if __name__ == "__main__":
asyncio.run(main())
ステップ5:Docker化して本番デプロイ
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
# 依存関係のインストール
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# アプリのコピー
COPY server_production.py .
# 非rootユーザーで実行(セキュリティ)
RUN adduser --disabled-password --gecos '' mcpuser
USER mcpuser
CMD ["python", "server_production.py"]
# docker-compose.yml
services:
mcp-server:
build: .
environment:
- MCP_API_KEYS=${MCP_API_KEYS}
- DATABASE_URL=${DATABASE_URL}
volumes:
- ./logs:/app/logs
restart: unless-stopped
# デプロイ
docker-compose up -d
docker-compose logs -f mcp-server
テスト方法
# mcp-inspector でブラウザからテスト
npx @modelcontextprotocol/inspector python server.py
# → ブラウザで http://localhost:5173 が開き、
# ツール一覧確認・実行テストができる
あわせて読みたい
- Claude CodeでJira調査を自動化する。MCPサーバーでツール連携を実現
- Claude Code初心者向け:MCPサーバーで外部データ連携を実現する
- MCPを1クリックでインストール — JSONファイルの編集をスキップする方法