🚀

MCP の Go 公式 SDK が公開されたので早速使ってみた

に公開

はじめに

はじめまして、株式会社カナリー ソフトウェアエンジニアの柏です。
お部屋探しマーケットプレイス CANARY の、主にバックエンドを担当しています。

先日、ついに MCP(Model Context Protocol)の公式 Go SDK が公開されました!🎉

https://github.com/modelcontextprotocol/go-sdk

今までは Go で MCP サーバーを開発する際、mark3labs/mcp-gometoro-io/mcp-golangktr0731/go-mcp などの非公式ライブラリを使うか、自分で実装する必要がありました。
しかし、ついに公式 SDK が公開されたということで、試さずにはいられないと思い、早速自分の個人プロジェクトで導入してみました。

MCP の基礎や全体像についてはModel Context Protocol の公式サイトに記載がありますので、詳細はそちらをご参照ください。

本記事では、

  • 公式 SDK の設計思想や使い方の紹介
  • mark3labs/mcp-go との比較
  • 実際に使ってみた感想

という流れで、簡潔にまとめていきます。


MCP 公式 SDK の紹介

開発経緯と設計思想

MCP 公式 Go SDK は、GitHub Discussion #364での活発な議論を経て、Go チームの公式参加のもと開発が進められました。

設計目標としては、

  • 完全性(MCP 仕様の忠実な実装)
  • Go 言語らしさ(型安全性・Generics 活用)
  • 堅牢性(エラー処理)
  • 拡張性(機能追加・仕様変更への対応)

が掲げられており、実際の設計にもその思想が色濃く反映されています。

基本的な使い方

今回は個人的に愛用している、Scrapbox (Cosense) を生成 AI から参照したりページを作成したりできるようにするための MCP サーバーを作成しました。実際に実装で利用した例をもとに、基本的な使い方を紹介します。
なお、今回は Tools の機能のみを追加した MCP サーバーの作成手順を説明します。実装の詳細は https://github.com/takak2166/scrapbox-mcp をご参照ください。
また、以前Go Connectというイベントで ktr0731/go-mcp を使った実装についても紹介しているので興味がある方はこちらも併せてご参照ください 🙏
https://speakerdeck.com/takak2166/go-mcpdemcpsabawozuo-tutemita

go-sdk のインストール

go-sdk パッケージをプロジェクトに追加します。

go get github.com/modelcontextprotocol/go-sdk@latest

サーバーの実装

ここでは、標準入出力を介して通信するシンプルなサーバーを作成します。

package main

import (
    "context"
    "log"

    "github.com/modelcontextprotocol/go-sdk/mcp"
    "github.com/takak2166/scrapbox-mcp/internal/config"
    mcpServer "github.com/takak2166/scrapbox-mcp/internal/official-mcp"
    "github.com/takak2166/scrapbox-mcp/pkg/scrapbox"
)

func main() {
    cfg, err := config.LoadConfig()
    if err != nil {
        log.Fatalf("Failed to load config: %v", err)
    }

    client := scrapbox.NewClient(cfg.ProjectName, cfg.ScrapboxSID)
    server := mcpServer.NewServer(client)

    // MCP サーバーを標準入出力トランスポートで起動
    if err := server.GetServer().Run(context.Background(), mcp.NewStdioTransport()); err != nil {
        log.Fatalf("Server failed: %v", err)
    }
}

Tools の実装

次に、特定の処理を実行する関数として Tools を実装し、NewServer で Tools の登録がされるように実装します。

// パラメータ構造体の定義
 type GetPageParams struct {
     PageTitle string `json:"page_title" jsonschema:"required,description=Page title to retrieve"`
 }

// サーバーの作成とTools登録
func NewServer(client *scrapbox.Client) *Server {
    server := mcp.NewServer("Scrapbox MCP Server", "1.0.0", nil)

    s := &Server{
        client: client,
        mcpServer: server,
    }

    s.registerTools()
    return s
}

// Toolsの登録
func (s *Server) registerTools() {
    getPageTool := mcp.NewServerTool("get_page",
        "Get a Scrapbox page by title",
        s.handleGetPage,
	    mcp.Input(
		    mcp.Property("page_title", mcp.Description("Page title to retrieve")),
	    ),
    )
    // 他のTools...
    s.mcpServer.AddTools(
        getPageTool,
        // 他のTools...
    )
}

// ハンドラー関数
func (s *Server) handleGetPage(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParamsFor[GetPageParams]) (*mcp.CallToolResultFor[any], error) {
     // Scrapbox APIの呼び出し
    page, err := s.client.GetPage(ctx, params.Arguments.PageTitle)
    if err != nil {
        return nil, fmt.Errorf("Failed to get page: %w", err)
    }

    pageJSON, err := json.Marshal(page)
    if err != nil {
        return nil, fmt.Errorf("Failed to marshal page: %w", err)
    }

    return &mcp.CallToolResultFor[any]{
        Content: []mcp.Content{&mcp.TextContent{Text: string(pageJSON)}},
    }, nil
}

ここまでの実装で、get_pageという Tools を追加した MCP サーバーとして動作させることができます。
(今回は実際に作った MCP サーバーを動作させる手順は割愛します。詳細は https://modelcontextprotocol.io/quickstart/user をご参照ください。)


mark3labs/mcp-go との比較

今回は、非公式ライブラリの中で最もメジャーな mark3labs/mcp-go と公式 SDK を、 Tools を追加する際の実装例を交えつつ比較してみようと思います。

mark3labs/mcp-go での実装例:

func (s *Server) registerTools() {
    getPageTool := mcp.NewTool("get_page",
        mcp.WithDescription("Get a Scrapbox page by title"),
        mcp.WithString("title", mcp.Required(), mcp.Description("Page title to retrieve")),
    )
    s.mcpServer.AddTool(getPageTool, s.handleGetPage)
    // 他のTools...
}

func (s *Server) handleGetPage(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	title, err := req.RequireString("title")
	if err != nil {
		return mcp.NewToolResultError(err.Error()), nil
	}
	page, err := s.client.GetPage(ctx, title)
	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("Failed to get page: %v", err)), nil
	}
	b, err := json.Marshal(page)
	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("Failed to marshal page: %v", err)), nil
	}
	return mcp.NewToolResultText(string(b)), nil
}

mark3labs/mcp-go では、NewToolで Tools を定義し、それをAddToolでサーバーに登録する流れになっています。
AddToolで追加するハンドラーのシグネチャはfunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error)という形で、受け取ったリクエストを文字列ベースで処理し、結果を返すというシンプルな構造になっています。

公式 SDK での実装例:

type GetPageParams struct {
	PageTitle string `json:"page_title" jsonschema:"required,description=Page title to retrieve"`
}

func (s *Server) registerTools() {
    getPageTool := mcp.NewServerTool("get_page",
        "Get a Scrapbox page by title",
        s.handleGetPage,
        mcp.Input(
            mcp.Property("page_title", mcp.Description("Page title to retrieve")),
        ),
    )
    // 他のTools...
    s.mcpServer.AddTools(
        getPageTool,
        // 他のTools...
    )
}

func (s *Server) handleGetPage(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[GetPageParams]) (*mcp.CallToolResultFor[any], error) {
    page, err := s.client.GetPage(ctx, params.Arguments.PageTitle)
	if err != nil {
		return nil, fmt.Errorf("Failed to get page: %w", err)
	}

	pageJSON, err := json.Marshal(page)
	if err != nil {
		return nil, fmt.Errorf("Failed to marshal page: %w", err)
	}

	return &mcp.CallToolResultFor[any]{
		Content: []mcp.Content{&mcp.TextContent{Text: string(pageJSON)}},
	}, nil
}

公式 SDK も mark3labs/mcp-go と同様に、NewServerToolで Tools を定義し、それをAddToolsでサーバーに登録する流れになっていますが、Tools 定義時にハンドラー関数を指定し、まとめて Tools を追加できる点が異なります。
また、公式 SDK の場合は、AddToolsで追加するハンドラーのシグネチャはfunc(context.Context, *ServerSession, *CallToolParamsFor[In]) (*CallToolResultFor[Out], error)という形で、Generics を活用した型安全な設計になっています。

実際に使ってみた所感

mark3labs/mcp-go は、パラメータは文字列ベースで取得し、インラインで Tools の定義やハンドラーを記述できるため、最小限のコードでサクッと実装できました。学習コストも低く、Go に不慣れな方や MCP を初めて触る方でもとっつきやすいかと思います。
ただし、型安全性は実行時チェックに頼る部分が多く、IDE の型補完や静的解析の恩恵は限定的で、大規模な開発や長期運用を考えると型の不一致やパラメータ名のミスに気付きにくいリスクがあると思いました。

一方、公式 SDK はパラメータを構造体で定義し、Generics を活用した設計になっているため、IDE の補完や型チェックがしっかり効きます。これにより、実装時やリファクタ時に型の不一致や typo によるバグを未然に防ぎやすく、Go らしい安心感があると思いました。
コード量は多少増えますが、Tools を追加してサーバーを起動するという点では実装に大きな差異はなく、今まで mark3labs/mcp-go で MCP サーバーを実装した経験がある方にとっても違和感のない設計になっていると感じました。

実際に公式 SDK と mark3labs/mcp-go で同じ機能を実装してみて、公式 SDK は mark3labs/mcp-go の実用的な思想を踏襲しつつ、Go 言語らしい型安全性を強化した設計になっていると感じました。


おわりに

今回は、先日公開されたばかりの MCP の公式 Go SDK の使用感を確かめるべく実際に触ってみましたが、現時点では v0.0.0 という初期バージョンの状態で、安定版のリリースは 2025 年 8 月予定[1]となっています。

今後仕様が大きく変更される可能性もあるかと思いますので、参考程度に読んでいただけますと幸いです 🙏

公式の Go SDK がついに公開されたということで、MCP エコシステムの発展と共に、Go での MCP の開発がより身近になることを期待しています。
個人的にも引き続き公式 Go SDK の動向を追っていこうと思います!

最後までご覧いただき、ありがとうございました!


参考情報

脚注
  1. https://github.com/modelcontextprotocol/go-sdk?tab=readme-ov-file#mcp-go-sdk ↩︎

Canary Tech Blog

Discussion