MCPサーバーのPython実装:エラーハンドリングの設計と実装 Link to heading
はじめに Link to heading
前回の記事で基本的な動作を確認したMCPサーバーに、エラーハンドリング機能を追加しました。この記事では、エラーハンドリングの設計と実装について説明します。
エラーハンドリングの必要性 Link to heading
MCPサーバーでは、以下のような様々なエラーが発生する可能性があります:
リクエスト関連のエラー
- 不正なJSONフォーマット
- 必須フィールドの欠落
- 不正なメソッド名
- 不正なパラメータ型
サーバー内部のエラー
- 処理中の例外
- リソースの不足
- タイムアウト
プロトコル関連のエラー
- 未実装のメソッド
- 不正なプロトコルバージョン
- 互換性の問題
エラーレスポンスの実装 Link to heading
errors.py
でエラー関連のクラスを定義しました:
1class ErrorCode(IntEnum):
2 # リクエスト関連のエラー (-32000 から -32099)
3 INVALID_REQUEST = -32000
4 METHOD_NOT_FOUND = -32001
5 INVALID_PARAMS = -32002
6
7 # サーバー内部のエラー (-32100 から -32199)
8 INTERNAL_ERROR = -32100
9 RESOURCE_EXHAUSTED = -32101
10 TIMEOUT = -32102
11
12 # プロトコル関連のエラー (-32200 から -32299)
13 PROTOCOL_ERROR = -32200
14 VERSION_MISMATCH = -32201
15
16class ErrorInfo(BaseModel):
17 code: int
18 message: str
19 data: Optional[Dict[str, Any]] = None
20
21class ErrorResponse(BaseModel):
22 error: ErrorInfo
23 id: Optional[str] = None
エラーハンドリングの実装 Link to heading
カスタム例外クラス
1class MCPError(Exception): 2 def __init__(self, code: ErrorCode, message: str, data: Optional[Dict] = None): 3 self.code = code 4 self.message = message 5 self.data = data 6 7class RequestError(MCPError): 8 pass 9 10class ServerError(MCPError): 11 pass 12 13class ProtocolError(MCPError): 14 pass
グローバルエラーハンドラ
1@app.exception_handler(MCPError) 2async def mcp_exception_handler(request: Request, exc: MCPError) -> JSONResponse: 3 return JSONResponse( 4 status_code=200, 5 content=ErrorResponse( 6 error=ErrorInfo( 7 code=exc.code, 8 message=exc.message, 9 data=exc.data 10 ) 11 ).model_dump() 12 )
テストの実装 Link to heading
test_error_handling.py
で以下のテストケースを実装しました:
不正なJSONフォーマット
1def test_invalid_json(): 2 response = client.post("/", content=b"invalid json") 3 assert response.status_code == 200 4 data = response.json() 5 assert data["error"]["code"] == -32000 6 assert "Invalid JSON format" in data["error"]["message"]
メソッドの欠落
1def test_missing_method(): 2 response = client.post("/", json={}) 3 assert response.status_code == 200 4 data = response.json() 5 assert data["error"]["code"] == -32000 6 assert "Method is required" in data["error"]["message"]
不正なメソッド
1def test_invalid_method(): 2 response = client.post("/", json={ 3 "method": "nonexistent", 4 "params": {} 5 }) 6 assert response.status_code == 200 7 data = response.json() 8 assert data["error"]["code"] == -32001 9 assert "Method 'nonexistent' not found" in data["error"]["message"]
不正な初期化パラメータ
1def test_invalid_initialize_params(): 2 # capabilitiesが辞書でない場合 3 response = client.post("/", json={ 4 "method": "initialize", 5 "params": { 6 "capabilities": "not a dict", 7 "clientInfo": { 8 "name": "Test Client", 9 "version": "1.0.0" 10 } 11 } 12 }) 13 assert response.status_code == 200 14 data = response.json() 15 assert data["error"]["code"] == -32002 16 assert "Client capabilities must be a dictionary" in data["error"]["message"]
GETリクエストの処理
1def test_get_request(): 2 response = client.get("/") 3 assert response.status_code == 200 4 data = response.json() 5 assert data["error"]["code"] == -32000 6 assert "This server only accepts POST requests" in data["error"]["message"]
実装のポイント Link to heading
エラーの階層化
- 基本エラー(
MCPError
) - リクエストエラー(
RequestError
) - サーバーエラー(
ServerError
) - プロトコルエラー(
ProtocolError
)
- 基本エラー(
エラーレスポンスの標準化
- MCPプロトコルに準拠した形式
- 一貫したエラーコード体系
- 詳細なエラーメッセージ
テストの網羅性
- 各エラーケースのテスト
- エラーレスポンスの形式確認
- エンドツーエンドのテスト
次のステップ Link to heading
ログ機能の実装
- エラーログの構造化
- デバッグ情報の付加
- ログレベルの設定
エラーハンドリングの拡張
- より詳細なエラー情報
- エラーの追跡機能
- エラー統計の収集
まとめ Link to heading
エラーハンドリングの実装では、以下の点に注意を払いました:
- MCPプロトコルに準拠したエラーレスポンス
- 階層化されたエラー処理
- 網羅的なテストケース
- 拡張可能な設計
次の記事では、ログ機能の実装について説明します。
注記 本記事はエラーハンドリングの設計と実装にフォーカスしていますが、現状の実装では logger.py(ログ機能)も既に存在します。 ログ機能の詳細については、次回以降の記事で順次解説していきます。