Recce MCPサーバー開発
アーキテクチャ
RecceMCPServer は、MCP SDK Server を介して list_tools/call_tool ハンドラーを登録します。call_tool は _tool_* メソッドにディスパッチし、エラーを分類し、ログ/メトリクスを出力し、再スローします。
エントリポイント run_mcp_server() は、single_env をポップしてから、残りの kwargs を load_context() に渡します。
主要パターン
エラー分類 — 共有インジケーターリストは recce/tasks/rowcount.py で定義されています。優先順位(PERMISSION_DENIED > TABLE_NOT_FOUND > SYNTAX_ERROR)は、mcp_server.py の _classify_db_error() と rowcount.py の _query_row_count() によって適用されます。分類されたエラーは logger.warning() + sentry_metrics.count()(sentry_sdk が利用可能な場合)を出力します。未分類のエラーは logger.error() + トレースバックを出力します。
MCP SDK の癖 — ハンドラーは raise しなければならず、SDK が isError=True を設定します。
レスポンス契約 — CLAUDE.md を参照。追加の _meta のみ許可。summary.py では dict.get(key, 0) ではなく is None でガードします。N/A 表示には理由を含めます: "N/A (table_not_found)"。
シングル環境 — _maybe_add_single_env_warning() が差分結果に _warning を追加します。説明に条件付きの注意書きが付きます。
テスト(3層)
| 層 | ファイル | データソース | 実行場所 | 目的 |
|---|---|---|---|---|
| ユニット | tests/test_mcp_server.py | Mock RecceContext | CI (pytest) | ロジックの正確性 — ツールハンドラー、エラー分類、レスポンス形式 |
| 統合 | tests/test_mcp_e2e.py | DbtTestHelper + DuckDB(固定データ) | CI (pytest) | MCPプロトコルが anyio メモリストリームを介してエンドツーエンドで動作すること |
| スモーク(E2E) | /recce-mcp-e2e スキル | ユーザーの実際の dbt プロジェクト + 実際のデータベース | 手動 | 8つのツールすべてが実際のデータに対して有効な結果を返すこと |
新しいMCP機能や動作変更は、3つの層すべてでカバーされるべきです。
テストカバレッジギャップ分析
MCP変更のラウンド(定義は下記のE2Eゲートを参照)を完了した後、E2E検証について質問する前に、3層にわたるテストカバレッジの欠落を積極的にスキャンしてください。
確認方法:
- 何が変更されたか特定する — 新しいツールハンドラー?新しいエラーパス?新しいレスポンスフィールド?
- 各変更について、各層でカバレッジが存在することを確認する:
- ユニット:
tests/test_mcp_server.pyに新しい動作のテストケースがありますか?(正常系 + エラー系) - 統合:
tests/test_mcp_e2e.pyがMCPプロトコルを介して新しいツール/機能を実行していますか? - スモーク:
/recce-mcp-e2eテンプレートが新しいツールをカバーしていますか?(新しいツールが追加された場合、テンプレートの更新が必要かもしれません)
- ユニット:
ギャップが見つかった場合、E2Eゲートプロンプトの前にユーザーに報告してください:
テストカバレッジのギャップが見つかりました:
- ユニット: テーブルが見つからない場合の
_tool_fooエラーパスのテストが不足- 統合:
test_mcp_e2e.pyでfooツールを実行していない- スモーク:
/recce-mcp-e2eテンプレートにfooツールが含まれていないE2Eを実行する前にこれらのギャップを埋めますか?
スキャンしない場合: テストのみの変更、コメント/ドキュメント編集、インポートの並べ替え。
E2E検証ゲート
意味のあるMCP変更の各ラウンド後、必ずユーザーに質問してください:
このラウンドのMCP変更が完了しました。
/recce-mcp-e2eを実行して確認しますか?
ユーザーが「はい」と言った場合、/recce-mcp-e2e を呼び出します。このセッションで以前に dbt プロジェクトパスが使用されていた場合は、それを自動的に再利用します。それ以外の場合は尋ねます。
「ラウンド」とみなされるもの:
- ツールハンドラーの追加または変更 + そのユニットテストが通っていること
- エラー分類ロジックの変更 + テストが通っていること
- シングル環境またはレスポンス形式の変更 + テストが通っていること
質問しない場合: テストのみの変更、コメント/ドキュメント編集、インポートの並べ替え。
これは tests/test_mcp_e2e.py とは別です — そのファイルは CI で DbtTestHelper + DuckDB を使ってテストします。/recce-mcp-e2e は実際の dbt プロジェクトと実際のデータベースに対して8つのツールすべてを検証します。
落とし穴
sentry_sdkインポート: except 句には# pragma: no cover(CI では常に存在する)- Python 3.9:
Union[X, Y]でありX | Yではない - Pre-commit: black/isort が再フォーマットする可能性あり — 再ステージしてコミットすること
run.pyのschema_diff_should_be_approved()try/except は意図的(チェック作成を保証するため)
ファイルマップ
recce/mcp_server.py(サーバー + ハンドラー)、recce/tasks/rowcount.py(エラーインジケーター、RowCountStatus)、recce/run.py(CLIプリセット)、recce/summary.py(表示ロジック)、recce/event/__init__.py(Sentry)