非エンジニアのメンバーから「このデータ出して」と依頼が来るたびにSQLを書く。この繰り返しを解消するために、PostgreSQLに対して自然言語でクエリを投げられるMCPサーバーを構築しました。
課題:SQLを書けない人がデータにアクセスできない
業務データベースには案件の進捗、担当者の活動履歴、完了実績など日常的に参照が必要なデータがあります。しかしアクセスはSQLを書けるメンバーに集中します。よくある依頼パターンはこのようなものです。
- 「特定エリアの進捗状況を一覧で見たい」
- 「今週完了した案件を教えて」
- 「2週間以上動きのない案件を洗い出したい」
- 「ある担当者が処理した案件名と金額の一覧がほしい」
いずれも単純なSELECT文で済みますが、テーブル構造を知らないと書けません。
解決策:MCPサーバーでAIとDBをつなぐ
MCP(Model Context Protocol)は、AIアシスタントが外部ツールやデータソースと接続するためのプロトコルです。MCPサーバーを介することで、AIが自然言語をSQLに変換し、データベースから結果を取得して回答します。
アーキテクチャ
1ユーザー(自然言語で質問)2 ↓3Claude CLI4 ↓ MCP Protocol5MCPサーバー(SQL生成 + 実行)6 ↓ Read-Only接続7PostgreSQL(業務データベース)ポイントは3つあります。
- Claude CLIがユーザーインターフェース — 自然言語の入力をそのまま受け取ります
- MCPサーバーがブリッジ — スキーマ情報の提供とSQLの実行を担います
- PostgreSQLへの接続はRead-Only — 誤ったUPDATE/DELETEが発行されるリスクを排除します
仕様駆動開発で進めた
このMCPサーバーの開発は、以下のステージで段階的に進めました。
- ブレスト — ユースケースの洗い出し、どんな自然言語クエリが来るかの想定
- 要件定義 — 接続先DB、対応するクエリパターン、セキュリティ要件の明確化
- 設計 — MCPサーバーのツール定義、スキーマ情報の公開方法、エラーハンドリング
- テスト設計 — 自然言語→SQL変換の正確性、Read-Only制約の検証
- タスク分解 → 実装
各ステージでユーザー確認を入れることで、「作ったけど使えない」を防ぎました。特に要件定義で「実際に聞きたい質問」を列挙してもらったのが効果的でした。
自然言語クエリの実例
実際にテストした自然言語クエリと、それがどう処理されたかの例を挙げます。
| 自然言語の質問 | 処理内容 |
|---|---|
| 「エリアAの進捗を教えてください」 | 特定エリアでフィルタし、案件のステータスを一覧取得 |
| 「今週完了した案件を教えてください」 | 日付範囲 + ステータス条件でフィルタ |
| 「動きのない案件を教えて(2週間以上活動なし)」 | 最終更新日が14日以上前のレコードを抽出 |
| 「担当者Xが処理した案件名と金額を教えてください」 | 担当者名でJOINし、案件名と金額カラムを取得 |
MCPサーバーがスキーマ情報(テーブル名、カラム名、型、リレーション)を公開し、ClaudeがそれをもとにSQL文を生成・実行する仕組みです。
Read-Only接続にした理由と実装
Read-Onlyに制限した理由は明確です。
- 自然言語からのSQL生成は、意図しないUPDATEやDELETEを生成するリスクがゼロではありません
- 業務データベースに対する書き込み操作は、既存の業務フローを経由すべきです
- AIが「データを修正しておきました」と善意で書き換える事故を構造的に防ぎます
PostgreSQL側でRead-Only専用ユーザーを作成し、MCPサーバーはそのユーザーで接続します。
1CREATE USER mcp_readonly WITH PASSWORD '****';2GRANT CONNECT ON DATABASE business_db TO mcp_readonly;3GRANT USAGE ON SCHEMA public TO mcp_readonly;4GRANT SELECT ON ALL TABLES IN SCHEMA public TO mcp_readonly;5ALTER DEFAULT PRIVILEGES IN SCHEMA public6 GRANT SELECT ON TABLES TO mcp_readonly;MCPサーバー側でもSELECT文のみ許可するバリデーションを入れていますが、DB側の権限制約が最後の砦です。防御は多層にしておくのが基本です。
限界とエッジケース
万能ではありません。把握している限界を挙げます。
- 曖昧な質問への対応: 「最近の案件」がいつからを指すのか、コンテキストによって解釈が変わります。MCPサーバー側でデフォルト期間を定義するか、Claudeに確認を促させる必要があります
- 複雑なJOIN: 3テーブル以上のJOINが必要なクエリでは、スキーマ情報だけでは正しいリレーションを推測できないことがあります
- 集計クエリの精度: 「平均」「合計」などの集計は、GROUP BYの粒度が質問文から一意に決まらないケースがあります
- 日本語カラム名: スキーマが日本語カラム名の場合、自然言語との対応付けは比較的スムーズですが、英語カラム名の場合はスキーマのコメント情報が重要になります
今後の展開
現在はローカル環境で動作しています。今後の計画は以下の通りです。
- クラウドデプロイ: Google Cloud上にMCPサーバーを配置し、リモートアクセスを可能にする [要確認: 具体的なGCPサービス構成]
- ユーザー認証: 誰がどのクエリを実行したかのログ記録と、アクセス制御の追加
- Claude CLI → Claude API移行: 現在のCLI依存を脱し、APIベースにすることでWebインターフェースやSlack連携が可能になります
まとめ
MCPサーバーをPostgreSQLとClaude CLIの間に置くことで、自然言語でのデータベース検索が実現できました。非エンジニアが「今週完了した案件を教えて」と聞くだけでデータが返ってくる体験は、地味ですが業務効率への影響が大きいです。
Read-Only接続と仕様駆動開発の2点は、この種のAI連携ツールを作る上で再利用できる設計パターンだと考えています。