MCPサーバーの構築方法:コード例付きステップバイステップガイド

モデルコンテキストプロトコル(MCP)は、AIモデルを実世界のツールやサービスに接続するための新しい標準になりつつあります。MCPサーバーを構築することで、シンプルで標準化されたインターフェースを通じて、データ、アクション、リソースをClaudeのようなLLMに公開することができます。
このガイドでは、Pythonで基本的なMCPサーバーをセットアップし、リソースとツールを定義し、MCPクライアントに接続する方法をステップバイステップで学びます。
重要なポイント
- MCPサーバーにより、AIモデルは標準化されたリソースとツールを通じて外部システムと対話できます。
- 公式SDKを使用してPythonでMCPサーバーを構築できます。
- 最小限の動作するサーバーは、読み取り専用データ(リソース)と実行可能なアクション(ツール)の両方を公開できます。
- 本番環境のデプロイにはセキュリティとエラー処理が重要です。
MCPサーバーとは
MCPサーバーは、LLMとデータベース、ファイルストレージ、APIなどの外部システムの間の橋渡しとして機能します。リソース(読み取り可能なデータ)、ツール(アクション)、プロンプト(指示)をLLMがタスク中に安全に使用できる方法で定義します。
各モデルやツールごとにカスタム統合を作成する代わりに、MCPはプロトコルバージョン0.1(2025年4月現在)で動作する普遍的な標準を提供します。
開始前に必要なもの
- Python 3.8以降
- Pythonスクリプトの基本的な経験
- Python用MCP SDK(pipで入手可能)
- Claude DesktopやCursorなどのMCP互換クライアント(テスト用にオプション)
- バージョン管理用のGit(推奨)
- テキストエディタまたはIDE(Visual Studio Code推奨)
コア構造の理解
MCPでは:
- サーバー:LLMにリソースとツールを提供します。
- クライアント:LLMをサーバーに接続します。
- プロトコル:クライアントとサーバー間の通信を管理します。
次の2つの重要なプリミティブを定義します:
- リソース:LLMが読み取れる静的または動的な情報。
- ツール:LLMが実行できる呼び出し可能な関数。
通信フローは次のように機能します:
- LLM(クライアント経由)がサーバーからデータやアクションをリクエスト
- サーバーがこれらのリクエストを処理し、標準化された応答を返す
- LLMはこの情報を推論や応答に使用できる
1. Pythonプロジェクトのセットアップ
プロジェクトディレクトリとPython仮想環境を作成することから始めます。
mkdir my_mcp_server
cd my_mcp_server
python -m venv venv
source venv/bin/activate # Linux/Mac
venvScriptsactivate # Windows
基本的なプロジェクト構造を作成します:
mkdir -p src/resources src/tools tests
touch src/__init__.py src/resources/__init__.py src/tools/__init__.py
touch requirements.txt README.md
requirements.txt
に以下を追加します:
mcp-server>=0.1.0
pydantic>=2.0.0
pytest>=7.0.0
2. MCP SDKのインストール
Python用MCP Server SDKとその他の依存関係をインストールします:
pip install -r requirements.txt
公式SDKがまだ公開されていない場合は、GitHubリポジトリからインストールする必要があるかもしれません:
pip install git+https://github.com/anthropic/mcp-server-python.git
3. 基本的なMCPサーバーの作成
src/server.py
というファイルを作成します:
from typing import Dict, Any
from mcp_server import MCPServer
import logging
# ロギングの設定
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("mcp_server")
def main() -> None:
"""MCPサーバーを初期化して起動します。"""
try:
server = MCPServer(
name="MyMCPServer",
version="0.1.0",
description="シンプルなMCPサーバーの例"
)
# リソースとツールはここに追加されます
logger.info("MCPサーバーを起動しています...")
server.start()
except Exception as e:
logger.error(f"MCPサーバーの起動に失敗しました: {e}")
raise
if __name__ == "__main__":
main()
これで適切なロギングを備えた基本的なMCPサーバーがセットアップされます。
4. リソースの定義
リソースはモデルが読み取れるデータを公開します。src/resources/user_profiles.py
ファイルを作成しましょう:
from typing import List, Dict, Any
from pydantic import BaseModel
import logging
logger = logging.getLogger("mcp_server.resources")
class UserProfile(BaseModel):
"""ユーザープロファイルのデータモデル。"""
name: str
role: str
department: str = "General"
years_experience: int = 0
def fetch_user_profiles() -> List[Dict[str, Any]]:
"""
データベースからユーザープロファイルを取得します。
Returns:
List[Dict[str, Any]]: ユーザープロファイル辞書のリスト。
"""
try:
# 実際の実装ではデータベースにクエリを実行します
# この例では、モックデータを返します
users = [
UserProfile(name="Alice", role="Engineer", department="Engineering", years_experience=5),
UserProfile(name="Bob", role="Product Manager", department="Product", years_experience=3),
UserProfile(name="Charlie", role="Designer", department="Design", years_experience=7)
]
logger.info(f"{len(users)}件のユーザープロファイルの取得に成功しました")
return [user.model_dump() for user in users]
except Exception as e:
logger.error(f"ユーザープロファイルの取得中にエラーが発生しました: {e}")
# 本番環境では、エラー処理戦略に応じて空のリストを返すか
# 特定の例外を発生させることもあります
return []
次に、このリソースを含めるためにsrc/server.py
を更新します:
from typing import Dict, Any
from mcp_server import MCPServer, Resource
import logging
from src.resources.user_profiles import fetch_user_profiles
# ... 既存のコード ...
def main() -> None:
"""MCPサーバーを初期化して起動します。"""
try:
server = MCPServer(
name="MyMCPServer",
version="0.1.0",
description="シンプルなMCPサーバーの例"
)
# ユーザープロファイルリソースを追加
user_profiles = Resource(
name="user_profiles",
description="会社データベースからのユーザープロファイルのリスト。",
fetch_fn=fetch_user_profiles
)
server.add_resource(user_profiles)
logger.info("MCPサーバーを起動しています...")
server.start()
except Exception as e:
logger.error(f"MCPサーバーの起動に失敗しました: {e}")
raise
if __name__ == "__main__":
main()
これでLLMはMCPクライアントを通じてuser_profiles
をクエリできるようになりました。
5. ツールの定義
ツールを使用すると、LLMがアクションを実行できます。src/tools/user_management.py
ファイルを作成します:
from typing import Dict, Any, Optional
from pydantic import BaseModel, Field, ValidationError
import logging
logger = logging.getLogger("mcp_server.tools")
class CreateUserRequest(BaseModel):
"""ユーザー作成リクエストの検証モデル。"""
name: str = Field(..., min_length=2, description="ユーザーのフルネーム")
role: str = Field(..., min_length=2, description="ユーザーの職務")
department: Optional[str] = Field("General", description="ユーザーの部署")
years_experience: Optional[int] = Field(0, ge=0, description="専門的な経験年数")
def create_user_profile(request_data: Dict[str, Any]) -> Dict[str, Any]:
"""
データベースに新しいユーザープロファイルを作成します。
Args:
request_data (Dict[str, Any]): 名前、役割などを含むユーザーデータ。
Returns:
Dict[str, Any]: ステータスとユーザー情報を含むレスポンス
"""
try:
# 入力データを検証
user_data = CreateUserRequest(**request_data)
# 実際の実装ではデータベースに挿入します
# この例では、アクションをログに記録するだけです
logger.info(f"新しいユーザーを作成: {user_data.name} - {user_data.role} in {user_data.department}")
# 作成されたユーザーデータを含む成功レスポンスを返す
return {
"status": "success",
"message": f"ユーザー {user_data.name} が正常に作成されました",
"user": user_data.model_dump()
}
except ValidationError as e:
# 検証エラーの処理
logger.error(f"検証エラー: {e}")
return {
"status": "error",
"message": "無効なユーザーデータが提供されました",
"details": str(e)
}
except Exception as e:
# その他のエラーの処理
logger.error(f"ユーザー作成中にエラーが発生しました: {e}")
return {
"status": "error",
"message": "ユーザーの作成に失敗しました",
"details": str(e)
}
次に、このツールを含めるためにsrc/server.py
を更新します:
from typing import Dict, Any
from mcp_server import MCPServer, Resource, Tool
import logging
from src.resources.user_profiles import fetch_user_profiles
from src.tools.user_management import create_user_profile
# ... 既存のコード ...
def main() -> None:
"""MCPサーバーを初期化して起動します。"""
try:
server = MCPServer(
name="MyMCPServer",
version="0.1.0",
description="シンプルなMCPサーバーの例"
)
# ユーザープロファイルリソースを追加
user_profiles = Resource(
name="user_profiles",
description="会社データベースからのユーザープロファイルのリスト。",
fetch_fn=fetch_user_profiles
)
# ユーザー作成ツールを追加
create_user = Tool(
name="create_user_profile",
description="データベースに新しいユーザープロファイルを作成します。",
parameters={
"name": {"type": "string", "description": "ユーザーのフルネーム"},
"role": {"type": "string", "description": "ユーザーの職務"},
"department": {"type": "string", "description": "ユーザーの部署(オプション)"},
"years_experience": {"type": "integer", "description": "経験年数(オプション)"}
},
execute_fn=create_user_profile
)
server.add_resource(user_profiles)
server.add_tool(create_user)
logger.info("MCPサーバーを起動しています...")
server.start()
except Exception as e:
logger.error(f"MCPサーバーの起動に失敗しました: {e}")
raise
6. エラー処理と検証
検証ロジックを集中化するためにsrc/utils/validation.py
ファイルを作成します:
from typing import Dict, Any, List, Optional, Type
from pydantic import BaseModel, ValidationError
import logging
logger = logging.getLogger("mcp_server.validation")
def validate_request(
data: Dict[str, Any],
model_class: Type[BaseModel]
) -> tuple[Optional[BaseModel], Optional[Dict[str, Any]]]:
"""
リクエストデータをPydanticモデルに対して検証します。
Args:
data: 検証する入力データ
model_class: 検証に使用するPydanticモデルクラス
Returns:
tuple: (validated_model, error_dict)
- 有効な場合: (モデルインスタンス, None)
- 無効な場合: (None, エラー辞書)
"""
try:
validated_data = model_class(**data)
return validated_data, None
except ValidationError as e:
errors = e.errors()
error_dict = {
"status": "error",
"message": "検証に失敗しました",
"errors": errors
}
logger.error(f"検証エラー: {errors}")
return None, error_dict
この汎用関数は、すべてのツールで入力データを一貫して検証するために使用できます。
7. MCPサーバーの実行とテスト
サーバーが正常に動作することを確認するための簡単なテストスクリプトtest_server.py
を作成します:
import requests
import json
import time
import subprocess
import sys
from pathlib import Path
def test_server():
"""MCPサーバーが正しく実行されていることを確認するための簡単なテスト。"""
# 別のプロセスでサーバーを起動
server_process = subprocess.Popen([sys.executable, "src/server.py"])
try:
# サーバーが起動するのを待つ
time.sleep(2)
# MCPクライアントを使用してサーバーをテスト
# 実際のテストでは、MCP Client SDKを使用します
# この例では、HTTPリクエストを使用してクライアントをシミュレートします
# サーバーがlocalhost:8000で実行されていると仮定
base_url = "http://localhost:8000"
# リソースの取得をテスト
response = requests.get(f"{base_url}/resources/user_profiles")
assert response.status_code == 200
data = response.json()
print("リソースレスポンス:", json.dumps(data, indent=2))
# ツールの実行をテスト
tool_data = {
"name": "Test User",
"role": "Tester",
"department": "QA"
}
response = requests.post(
f"{base_url}/tools/create_user_profile",
json=tool_data
)
assert response.status_code == 200
data = response.json()
print("ツールレスポンス:", json.dumps(data, indent=2))
print("すべてのテストに合格しました!")
finally:
# クリーンアップ: サーバープロセスを終了
server_process.terminate()
server_process.wait()
if __name__ == "__main__":
test_server()
サーバーを実行します:
python src/server.py
サーバーが実行されているとき、別のターミナルでは次のような出力が表示されるかもしれません:
2025-04-28 10:15:23 - mcp_server - INFO - MCPサーバーを起動しています...
2025-04-28 10:15:23 - mcp_server - INFO - サーバーが0.0.0.0:8000でリッスンしています
2025-04-28 10:15:30 - mcp_server.resources - INFO - 3件のユーザープロファイルの取得に成功しました
2025-04-28 10:15:35 - mcp_server.tools - INFO - 新しいユーザーを作成: Test User - Tester in QA
次に、サーバーURLまたはサーバーを起動するコマンドを提供することで、MCPクライアント(Claude Desktopなど)をローカルMCPサーバーに接続するように設定します。
セキュリティに関する考慮事項
MCPサーバーをデプロイする際は、次のセキュリティのベストプラクティスを検討してください:
- 認証: APIキーやOAuthを実装してクライアントを認証します。
def authenticate_request(request):
api_key = request.headers.get("X-API-Key")
if not api_key or api_key != os.environ.get("MCP_API_KEY"):
raise ValueError("無効なAPIキー")
- 入力検証: Pydanticモデルを使用してすべての入力を常に検証します。
- レート制限: 乱用を防ぐためにレート制限を実装します。
- HTTPS: 本番環境では常にHTTPSを使用します。
- 制限されたアクション: ツールができることの明確な境界を定義します。
パフォーマンス最適化
- キャッシング: 高コストのリソース取得をキャッシュします:
from functools import lru_cache
@lru_cache(maxsize=128, ttl=300) # 5分間キャッシュ
def fetch_user_profiles():
# 高コストのデータベースクエリ
pass
- 非同期処理: I/Oバウンドの操作には非同期を使用します:
async def fetch_user_profiles():
async with aiohttp.ClientSession() as session:
async with session.get("https://api.example.com/users") as response:
data = await response.json()
return data
- 接続プーリング: データベースアクセスには接続プールを使用します。
デプロイメント
ローカル開発
ローカル開発では、次のように実行します:
python src/server.py
Dockerデプロイメント
Dockerfile
を作成します:
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "src/server.py"]
ビルドして実行:
docker build -t mcp-server .
docker run -p 8000:8000 mcp-server
クラウドデプロイメント(AWS)
- EC2インスタンスを作成するか、AWS App Runnerを使用
- Dockerコンテナをデプロイ
- Application Load Balancerをセットアップ
- セキュリティグループを設定してアクセスを制限
MCPサーバーのテスト
テストファイルtests/test_resources.py
を作成します:
import pytest
from src.resources.user_profiles import fetch_user_profiles
def test_fetch_user_profiles():
"""ユーザープロファイルが正常に取得できることをテストします。"""
profiles = fetch_user_profiles()
# 構造を確認
assert isinstance(profiles, list)
assert len(profiles) > 0
# コンテンツを確認
first_profile = profiles[0]
assert "name" in first_profile
assert "role" in first_profile
assert isinstance(first_profile["name"], str)
テストを実行:
pytest
一般的なエラーとトラブルシューティング
問題 解決策 例 MCPサーバーに接続できない サーバーが実行中でポートが正しいことを確認 netstat -tulpn | grep 8000
LLMがリソースを見つけられない name
とdescription
フィールドが適切に設定されているか確認 リソースの初期化を確認 ツール実行中のエラー 入力パラメータが期待される型と一致するか検証 検証にPydanticを使用 クライアントが出力を解析できない 関数がJSON直列化可能なデータを返すことを確認 カスタムオブジェクトの代わりに.model_dump()
を使用 起動時にサーバーがクラッシュする インポートと環境変数を確認 詳細なログのためにDEBUG=True
を設定 ツールのタイムアウト 外部API呼び出しのタイムアウト処理を追加 タイムアウト付きのasyncio.wait_for()
を使用 認証の失敗 APIキーと権限を確認 リクエストヘッダーを確認 XML/JSON解析エラー 適切なコンテンツタイプヘッダーを使用 Content-Type: application/json
を設定
次のステップ
基本的なMCPサーバーを構築した後、次の高度な拡張を検討してください:
- データベース統合: PostgreSQL、MongoDB、その他のデータベースに接続。
- ファイル操作: ファイルの読み取り、書き込み、変換のためのツールを追加。
- 外部API: GitHub、Slack、Google Driveなどの人気サービスと統合。
- Webhook: LLMが他のシステムでイベントをトリガーできるようにする。
- ストリーミングリソース: 大規模なデータセットのストリーミングをサポート。
- コンテキスト対応アクション: LLMの現在のコンテキストを理解するツールを追加。
例:データベース接続の追加
import psycopg2
from contextlib import contextmanager
@contextmanager
def get_db_connection():
"""データベース接続コンテキストマネージャーを作成します。"""
conn = None
try:
conn = psycopg2.connect(
host=os.environ.get("DB_HOST"),
database=os.environ.get("DB_NAME"),
user=os.environ.get("DB_USER"),
password=os.environ.get("DB_PASSWORD")
)
yield conn
finally:
if conn is not None:
conn.close()
def fetch_user_profiles_from_db():
"""PostgreSQLデータベースからユーザープロファイルを取得します。"""
with get_db_connection() as conn:
with conn.cursor() as cur:
cur.execute("SELECT name, role, department FROM users")
columns = [desc[0] for desc in cur.description]
return [dict(zip(columns, row)) for row in cur.fetchall()]
結論
Pythonで簡単なMCPサーバーを構築することは、LLMをより強力にするための扉を開きます。標準化されたリソースとツールを通じてデータとアクションを公開することで、AIシステムが外部サービスと安全かつ有意義に対話することが容易になります。
小さく始め、1つのリソースと1つのツールに焦点を当て、時間をかけてデータベース、クラウドストレージ、内部APIなどのより高度なユースケースに拡張することができます。
MCPエコシステムは急速に成長しており、今これらの標準を実装することで、プロトコルとそれを使用するLLMの両方の改善からアプリケーションが恩恵を受ける態勢が整います。
よくある質問
Pythonの経験が多少必要です。MCPサーバーはリソースとツールの正確な定義が必要なソフトウェアプロセスです。
はい。AnthropicとコントリビューターがPythonやTypeScriptなど複数の言語用のSDKをリリースしています。
はい。MCPサーバーをクラウドプラットフォームにホスティングし、ファイアウォールの背後に配置し、LLMクライアントに安全に利用可能にすることができます。