はじめに こんにちは、エンジニア3年目のTKDSです! 最近 MCP が盛り上がってます。 流れに乗ってGoでやる方法を調べて試してみました! まず簡単に現在時刻を返す MCP サーバーを作ったあと、割と実用的に使えそうなファイルを連結して返す MCP サーバーを作っていきます。 今回書いたコードの リポジトリ です。 https://github.com/tkeshun/mcp はじめに MCPとは? 今回使用するライブラリ 現在時刻を返すMCPサーバー 1. プロジェクトの準備 2.コード作成 3. パッケージのダウンロードとビルド 4. 試す 特定のディレクトリ以下のファイルを返すMCPサーバー まとめ MCP とは? MCPのドキュメント によると MCP is an open protocol that standardizes how applications provide context to LLMs. 要するに、LLMが外部リソースとアクセスする約束事を決めてくれたみたいです。 MCP についての詳細はは他に詳しい記事がたくさんあると思うのでそちらに譲ります。 では早速つくっていきましょう! 今回使用するライブラリ 今回使用するライブラリは mcp -goです。 github mcp server で使われてたので採用しました。 go.mod https://github.com/mark3labs/mcp-go では、実装に移っていきます。 現在時刻を返す MCP サーバー 今回はサーバー経由で計算して結果を返すのでtoolsを使います。 toolsには、利用可能なツール一覧を表示する tools/list と実際に叩かれるtools/callが必要なようです。 Discovery: Clients can list available tools through the tools/list endpoint Invocation: Tools are called using the tools/call endpoint, where servers perform the requested operation and return results mcp -goでは mcp .NewTool関数を使えば作れそうです。 では実際に作ってみましょう。 Goでの準備方法書いてますが知ってる人は飛ばしてOKです。 1. プロジェクトの準備 mkdir mcp-time-server go mod init mcp-time-server 2.コード作成 以下のような感じです。 サンプルを参考に書きました。 AddToolsの定義見た感じ、処理の関数はserver.ToolHandlerFunc型を満たしていればよさそうです。 まず、NewMCPServerで MCP サーバーを作ります。 次に、 mcp .NewToolで登録するtoolの素を作ります。 そして、s.AddToolでtoolと処理を紐付けて登録します。 最後に標準出力を受け付けるようにサーバー起動します。 package main import ( "context" "fmt" "time" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) func main() { s := server.NewMCPServer( "時刻答える君" , "0.0.1" ) currentTimeTool := mcp.NewTool( "current_time" , mcp.WithDescription( "現在時刻を返します" ), ) s.AddTool(currentTimeTool, func (ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error ) { jst := time.FixedZone( "Asia/Tokyo" , 9 * 60 * 60 ) now := time.Now().In(jst) message := fmt.Sprintf( "現在の時刻: %s" , now.Format( "2006-01-02 15:04:05" )) return mcp.NewToolResultText(message), nil }) if err := server.ServeStdio(s); err != nil { fmt.Printf( "サーバーエラー: %v \n " , err) } } 3. パッケージのダウンロードとビルド MCP サーバーを起動したときのPathの関係とかがよくわからないので、今回はビルドして MCP クライアントを起動する場所におきました! go mod tidy go build -o current-time-server 4. 試す inspector を使います。 README.mdに載ってるconfig. json をもとに適当に書き換えました。 一回起動してみると、 everything が選択肢にあったのでおそらくそこがサーバー名です。なので、そこ以下の値を書き換えていきます。 項目は Github Copilot Chatの設定と変わらないので、特に迷うことはないと思います。 commandに起動するバイナリ名、argに引数、envに 環境変数 を書きます。項目名にちょっとDockerfileっぽさを感じます。 今回はcommandだけで大丈夫です。 { " mcpServers ": { " current-time ": { " command ": " current-time-server ", " args ": [] , " env ": { } } } } ファイルの配置はこんな感じです。 . - current-time-server(Goのバイナリ) | - config.json では起動します。 npx @modelcontextprotocol/inspector --config ./config.json --server current-time で起動できます。 ↓出力 $ npx @modelcontextprotocol/inspector --config ./config.json --server current-time Starting MCP inspector... ⚙️ Proxy server listening on port 6277 🔍 MCP Inspector is up and running at http://127.0.0.1:6274 🚀 起動するとこんな画面になります。 Connectを押して、List Toolsボタンを押すと、以下のような画面になります。 tools/listエンドポイントを叩いて、ツール一覧を取得してるようです。 表示されるメッセージは、 currentTimeTool := mcp.NewTool("current_time", mcp.WithDescription("現在時刻を返します"), ) に書いたやつなので、 mcp -goでは、 mcp .NewToolの宣言時に書いたものがlistで返される値になるようです。 current_timeを押すと、以下のようにRun Toolボタンが表示されます。 これを押すと mcp サーバーが実行されます。 無事現在時刻が表示されました!👏 以上が MCP サーバーの簡単な作り方でした。 特定の ディレクト リ以下のファイルを返す MCP サーバー AI Agent搭載エディターはファイル探してコンテキストに取り込んでくれたりしますよね? ただ、探す時間が長かったり、検討違いしてたりするケースがままあります。 そこで、最初からほしいコードを返してくれる MCP サーバーがあればいいのでは?と思い、作ろうとしました。 ソースコード は以下に載せておきます。 指定した ディレクト リのファイルを検索し、返してくれる処理を実装してます。 設定ファイルを外出しして、Goプログラムをいじらずにエンドポイントを作れるようにしました! package main import ( "context" "encoding/json" "fmt" "log/slog" "os" "path/filepath" "strings" "github.com/bmatcuk/doublestar/v4" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) type QueryConfig struct { Name string `json:"name"` // クエリ名 Description string `json:"description"` // MCP説明 Dir string `json:"dir"` // 環境変数ROOT_ENVからの相対パス PathPattern string `json:"path_pattern"` // パスパターン(glob) } func loadConfig(filename string ) ([]QueryConfig, error ) { file, err := os.ReadFile(filename) if err != nil { return nil , err } var cfg []QueryConfig err = json.Unmarshal(file, &cfg) return cfg, err } func concatFilesWithGlob(root, pattern string ) ( string , error ) { var builder strings.Builder matches, err := doublestar.Glob(os.DirFS(root), pattern) if err != nil { return "" , err } for _, match := range matches { fullPath := filepath.Join(root, match) data, err := os.ReadFile(fullPath) if err != nil { continue } builder.WriteString(fmt.Sprintf( "==== %s ==== \n " , match)) builder.Write(data) builder.WriteString( " \n\n " ) } return builder.String(), nil } func main() { // 環境変数取得 rootDir := os.Getenv( "ROOT_DIR" ) if rootDir == "" { panic ( "環境変数 ROOT_DIR が未設定です" ) } configPath := os.Getenv( "CONFIG_PATH" ) if configPath == "" { slog.Error( "CONFIG_PATHが未設定" ) } // MCPサーバー初期化 s := server.NewMCPServer( "Dynamic MCP Server" , "1.0.0" ) // 設定ファイル読み込み configs, err := loadConfig(configPath) if err != nil { slog.Error(fmt.Errorf( "設定ファイル読み込み失敗: %w" , err).Error()) } // 各ツールを登録 for _, cfg := range configs { tool := mcp.NewTool(cfg.Name, mcp.WithDescription(cfg.Description), ) localCfg := cfg // クロージャで固定 // rootDir + cfg.Dir に解決(再帰探索のベース) fullSearchRoot := filepath.Join(rootDir, localCfg.Dir) s.AddTool(tool, func (ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error ) { result, err := concatFilesWithGlob(fullSearchRoot, localCfg.PathPattern) if err != nil { return mcp.NewToolResultError(fmt.Sprintf( "ファイル探索エラー: %v" , err)), nil } return mcp.NewToolResultText(result), nil }) } // 起動 if err := server.ServeStdio(s); err != nil { fmt.Printf( "サーバーエラー: %v \n " , err) } } config. json は次のようになってます。 ROOT_DIRに対象にしたいプロジェクトのルート ディレクト リのパスを書きます。 GONFIG_PATHにはクエリや走査対象の ディレクト リを書きます ↓config. json { "mcpServers": { "current-time": { "command": "./current-time-server", "args": [], "env": { } }, "file-finder": { "command": "./mcp-file-finder", "args": [], "env": { "ROOT_DIR": "./test", "CONFIG_PATH": "./finder-config.json" } } } } GONFIG_PATHのほうにはエンドポイント名、LLMへの説明、走査対象 ディレクト リ、マッチさせるファイルパターンを書きます。 ↓finder-config. json [ { "name": "docs_query", "description": ".vscode以下の情報を取得します。vscodeなどの設定がほしい場合に使用してください", "dir": "./github-mcp-server/.vscode", "path_pattern": "**" }, { "name": "e2e_code_query", "description": "ソースコードファイルを読み込みます。E2Eテストのコードです。", "dir": "./github-mcp-server/e2e", "path_pattern": "**/*.go" } ] 実際に叩いてみると以下のようにファイル内容を取得できます。 まとめ 今回は MCP サーバーを2種類作ってみました! mcp -goは非常に簡単に MCP サーバーを作れ、Goバイナリにできるため、配布・使用が非常に簡単です。 2つ目のプログラムについては実用性も非常に高いと思います! 記事を見た皆さんもぜひ試してみてください!