🤖

MCP Go SDK 入門

に公開

MCP Go SDK を使ってみる

この記事は、最新版(2025/08/17) の MCP Go SDK で stdioStreamable HTTP のMCPサーバーを実装してみる記事です。

※100%人力でこの記事を書いています

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

MCP inspector を起動する

これからMCPサーバーを実装してみますが、実装したMCPサーバーの動作確認をするために MCP inspector というものを使ってデバッグしていきます。

https://github.com/modelcontextprotocol/inspector

MCP inspector はこういうGUI上でMCPサーバーをデバッグできるツールです。

MCP inspector を起動するコマンドは次のとおりで、このコマンドを打つと勝手にブラウザが立ち上がり MCP inspector を使える状態になります。

npx @modelcontextprotocol/inspector

この MCP inspectorstdioStreamable HTTP の両方に対応しており、 stdioStreamable HTTP の切り替えは画面上からポチポチ変更できますが、次のようにすると、自動で設定された状態で起動することも可能です。

stdio で go の MCP サーバーを起動する例:

npx @modelcontextprotocol/inspector --transport stdio go run ./examples/stdio/main.go

Streamable HTTP で go の MCP サーバーに接続する例:

npx @modelcontextprotocol/inspector --transport http --server-url http://localhost:3001

ちなみにポートを変えたい時はこんな感じにすればよさそう:

CLIENT_PORT=6275 SERVER_PORT=6279 npx @modelcontextprotocol/inspector

stdio 接続を試してみる

これからシンプルなMCPサーバーを実装していきますが、まずはシンプルな stdio の方から実装してみます。

こんな感じのソースコードを書きます。

main.go
package main

import (
	"context"
	"log"
	"os"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

type HiArgs struct {
	Name string `json:"name" jsonschema:"the name to say hi to"`
}

func SayHi(ctx context.Context, req *mcp.ServerRequest[*mcp.CallToolParamsFor[HiArgs]]) (*mcp.CallToolResultFor[struct{}], error) {
	return &mcp.CallToolResultFor[struct{}]{
		Content: []mcp.Content{
			&mcp.TextContent{Text: "Hi " + req.Params.Arguments.Name},
		},
	}, nil
}

func main() {
	server := mcp.NewServer(&mcp.Implementation{Name: "greeter"}, nil)
	mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
	t := &mcp.LoggingTransport{Transport: &mcp.StdioTransport{}, Writer: os.Stderr}
	if err := server.Run(context.Background(), t); err != nil {
		log.Printf("Server failed: %v", err)
	}
}

なお現在リリースされている go-sdk v0.2.0 は機能が足りないので最新(2025/08/17時点)のmainブランチのgo-skdを使っています

go get -u github.com/modelcontextprotocol/go-sdk@main

最低限の実装ができたので MCP inspector で接続してみましょう。次のようなコマンドで MCP inspector を立ち上げるとよいと思います(実装した main.go へのパスになっていること)

npx @modelcontextprotocol/inspector --transport stdio go run ./examples/stdio/main.go

左側に Connect ボタンがあるのでクリックすると MCP サーバーを立ち上がり stdio 接続されます。

接続すると画面上部に 🔨Tools と表示されるのでクリックしてみましょう。

  1. List Tools をクリックする
  2. greet が表示されるのでクリック
  3. name に適当に入力して Run Tool をクリックする

すると "Hi xxx" と応答が表示されます。これで stdio で無事に動いていることを確認ができました!🎉

Streamable HTTP 接続を試してみる

Streamable HTTP というのは Model Context Protocol の Transports という仕様で定義されている HTTP を使った接続方式です。 HTTP を使ってどうにか stdio と同じことを実現させようとしているためちょっと複雑な仕様になっています。

この仕様をざっくり説明すると、クライアント側からサーバー側に問い合わせるときはPOSTメソッドによる一般的なHTTP通信を行いつつ、サーバー側からのPUSH通知を受け取るために事前にGETメソッドで Server-Sent Events(SSE) を確立しておきサーバー側からPUSH通知を受け取るというものです。
※同じパスで「常時GETの接続」をしつつ「必要になったらPOSTでリクエストを送る」というイメージです。

ちょっとややこしい仕様な気がしますが、 Go SDK を使えば特にこの仕様を意識する必要はないので、ちゃんと理解できる必要はないと思います。

では早速 Streamable HTTP のサーバーを書いてみましょう。
先ほどの stdio 版のソースを次のように書き換えるだけで http://localhost:3001 で起動する Streamable HTTP のMCPサーバーになります。

 package main
 
 import (
 	"context"
 	"log"
+	"net/http"
-	"os"
 	"github.com/modelcontextprotocol/go-sdk/mcp"
 )
 
 type HiArgs struct {
 	Name string `json:"name" jsonschema:"the name to say hi to"`
 }
 
 func SayHi(ctx context.Context, req *mcp.ServerRequest[*mcp.CallToolParamsFor[HiArgs]]) (*mcp.CallToolResultFor[struct{}], error) {
 	return &mcp.CallToolResultFor[struct{}]{
 		Content: []mcp.Content{
 			&mcp.TextContent{Text: "Hi " + req.Params.Arguments.Name},
 		},
 	}, nil
 }
 
 func main() {
 	server := mcp.NewServer(&mcp.Implementation{Name: "greeter"}, nil)
 	mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
+	handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server {
+		return server
+	}, nil)
+	if err := http.ListenAndServe(":3001", handler); err != nil {
-	t := &mcp.LoggingTransport{Transport: &mcp.StdioTransport{}, Writer: os.Stderr}
-	if err := server.Run(context.Background(), t); err != nil {
 		log.Printf("Server failed: %v", err)
 	}
 }

ソースを書き換えたので go run main.go でサーバーを立ち上げ、 MCP inspector から接続してみましょう。次のコマンドで MCP inspector を立ち上げるとよいと思います。

npx @modelcontextprotocol/inspector --transport http --server-url http://localhost:3001

起動したら stdio で行ったように Connect でMCPサーバーと接続し、 greet ツールを使うことができるはずです!
たったこれだけの変更で Streamable HTTP として接続できるようになりました!🎉

Streamable HTTP で SSE のレスポンスを確認してみる

先ほどは Streamable HTTP を使って greet という即座に結果が返ってくる tool を使えることを確認しましたが、実はこれは Server-Sent Events(SSE) は使っていませんでした(厳密には使っているんですが)。

そこで次はGETメソッドによるSSEが動作することを確認してみます。

今度はソースを次のように書き換えます。変更内容はMCPサーバーが起動後10秒経過すると greet2 という tool が増えるようにしました。こうすることでMCPサーバーから15秒後にSSEでnotificationが届くことを確認できるはずです。

 package main
 
 import (
 	"context"
 	"log"
 	"net/http"
+	"time"
 
 	"github.com/modelcontextprotocol/go-sdk/mcp"
 )
 
 type HiArgs struct {
 	Name string `json:"name" jsonschema:"the name to say hi to"`
 }
 
 func SayHi(ctx context.Context, req *mcp.ServerRequest[*mcp.CallToolParamsFor[HiArgs]]) (*mcp.CallToolResultFor[struct{}], error) {
 	return &mcp.CallToolResultFor[struct{}]{
 		Content: []mcp.Content{
 			&mcp.TextContent{Text: "Hi " + req.Params.Arguments.Name},
 		},
 	}, nil
 }
 
 func main() {
 	server := mcp.NewServer(&mcp.Implementation{Name: "greeter"}, nil)
 	mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
+
+	go func() {
+		time.Sleep(15 * time.Second)
+		mcp.AddTool(server, &mcp.Tool{Name: "greet2", Description: "say hi2"}, SayHi)
+	}()
+
 	handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server {
 		return server
 	}, nil)
 	if err := http.ListenAndServe(":3001", handler); err != nil {
 		log.Printf("Server failed: %v", err)
 	}
 }

MCPサーバーを起動しつつ MCP inspector で15秒以内に Connect ボタンを押して接続すると notification が届くはずです。

npx @modelcontextprotocol/inspector --transport http --server-url http://localhost:3001

Server Notifications の欄に notifications/tools/list_changed が届いたことが確認できました。

また devtools の Network タブから EventStream が届いていることを確認できます。

ここで注目したいのは、MCPサーバー側から通知が来るのはGETメソッドを使った接続からであることです。GETメソッドでのリクエストは Connection: keep-alive で常時接続しながらMCPサーバーからの通知を待ち受けます。

今確認したのは notifications/tools/list_changed というnotificationでしたが、 tool を次のように書き換えるとtoolの進捗をnotificationすることができます。これは notification/progress という種類の通知です。

 func SayHi(ctx context.Context, req *mcp.ServerRequest[*mcp.CallToolParamsFor[HiArgs]]) (*mcp.CallToolResultFor[struct{}], error) {
+	req.Session.NotifyProgress(ctx, &mcp.ProgressNotificationParams{
+		Message:       "Starting...",
+		ProgressToken: req.Params.GetProgressToken(),
+		Progress:      1,
+		Total:         2,
+	})
+
+	// なにか重い処理
+	time.Sleep(2 * time.Second)
+
+	req.Session.NotifyProgress(ctx, &mcp.ProgressNotificationParams{
+		Message:       "Finishing up...",
+		ProgressToken: req.Params.GetProgressToken(),
+		Progress:      2,
+		Total:         2,
+	})
 	return &mcp.CallToolResultFor[struct{}]{
 		Content: []mcp.Content{
 			&mcp.TextContent{Text: "Hi " + req.Params.Arguments.Name},
 		},
 	}, nil
 }

こんな感じで通知が来ていることを確認できます。

おわり

以上 Go SDK でMCPサーバーを動かしてみる記事でした。

最後にあらためて Streamable HTTP の最小のソースコードを載せておきます。

package main

import (
	"context"
	"log"
	"net/http"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

type HiArgs struct {
	Name string `json:"name" jsonschema:"the name to say hi to"`
}

func SayHi(ctx context.Context, req *mcp.ServerRequest[*mcp.CallToolParamsFor[HiArgs]]) (*mcp.CallToolResultFor[struct{}], error) {
	return &mcp.CallToolResultFor[struct{}]{
		Content: []mcp.Content{
			&mcp.TextContent{Text: "Hi " + req.Params.Arguments.Name},
		},
	}, nil
}

func main() {
	server := mcp.NewServer(&mcp.Implementation{Name: "greeter"}, nil)
	mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
	handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server {
		return server
	}, nil)
	if err := http.ListenAndServe(":3001", handler); err != nil {
		log.Printf("Server failed: %v", err)
	}
}

以上です

Discussion