Yappli Tech Blog

株式会社ヤプリの開発メンバーによるブログです。最新の技術情報からチーム・働き方に関するテーマまで、日々の熱い想いを持って発信していきます。

Go StudyでMCPサーバーを実装しました

はじめに

こんにちは、 夏バテ対策で夏野菜カレー作るために猛暑の中野菜を買いに行き、玉ねぎに目を潰されながら大量の野菜を切って大変な思いして作ったもののカレー自体重いのでグロッキーになりバテ対策になったかは謎だったサーバーサイドエンジニアの西村です。

弊社では技術顧問のtenntennさんを招いてGo StudyというGoの勉強会を月1で1時開催しています。

tenntennさんに解説してもらったり、実際に手を動かしたり、わいわいスレでみんなでわいわい(質問・感想投げ)しながら実施しています。

標準packageを読んだり、Goで新しく使えるようになった機能について学んだりとテーマはその時によりますが、(2025年)4、5、6月はみなさんの興味関心のあるLLM・MCP関連のものを題材にしました。

今回は4月に取り組んだ内容「MCPサーバー編」です。

Goのmark3labs/mcp-goでMCPサーバーを作り、Claude Codeで使えるようにします。

MCPとは

MCPとは、アプリケーションが大規模言語モデル(LLM)にコンテキストを提供する方法を標準化するオープンプロトコルで、下記のような特徴があります。

  • Anthropic社が2024年11月に公開したプロトコル
    • LLMに外部情報(コンテキスト)を提供するプロトコル
      • これまでのtool useやfunction callingを標準化したもの
    • JSON-RPC 2.0をベースにしている

また、MCPサーバーが提供する機能としては下記があります。

  • リソース(Resources)
    • データやコンテキスト情報をLLMに提供することできる
  • プロンプト(Prompts)
    • プロンプトテンプレートを提供
      • 穴埋めになってるようなテンプレートを用意し、それを使ってユーザーからLLMにプロンプトを流してもらうみたいなことに使える
    • ツール(Tools)
      • LLMから利用できるツール操作や関数呼び出しを提供

安全なのか?

ただのJSON-RPCのサーバーなので安全ではないです。 具体的にどのようなリスクがあるかというと下記があります。

  • プロンプトインジェクション
  • アカウントの乗っ取りなど
  • 任意のコマンドの実行

基本的には自己責任で、下記に注意しての使用をおすすめします。

  • 信用できるものだけを使う
  • 不用意にアカウント情報などを渡さない
  • 安全対策も一緒に標準化されていくことを期待
  • 社内で使う場合はルールを決める
  • githubなどサービスをhostingしている会社が提供しているMCPを選定すると無難

GoでのMCPサーバーの作り方

モジュール選定

下記のようにいくつかモジュールがあり、MCP公式のSDKもGoogleのGoチームとAnthropicが協力して進められていますが、今回は一番利用されてるmark3labsのモジュールを使ってMCPサーバーを作ります。

サーバーの作成

ディレクトリとmodの作成

    mkdir mcpserver
    cd mcpserver
    go mod init exmample.com/example // go.mod作成

main.goの作成

ここからはコードの説明ですが、最後に全体のコードがあるので、それをコピペして読んでいくのもおすすめです。

ひとまずmain関数とcontextを受け取ってerrorを返すrun関数を実装し、それをmain関数の中で実行します。

    package main
    
    import (
      "context"
      "fmt"
      "log"
      "os"
    )
    
    func main() {
      log.Println("Starting...")
      if err := run(context.Background()); err != nil {
          fmt.Fprintln(os.Stderr, "Error:", err)
      }
    }
    
    func run(ctx context.Context) error {
      return nil
    }
    

run関数の中に下記を実装してMCPサーバーを作成する

※この時点ではまだ動かない

  import   "github.com/mark3labs/mcp-go/server"
    
  s := server.NewMCPServer(
          "Echo MCP",
          "1.0.0",
      )

toolを作る

上記の実装の直下、return nilの前に下記を実装します。

※この時点ではまだ紐づけてないので動かない

    tool := mcp.NewTool("echo", // ツールの名前
      mcp.WithDescription("このツールは与えられたメッセージを指定された指定回数繰り返します"),
      mcp.WithNumber("count", // LLMから渡されるパラメータ
          mcp.Required(), // 必須化どうか
          mcp.Description("count of echo"), // パラメータの説明
      ),
      mcp.WithString("message", // LLMから渡されるパラメータ
          mcp.Required(), // 必須化どうか
          mcp.Description("message"), // パラメータの説明
      ),
    )

上記のtoolを、作ったサーバーに紐付ける

    f := func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
      // パラメータの取得
          args := request.Params.Arguments
        if args == nil || len(args) == 0 {
            return nil, errors.New("invalid arguments")
        }         message := args["message"].(string)
          count := args["count"].(float64)
         
      // 結果を返す
      return mcp.NewToolResultText(strings.Repeat(message, int(count))), nil
    }
    
    // ツールに関数を設定
    s.AddTool(tool, f)
     

サーバーの起動

トランスポートは下記のように2種類あります。

※トランスポート ・・・ MCPクライアント ↔ MCPサーバー間の通信手段

①標準入出力を使う方法   ←今回はこっちを使います

    ・ NewMCPServer関数で作成したサーバの起動を行う
    ・ 標準入出力を用いてLLMとやり取りをする
    ・ 今回はmcp-go/server.ServeStdioを使う

②httpを使う方法

    ・ NewSSEServer関数を用いた場合はそのままHTTPハンドラになる
    ・ SSE(Server-Sent Event)でやり取りをする

コード的には下記をs.AddToolとreturn nilの間に実装します。

        err := server.ServeStdio(s)
        if err != nil && !errors.Is(err, context.Canceled) {
          return fmt.Errorf("failed to start server: %w", err)
        }
        

ここまでできたら go buildしてみる

全体のコード

    package main
    
    import (
      "context"
      "errors"
      "fmt"
      "log"
      "os"
      "strings"
    
      "github.com/mark3labs/mcp-go/mcp"
      "github.com/mark3labs/mcp-go/server"
    )
    
    func main() {
      log.Println("Starting...")
      if err := run(context.Background()); err != nil {
          fmt.Fprintln(os.Stderr, "Error:", err)
      }
    }
    
    func run(ctx context.Context) error {
      s := server.NewMCPServer(
          "Echo MCP",
          "1.0.0",
      )
      tool := mcp.NewTool("echo", // ツールの名前
          mcp.WithDescription("echo tool"),
          mcp.WithNumber("count", // LLMから渡されるパラメータ
              mcp.Required(),                   // 必須化どうか
              mcp.Description("count of echo"), // パラメータの説明
          ),
          mcp.WithString("message", // LLMから渡されるパラメータ
              mcp.Required(),             // 必須化どうか
              mcp.Description("message"), // パラメータの説明
          ),
      )
    
      f := func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
          // パラメータの取得
          args := request.Params.Arguments
        if args == nil || len(args) == 0 {
            return nil, errors.New("invalid arguments")
        }
          message := args["message"].(string)
          count := args["count"].(float64)
          // 結果を返す
          return mcp.NewToolResultText(strings.Repeat(message, int(count))), nil
      }
    
      s.AddTool(tool, f)
      //サーバーの起動
      err := server.ServeStdio(s)
      if err != nil && !errors.Is(err, context.Canceled) {
          return fmt.Errorf("failed to start server: %w", err)
      }
    
      return nil
    }
    

Claude Codeへ導入

※勉強会当時はclineで導入していたけど、鮮度落ちると思うのでClaude Codeへの導入法を記載します

go buildしてコンパイル

今回はmain.goをecho-mcpと言うバイナリファイルにコンパイルしたかったので下記コマンドでbuildします。

            go build -o echo-mcp main.go
mcp add で導入

コンパイルしてできたバイナリファイルへのpathとともにmcp add して導入。

         claude mcp add echo-mcp /Users/xxxx/gitrepo/mcpserver/echo-mcp
Claude Codeで確認

Claude Codeを再起動して /mcp で確認すると入ってます!

実装的に、渡された文字列を指定回数繰り返すMCPなので、「もに」を6回繰り返してもらいます・・・。

🟢のところにありますがecho-mcpがもにを6回繰り返してくれました!

最後に

こんな感じでわいわい手を動かしながらGoやその時気になった話題を元に勉強会を開いてます。

また、弊社ではこちらの記事にあるように、AI委員会があり、気になったAI情報を交換したり、いち早くツールを導入するために動いてくださったりするので、早い段階で会社でAIツール使えるようになってとても助かります。

そんな弊社が気になった方はぜひカジュアル面談にご応募お願いいたします!

open.talentio.com