Go公式MCPサーバーで両トランスポートに対応する
まず背景の整理から:
| トランスポート |
MCP仕様バージョン |
エンドポイント |
| Streamable HTTP (新) |
2025-03-26〜 |
POST /mcp + GET /mcp の単一エンドポイント |
| SSE (旧・非推奨) |
2024-11-05 |
GET /sse (接続) + POST /messages (送信) の2本 |
公式SDKは両方のサーバーハンドラーを持っています。NewSSEHandler は2024-11-05仕様のSSEトランスポートを、NewStreamableHTTPHandler はStreamable HTTPトランスポートをそれぞれ実装しており、同一サーバーインスタンスを両方に渡せます。
MCP仕様の後方互換ガイドによると、新旧両クライアントをサポートしたいサーバーは、旧SSEの/sse・/messagesエンドポイントと、新しいMCPエンドポイントを並行して提供する必要があります。
実装例
// main.go
package main
import (
"context"
"log"
"net/http"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// --- ツール定義 ---
type GreetInput struct {
Name string json:"name" jsonschema:"挨拶する相手の名前"
}
type GreetOutput struct {
Message string json:"message"
}
func greetHandler(
ctx context.Context,
req *mcp.CallToolRequest,
input GreetInput,
) (*mcp.CallToolResult, GreetOutput, error) {
return nil, GreetOutput{Message: "Hello, " + input.Name + "!"}, nil
}
func newMCPServer() *mcp.Server {
server := mcp.NewServer(&mcp.Implementation{
Name: "my-mcp-server",
Version: "v1.0.0",
}, nil)
mcp.AddTool(server, &mcp.Tool{
Name: "greet",
Description: "名前を受け取って挨拶する",
}, greetHandler)
return server
}
func main() {
mux := http.NewServeMux()
// ① Streamable HTTP (新仕様 2025-03-26〜)
// 単一エンドポイント: POST /mcp, GET /mcp
streamableHandler := mcp.NewStreamableHTTPHandler(
func(r *http.Request) *mcp.Server {
return newMCPServer()
},
&mcp.StreamableHTTPOptions{
// セッションIDを自動生成 (デフォルト)
// Stateless: true, // ステートレスにしたい場合
},
)
mux.Handle("/mcp", streamableHandler)
// ② Legacy SSE (旧仕様 2024-11-05)
// /sse → GET: SSEストリームを確立
// /message → POST: クライアントからのメッセージ受信
sseHandler := mcp.NewSSEHandler(
func(r *http.Request) *mcp.Server {
return newMCPServer()
},
)
mux.Handle("/sse", sseHandler)
mux.Handle("/message", sseHandler) // SSEHandlerが内部でルーティング
log.Println("MCP server listening on :8080")
log.Println(" Streamable HTTP : POST/GET http://localhost:8080/mcp")
log.Println(" Legacy SSE : GET http://localhost:8080/sse")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Fatal(err)
}
}
go.mod
module my-mcp-server
go 1.23
require github.com/modelcontextprotocol/go-sdk v1.4.0
ポイント解説
newMCPServer() を関数にする理由
getServer func(*http.Request) *mcp.Server のシグネチャに合わせています。リクエストごとに新しいサーバーインスタンスを返すことで、セッション分離が可能です。同じインスタンスを返しても問題ありません。
SSEHandlerのエンドポイント
mcp.NewSSEHandler は内部でパスを自動判別します。/sse をGETされたらSSEストリームを確立し、クライアントへ /message?sessionId=xxx というエンドポイントを通知します。そのため /message も同じハンドラーに向けておく必要があります:
mux.Handle("/sse", sseHandler)
mux.Handle("/message", sseHandler)
SDKバージョンの確認
v1.4.0以降では 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25 の全仕様バージョンをサポートしています。最新の v1.4.0 を使うのが安全です。
クライアントからの接続確認
# Streamable HTTP (新しいMCPクライアントから)
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","clientInfo":{"name":"test","version":"1.0"},"capabilities":{}}}'
Legacy SSE (古いクライアントから)
curl http://localhost:8080/sse # GETでSSEストリーム確立
Cursor、Claude Desktop等の古いクライアントはSSE側、最新のクライアントやClaude for Chromeは自動的にStreamable HTTPを試みてフォールバックします。
Go公式MCPサーバーで両トランスポートに対応する
まず背景の整理から:
公式SDKは両方のサーバーハンドラーを持っています。
NewSSEHandlerは2024-11-05仕様のSSEトランスポートを、NewStreamableHTTPHandlerはStreamable HTTPトランスポートをそれぞれ実装しており、同一サーバーインスタンスを両方に渡せます。MCP仕様の後方互換ガイドによると、新旧両クライアントをサポートしたいサーバーは、旧SSEの
/sse・/messagesエンドポイントと、新しいMCPエンドポイントを並行して提供する必要があります。実装例
go.modポイント解説
newMCPServer()を関数にする理由getServer func(*http.Request) *mcp.Serverのシグネチャに合わせています。リクエストごとに新しいサーバーインスタンスを返すことで、セッション分離が可能です。同じインスタンスを返しても問題ありません。SSEHandlerのエンドポイント
mcp.NewSSEHandlerは内部でパスを自動判別します。/sseをGETされたらSSEストリームを確立し、クライアントへ/message?sessionId=xxxというエンドポイントを通知します。そのため/messageも同じハンドラーに向けておく必要があります:SDKバージョンの確認
v1.4.0以降では
2024-11-05,2025-03-26,2025-06-18,2025-11-25の全仕様バージョンをサポートしています。最新のv1.4.0を使うのが安全です。クライアントからの接続確認
Cursor、Claude Desktop等の古いクライアントはSSE側、最新のクライアントやClaude for Chromeは自動的にStreamable HTTPを試みてフォールバックします。