🐘

MCPサーバーを立ててAIがPostgreSQLを参照できるようにしてみた話

に公開

はじめに

こんにちは。マーケティングオートメーションツール「ListFinder」開発チームのはちです。
普段はデータ加工や外部システムとのデータ連携処理などをGo言語で書いています。

弊社では生成AIの活用を推進しており、AIエージェントの Devin や AIエディタの Cursor 、 AIレビュアーの CodeRabbit などを利用しています。

この記事は、データベースを使うプログラムを書くことが多い私が開発効率向上のためにgithub copilotがコンテナ上のPostgreSQLを直接参照できるようにした話です。

実行環境

この記事の内容は以下の環境で行なったものです。構成の違いによって違う結果になる可能性があります

要約

  • 公式SDKを使って簡単なプログラムを書くことでVSCodeからgithub copilot chatがコンテナ上のPostgreSQLにアクセスできるようになった
  • AIがテーブル構造を理解した上でコードを生成できるようになったのでデータベース周りの実装が楽になった
  • 作成したMCPサーバーのソースコードを公開します
  • 公式Go言語SDKを使ったローカルMCPサーバー開発の流れ
  • VSCodeでローカルMCPサーバーを使う設定の流れ

やろうと思ったきっかけ

GoでORMを使う場合はテーブル構造に沿った構造体型を定義する必要があります。
ListFinderではデータベースのマイグレーションはWebサービスに使用するLaravelで行うので、Go側はマイグレーション結果を見て実装する必要があります。
テーブルに変更を反映する必要が出てきた際など とても面倒くさい(※怠惰はプログラマーの3大美徳の一つです)
github copilotを導入した結果エディタ上でAIの支援を受けられるようになりましたが、データベース定義はAIの手が届かないところにあるので手作業が必要でした。

MCPサーバーを使ってAIがデータベースにアクセスできたら楽になるかと考えチャレンジしてみました。
効率化のた目の面倒くさいことは積極的に行いましょう

最初は公式の リファレンスサーバーを使って実現した という内容を予定していたのですが、気づいたらアーカイブされていたので 自分で実装してみた という内容に変更しました。

参考にしたWebサイト

作成したソースコード

作成したソースコードはこちらです。

作成したMCPサーバーのソースコード
package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"time"

	"github.com/jackc/pgx/v5"
	"github.com/modelcontextprotocol/go-sdk/mcp"
)

// DSN パラメータで受け取る
var (
	dsn string
)

// Input MCPクライアントから受け取るパラメータ構造体
type Input struct {
	Query string `json:"query" jsonschema:"実行するSQLクエリ"`
}

// Output MCPクライアントに返すパラメータ構造体
type Output struct {
	QueryResult string `json:"query_result" jsonschema:"SQLクエリの結果(JSON形式)"`
}

// runQuery クエリを実行し、結果をJSON形式で返すハンドラ
func runQuery(ctx context.Context, request *mcp.CallToolRequest, input *Input) (*mcp.CallToolResult, *Output, error) {
	// クエリが空の場合はエラーメッセージを返す
	if input.Query == "" {
		return nil, &Output{QueryResult: "No query provided"}, nil
	}

	// タイムアウト設定
	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
	defer cancel()

	// PostgreSQLに接続
	// 都度接続するので、トランザクションは使えない
	db, err := pgx.Connect(ctx, dsn)
	if err != nil {
		log.Fatalf("Unable to connect to database: %v\n", err)
	}
	defer db.Close(ctx)

	// クエリを実行
	rows, err := db.Query(ctx, input.Query)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to execute query: %w", err)
	}
	defer rows.Close()

	// フィールド情報を取得
	// 結果が同じ場合、毎回呼び出すのは非効率なので変数に代入
	cols := rows.FieldDescriptions()

	// 結果をマップのスライスとして収集
	var results []map[string]string
	for rows.Next() {
		row := make(map[string]string, len(cols))
		vals, err := rows.Values()
		if err != nil {
			return nil, nil, fmt.Errorf("failed to scan row: %w", err)
		}
		for i, c := range cols {
			// 型がバラバラなのでstringに変換する
			row[c.Name] = fmt.Sprint(vals[i])
		}
		results = append(results, row)
	}

	// 結果をJSONに変換して返す
	jr, err := json.Marshal(results)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to marshal results: %w", err)
	}

	return nil, &Output{QueryResult: string(jr)}, nil
}

func main() {
	// DSN(接続URL)をコマンドライン引数から取得
	flag.StringVar(&dsn, "dsn", "postgresql://username:password@hostname:5432/database", "DSN to database")
	flag.Parse()

	ctx := context.Background()

	// サーバーの初期化
	server := mcp.NewServer(&mcp.Implementation{Name: "PostgreSQL", Version: "v1.0.0"}, nil)

	// ツールの登録
	mcp.AddTool(server, &mcp.Tool{Name: "RunQuery", Description: "run query"}, runQuery)

	// サーバーの起動
	if err := server.Run(ctx, &mcp.StdioTransport{}); err != nil {
		log.Fatalf("Failed to run server: %v\n", err)
	}
}

実装のポイント

ほぼ公式のサンプル通りです。
今回は標準入力を介してパラメータを受け取ります。
頻繁に更新されるのでタグを指定してパッケージの取得を行うと安全です。
このソースコードでは v0.5.0 を使用しているので、次のコマンドを実行します。

$ go get -u github.com/modelcontextprotocol/go-sdk@v0.5.0

動作確認方法

MCP公式のinspector を使用するとMCPクライアントなしでもMCPサーバーの動作確認ができます。
npx コマンドをインストールした状態で次のコマンドを実行します。

$ npx @modelcontextprotocol/inspector

コマンドを実行するとURLが表示されるので、ブラウザで開くとこんな画面が表示されます。

inspector起動時の画面

CommandArguments を入力して Connect ボタンをクリックすると右側にツールリストが表示されます。
go run main.go がなぜか動かなかったので、ビルドして実行ファイルを指定しました。

inspector Connect後

List Tools をクリックすると登録されているツールの一覧が表示され、MCPサーバーへリクエストを送信することができます。

このように incpector から自作MCPサーバーを通してSQLを実行することができました。

inspector リクエスト送信後

どうやら思惑通りに動作しているようです。
MCPサーバーの実装は以上です。

VSCodeで自作MCPサーバーを使う設定

VSCodeのワークスペースに .vscode/mcp.json ファイルを作成します。
スクリーンショットを貼り付けたら見づらくなったので表示は省略します。

mcp.json

ファイルの内容を空のjson

{}

に変更すると、エディタの下に サーバーの追加 ボタンが現れるのでクリックします。

サーバーの追加

今回は コマンド(stdio) ... 手動インストール を選択します。

手動インストール

inspector で指定した実行モジュールとパラメータを入力してEnter。
サーバーIDは考えなくても自動的につけてくれます。名前をつけてEnter。

コマンド指定

もちろん 信頼する をクリック。

信頼する

.vscode/mcp.json にこんな内容が保存されます。
サーバーIDの上に 起動 と薄く表示されるのでクリックするとMCPサーバーが起動します。

MCPサーバー起動

{
	"servers": {
		"my-mcp-server-82d0e78e": {
			"type": "stdio",
			"command": "./mcp",
			"args": [
				"-dsn",
				"postgresql://xxxxx:xxxxx@localhost:5432/dbname"
			]
		}
	},
	"inputs": []
}

Copilotの入力欄の ツールの構成 ボタンをクリックしてCopilotが使用するMCPサーバーを選択します。

ツールの構成

これでCopilotからMCPサーバーを使う準備ができました。

試しに、適当なエンティティ構造体を作成してCopilotに構造体定義の妥当性を検証してもらいます。

使ってみる

動きました!
これでモデル構造体の記述はGithub Copilot Chat Agentに任せられるようになりました。

まとめ

公式ドキュメント記載のサンプルコードに独自のハンドラを追加した程度でMCPサーバーが動きました。
簡単に実装できるので夢が広がりますね。
SDKも日々改善が行われておりどんどん使いやすくなっています(たまに壊れていますが)。
皆さんもオリジナルMCPサーバーを作って快適なAIコーディングライフ、快適なGoライフをお送りください。

株式会社イノベーション Tech Blog

Discussion