MCP Go SDK(github.com/modelcontextprotocol/go-sdk)は、Model Context Protocolの公式Go実装で、Googleと共同で保守されています。2026年10月時点の最新はv1.8.0(2026-09-14)で、v1.7.0から仕様2026-07-28に対応しました。この記事では、Go 1.26.5の手元環境でv1.8.0を実際にビルドして動かした結果をもとに、サーバーの作り方、HTTPで公開するときの設定、コミュニティ版mark3labs/mcp-goとの違いを順に説明します。MCPそのものの仕組みはMCP(Model Context Protocol)とは?AIと外部ツールをつなぐ標準規格の仕組みにまとめています。
まとめ:MCP Go SDK v1.8.0で押さえる要点
- 最新はv1.8.0(2026-09-14)。2026-07-28仕様への対応はv1.7.0(2026-07-28)からで、旧仕様2025-11-25以前のクライアントとも自動で版を合わせます。
go getはモジュールのルートでなく/mcpパッケージを指定します。ルートを指定するとgo.sumが揃わずビルドが止まります(手元で再現)。- ツールは
mcp.AddToolにGoの構造体を渡すだけで、入力・出力のJSON Schemaと引数検証が自動で付きます。 - stdioサーバーでは標準出力にプロトコル以外の出力を書かないこと。
fmt.Printlnが1行混ざるだけで接続に失敗します。 - Streamable HTTPで2026-07-28仕様を話すには
Stateless: trueが必要です。ステートフルのままだと2025-11-25に下がります。 - mark3labs/mcp-goもv1.0.0(2026-09-02)で2026-07-28仕様に対応済み。新規なら公式SDK、既存のmcp-go資産は急いで移行しなくてよい、が判断の基準です。
MCP Go SDKの位置づけと現行バージョン
公式Go SDKは4つのパッケージで構成されます。サーバーとクライアントを作る中心がmcp、独自トランスポート向けのjsonrpc、OAuthの部品を持つauthとoauthexです。READMEは謝辞の節でmark3labs/mcp-goなど先行するサードパーティSDKを設計の参考にしたと明記しており、公式版はそれらの後発にあたります。
ライセンスはMITからApache-2.0へ移行中です。新しい貢献はApache-2.0、再許諾の同意が取れていない過去の貢献はMITのまま残る二本立てで、GitHubのライセンス表示が「NOASSERTION」になっているのはこのためです。
SDKの版と対応するMCP仕様
公式Go SDKの対応表を整理すると次のとおりです。仕様の版はSDKが接続時に相手と交渉して、双方が対応する最も新しい版に決まります。なお、v1.4.0以降の2025-11-25対応のうち、クライアント側のOAuthは実験的サポートの扱いです。
| SDKの版 | 最新の対応仕様 | あわせて話せる旧仕様 |
|---|---|---|
| v1.7.0以降 | 2026-07-28 | 2025-11-25・2025-06-18・2025-03-26・2024-11-05 |
| v1.4.0〜v1.6.1 | 2025-11-25 | 2025-06-18・2025-03-26・2024-11-05 |
| v1.2.0〜v1.3.1 | 2025-11-25(一部) | 2025-06-18・2025-03-26・2024-11-05 |
| v1.0.0〜v1.1.0 | 2025-06-18 | 2025-03-26・2024-11-05 |
リリースの節目は、v0.2.0(2025-07-11)、安定版v1.0.0(2025-09-30)、2026-07-28仕様対応のv1.7.0(2026-07-28)、トランスポートの防御を固めたv1.8.0(2026-09-14)です。v1.2.0〜v1.3.1はクライアント側OAuthとツール付きサンプリングが未実装で、2025-11-25対応は部分的でした。2025年夏に書かれた解説はv0系のAPIを前提にしていることが多く、関数名が今と合わない場合があります。
Goの要件は、v1.8.0のgo.modがgo 1.25.0を宣言しています。これはSDKの最低要件で、Go自体のサポート期間とは別の話です。Goは各メジャー版を「新しいメジャー版が2つ出るまで」サポートするため、Go 1.27.0(2026-08-19)の公開でGo 1.25系はサポート対象外になりました。READMEは「新しいリリースはサポート中のGoだけを対象にする」としているので、1.25系のままでは今後のSDK更新で最低要件を満たせなくなる可能性があります。Goの導入から確認したい場合はGo言語とは|1.27系の導入からHTTP APIを動かすまでの実装入門、最新版の変更点はGo 1.27の新機能・変更点まとめを参照してください。
go getでビルドが止まる指定先と正しい導入手順
公式のクイックスタートには長くgo get github.com/modelcontextprotocol/go-sdkと書かれていました。ところがモジュールのルートには取り込めるパッケージが無いため、この指定では依存が// indirectとして記録されるだけで、mcpパッケージが必要とするgo.sumの行が書かれません。v1.8.0のリリースノートにも、この手順の修正(PR #1148)が含まれています。
空のディレクトリでgo mod initのあと、次節のサーバーのコードをmain.goとして保存してから同じ手順を踏むと、最初のビルドで次のエラーが出ます(Go 1.26.5・v1.8.0で再現)。
$ go mod init example.com/qs
$ go get github.com/modelcontextprotocol/[email protected]
go: added github.com/modelcontextprotocol/go-sdk v1.8.0
$ go build ./...
missing go.sum entry for module providing package github.com/google/jsonschema-go/jsonschema
(imported by github.com/modelcontextprotocol/go-sdk/mcp); to add:
go get github.com/modelcontextprotocol/go-sdk/[email protected]
対処は、パッケージのパスを指定して取得し直すか、コードを書いたあとにgo mod tidyを実行することです。最初から次の形で入れておけば、この問題は起きません。
go get github.com/modelcontextprotocol/go-sdk/[email protected]
stdioで動くMCPサーバーの最小実装
サーバーはmcp.NewServerで作り、mcp.AddToolでツールを足し、server.Runでトランスポートにつなぎます。次のコードは名前を受け取って挨拶を返すツールを1つ持つサーバーで、READMEの例と同じ構成です。
package main
import (
"context"
"log"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
type Input struct {
Name string `json:"name" jsonschema:"the name of the person to greet"`
}
type Output struct {
Greeting string `json:"greeting" jsonschema:"the greeting to tell to the user"`
}
func SayHi(ctx context.Context, req *mcp.CallToolRequest, input Input) (*mcp.CallToolResult, Output, error) {
return nil, Output{Greeting: "Hi " + input.Name}, nil
}
func main() {
server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v1.0.0"}, nil)
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
log.Fatal(err)
}
}
go build -o bin/greeter .でバイナリにしておくと、後で各クライアントへ登録するときにパスを渡すだけで済みます。
構造体タグから生成されるスキーマ
mcp.AddToolはジェネリクスで入力型と出力型を受け取り、JSON Schemaを自動生成します。上のサーバーが返したtools/listの入力スキーマは次のとおりでした。
{"additionalProperties":false,
"properties":{"name":{"description":"the name of the person to greet","type":"string"}},
"required":["name"],"type":"object"}
jsonschemaタグの文字列がそのままdescriptionになり、omitemptyを付けないフィールドはrequiredに入ります。任意の引数にしたいときはjson:"name,omitempty"と書きます。出力の構造体にも同じ規則でスキーマが付き、結果はstructuredContent(JSONオブジェクト)と、同じ内容をJSON文字列にしたcontentのテキストの両方で返されます。
引数の型検証とツールエラーの判定
nameに数値の42を渡して呼ぶと、ハンドラーは呼ばれず、SDKが検証で弾きます。このときGo側のCallToolはエラーを返さず、結果のisErrorがtrueになり、本文にvalidating /properties/name: type: 42 has type "integer", want "string"が入ります。プロトコルのエラーではなくツールの失敗として返るので、AIはメッセージを読んで引数を直して呼び直せます。クライアントを自作する場合は、errだけでなくres.IsErrorも必ず確認してください。
標準出力へのログ混入による接続失敗
stdioトランスポートは標準入出力をJSON-RPCの通信路にそのまま使い、標準出力にはSDKがプロトコルのメッセージだけを書きます。試しにmainの先頭へfmt.Println("server starting")を1行足すと、クライアントはinvalid character 's' looking for beginning of valueで接続に失敗しました。ログは標準エラー出力に出します。Goのlogパッケージは既定で標準エラー出力へ書くのでそのまま使えますが、fmt.Printlnや、標準出力に書くロガーの設定は使わないでください。
Goクライアントからの動作確認
Claude Codeなどへ登録する前に、Goのクライアントからサーバーを直接呼ぶと切り分けが楽です。mcp.CommandTransportがサーバーのバイナリを子プロセスとして起動し、標準入出力でつなぎます。サーバーと同じパッケージに置くとmain関数が重複するので、次のコードはclient/main.goに分けて保存し、bin/greeterがあるモジュールのルートからgo run ./clientで実行します。
package main
import (
"context"
"fmt"
"log"
"os/exec"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
func main() {
ctx := context.Background()
client := mcp.NewClient(&mcp.Implementation{Name: "probe", Version: "v0.0.1"}, nil)
session, err := client.Connect(ctx, &mcp.CommandTransport{Command: exec.Command("./bin/greeter")}, nil)
if err != nil {
log.Fatal(err)
}
defer session.Close()
fmt.Println("protocol:", session.InitializeResult().ProtocolVersion)
res, err := session.CallTool(ctx, &mcp.CallToolParams{
Name: "greet",
Arguments: map[string]any{"name": "Gopher"},
})
if err != nil {
log.Fatal(err)
}
if res.IsError {
log.Fatal("tool failed")
}
fmt.Println(res.StructuredContent)
}
手元の実行結果はprotocol: 2026-07-28とmap[greeting:Hi Gopher]でした。v1.8.0同士ならstdioでは新仕様で話していることを、この1行で確かめられます。
Streamable HTTPで公開するときのStateless設定
リモートから使うサーバーは、mcp.NewStreamableHTTPHandlerでhttp.Handlerを作り、標準のnet/httpに載せます。次のコードはstdio版のserver.Runの行を置き換えるもので、importにnet/httpを足します。待ち受け先の127.0.0.1:8080はローカルでの動作確認用で、外部に公開するときは待ち受けアドレスとHostヘッダーの扱い(後述)を合わせて変えます。トランスポートそのものの仕組みはStreamable HTTPとは?MCPのトランスポートとSSEの違い、2026-07-28仕様の変更点で解説しています。
handler := mcp.NewStreamableHTTPHandler(
func(*http.Request) *mcp.Server { return server },
&mcp.StreamableHTTPOptions{Stateless: true},
)
http.Handle("/mcp", handler)
log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
v1.7.0のリリースノートは、HTTPで2026-07-28仕様のリクエストを受け付けるのはStatelessをtrueにしたときだけ、と定めています。同じサーバーを両方の設定で立て、v1.8.0のクライアント(mcp.StreamableClientTransport)から接続した結果が次の表です。
| 設定 | 交渉された仕様 | セッションID | ツール呼び出し |
|---|---|---|---|
Stateless: true |
2026-07-28 | なし | 成功 |
Stateless: false(既定) |
2025-11-25 | あり | 成功 |
どちらでもツールは動くため、ステートフルのまま運用していても不具合としては表に出ません。新仕様のキャッシュ指定(ttlMs)やHTTPヘッダーでのルーティング(Mcp-Method)を使いたいならStatelessへ切り替えます。逆に、接続ごとの状態をサーバーのメモリに持たせる設計なら、ステートフルのまま2025-11-25で動かすことになります。
Hostヘッダーの検査による403
ハンドラーは既定でDNSリバインディング対策を有効にしています。localhostのアドレス(127.0.0.1や[::1])に届いたリクエストのHostヘッダーがlocalhostでなければ、403で拒否します。手元でもHost: evil.exampleを付けたリクエストにはForbidden: invalid Host header "evil.example"が返りました。同じマシン上のリバースプロキシがHostヘッダーを書き換えずに転送する構成では、正規のリクエストもこの検査で落ちます。プロキシ側でHostを書き換えるのが筋で、StreamableHTTPOptions.DisableLocalhostProtectionで検査を外すのは、外部に露出しないと確認できた場合に限ります。
2026-07-28仕様でGo SDKの挙動が変わった点
v1.7.0で通信の土台が書き換わりました。サーバーを書く側に影響が大きいのは次の5点です。
- 初期化ハンドシェイクの廃止:
initializeの代わりに、各リクエストの_metaが版とクライアント情報を運び、server/discoverで対応版を問い合わせます。discoverが失敗すると、SDKは旧来のinitializeへ自動で戻ります。 - サーバーからの問い合わせの方式変更:エリシテーションやサンプリングは、ハンドラーが
InputRequiredResultを返し、クライアントが答えを付けて呼び直す方式(MRTR)になりました。旧クライアント向けの互換層はSDKが持っています。 - 変更通知の一本化:
tools/list_changedなどの通知はsubscriptions/listenの1本のストリームにまとまりました。 - roots・sampling・loggingの非推奨化:Goの型は互換のため残りますが、新しいサーバーは頼らない前提です。READMEは非推奨の猶予を少なくとも12か月としています。
- MCPGODEBUGの期限:v1.7.0とv1.8.0で、変更前の挙動に戻すフラグが計9個入りました(例:
MCPGODEBUG=hintomitempty=1)。いずれもv1.9.0で削除予定なので、使っている場合は外す準備が要ります。
公式Go SDK v1.8.0リリースノートによると、v1.8.0は新しい仕様を足さず、防御の強化が中心です。JSONの入れ子は1000段を超えると拒否され、stdioの1フレームの上限はStdioTransport.MaxLineLength、SSEの1イベントの上限はMaxEventSizeで設定できます。ServerOptions.SupportedProtocolVersionsで広告する仕様の版を絞れるようになり、SDKが実装していない版を書くと生成時にpanicします。
Claude Code・Gemini CLIへの登録手順
ビルドしたバイナリは、各クライアントの設定に絶対パスで登録します。相対パスはクライアントの起動ディレクトリ次第で解決先が変わるため、避けるのが無難です。
Claude Codeへの登録
stdioのサーバーはclaude mcp addで登録します。区切りの--より後ろが、サーバーを起動するコマンドです。
claude mcp add --transport stdio greeter -- /Users/you/greeter/bin/greeter
Gemini CLIへの登録
Gemini CLIは~/.gemini/settings.json(プロジェクト単位なら.gemini/settings.json)のmcpServersを読みます。コマンドで書く場合はgemini mcp add -s user greeter /Users/you/greeter/bin/greeterです。-sを省くと既定のスコープはprojectで、カレントディレクトリの.gemini/settings.jsonに書かれます。
{
"mcpServers": {
"greeter": {
"command": "/Users/you/greeter/bin/greeter",
"timeout": 30000,
"trust": false,
"includeTools": ["greet"]
}
}
}
timeoutの既定は600,000ミリ秒(10分)です。trustをtrueにするとツール実行の確認がすべて省かれるので、書き込みを伴うツールを持つサーバーではfalseのままにします。HTTPで公開したサーバーは、commandの代わりにhttpUrlへエンドポイントのURLを書きます。権限や認可を含めた接続設計全般はAIエージェントにMCPで外部ツールを接続する実装手順とは?にまとめています。
mcp-goとの違いと選び分け
mark3labs/mcp-goは、公式SDKより前からあるコミュニティ版です。2026-09-02にv1.0.0、2026-09-23にv1.1.1が出ています。同じ挨拶ツールをmcp-goで書くと次のようになります。公式SDKとは別のモジュール(go get github.com/mark3labs/[email protected])で、importはgithub.com/mark3labs/mcp-go/mcpとgithub.com/mark3labs/mcp-go/serverです。公式SDKのmcpとはパッケージ名が同じでも中身が別物なので、同じファイルには混ぜられません。掲載範囲はmain関数の中身です。
s := server.NewMCPServer("greeter", "1.0.0", server.WithToolCapabilities(false))
tool := mcp.NewTool("greet",
mcp.WithDescription("say hi"),
mcp.WithString("name", mcp.Required(), mcp.Description("the name of the person to greet")),
)
s.AddTool(tool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
name, err := req.RequireString("name")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
return mcp.NewToolResultText("Hi " + name), nil
})
if err := server.ServeStdio(s); err != nil {
log.Fatal(err)
}
このmcp-go v1.1.1のサーバーを、公式SDK v1.8.0のクライアントから呼んだところ、2026-07-28仕様で接続でき、ツールも動きました。mcp-goのREADMEは対応仕様を「2025-11-25」と書いたままですが、mcp-go v1.0.0リリースノートには2026-07-28対応が載っており、実際の挙動もそちらに一致します。READMEの記述だけで「mcp-goは新仕様に未対応」と判断しないでください。
| 比較項目 | 公式go-sdk | mark3labs/mcp-go |
|---|---|---|
| 最新版(2026-10-02時点) | v1.8.0(2026-09-14) | v1.1.1(2026-09-23) |
| GitHubスター数 | 5,178 | 9,150 |
| ライセンス | Apache-2.0(一部MIT) | MIT |
| go.modのGo指定 | 1.25.0 | 1.25.5 |
| ツールの定義 | 構造体とジェネリクス | ビルダー関数 |
| 引数の検証 | スキーマで自動 | ハンドラー内で取り出して確認 |
| 出力スキーマ | 出力の構造体から自動 | WithOutputSchemaで明示 |
数値の42を渡したときの返り方にも差が出ました。公式版はスキーマ検証のメッセージ、mcp-goはargument "name" is not a stringで、どちらもisError: trueです。mcp-goにもmcp.NewStructuredToolHandlerやmcp.WithInputSchemaといった型付きの補助関数があり、構造体ベースで書くこともできます。
選び方の基準は次のとおりです。
- 新規に作るなら公式go-sdk。仕様の策定元と同じ組織で保守され、新しい版への追従はv1.7.0が仕様の公開日と同じ日に出たほど早いです。
- mcp-goで動いている既存サーバーは、移行を急がない。v1系で新仕様に対応しており、この記事で比べたstdioのツール定義・引数検証・2026-07-28仕様での接続の範囲では、書き直す手間に見合う差は見つかりませんでした。認証やミドルウェアを多用している場合は、その部分を個別に比べてから判断します。
- 公式SDKを採用すべきでない場面:mcp-goのセッション単位のツール追加(
AddSessionTool)や特定クライアントへの通知送信に依存している場合です。公式版で同じ設計を組むならステートフル運用が前提になり、HTTPでは2025-11-25で動かすことになります。
他の言語のSDKも同じ仕様改定に合わせて大きく変わっています。TypeScriptはMCP TypeScript SDK v2とは?パッケージ分割とステートレス化の移行要点、PythonはFastMCP 4とは?sessionless対応と廃止APIから読む3系からの移行判断で扱っています。
よくある質問
MCP Go SDKは公式のSDKですか?
はい。github.com/modelcontextprotocol/go-sdkはMCPの公式GitHub組織にあるGo実装で、Googleと共同で保守されています。mark3labs/mcp-goはコミュニティ版です。
go getのあとに「missing go.sum entry」で止まるのはなぜですか?
モジュールのルートを指定して取得したためです。go get github.com/modelcontextprotocol/go-sdk/[email protected]でパッケージを指定するか、go mod tidyを実行すると解消します。
MCP Go SDKに必要なGoのバージョンは?
v1.8.0のgo.modはgo 1.25.0を宣言しています。新しいリリースはサポート中のGoだけを対象にする方針なので、最新のGoを使うのが確実です。
2026-07-28仕様で動かすには何を変えればよいですか?
v1.7.0以降へ上げれば、stdioでは自動で新仕様になります。Streamable HTTPではStreamableHTTPOptionsのStatelessをtrueにする必要があり、既定のステートフルのままだと2025-11-25で接続されます。
mcp-goのサーバーと公式SDKのクライアントは組み合わせられますか?
組み合わせられます。mcp-go v1.1.1のサーバーを公式SDK v1.8.0のクライアントから呼んだところ、2026-07-28仕様で接続し、ツール呼び出しも成功しました。