Skip to content

MCPサーバーのSSE対応 #211

@o-ga09

Description

@o-ga09

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を試みてフォールバックします。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions