package mcp import ( "context" "net/http" sdkmcp "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/rest" ) // McpServer defines the interface for Model Context Protocol servers using the official SDK type McpServer interface { // Start starts the HTTP server Start() // Stop stops the HTTP server Stop() } type mcpServerImpl struct { conf McpConf httpServer *rest.Server mcpServer *sdkmcp.Server options serverOptions } // NewMcpServer creates a new MCP server using the official SDK func NewMcpServer(c McpConf) McpServer { return NewMcpServerWithOptions(c) } // NewMcpServerWithOptions creates a new MCP server with optional customizations. func NewMcpServerWithOptions(c McpConf, opts ...McpOption) McpServer { serverOpts := defaultServerOptions() for _, opt := range opts { if opt != nil { opt.apply(&serverOpts) } } // Create the underlying rest HTTP server var httpServer *rest.Server if len(c.Mcp.Cors) == 0 { httpServer = rest.MustNewServer(c.RestConf) } else { httpServer = rest.MustNewServer(c.RestConf, rest.WithCors(c.Mcp.Cors...)) } // Set defaults if len(c.Mcp.Name) == 0 { c.Mcp.Name = c.Name } if len(c.Mcp.Version) == 0 { c.Mcp.Version = "1.0.0" } // Create the MCP SDK server impl := &sdkmcp.Implementation{ Name: c.Mcp.Name, Version: c.Mcp.Version, } mcpServer := sdkmcp.NewServer(impl, nil) s := &mcpServerImpl{ conf: c, httpServer: httpServer, mcpServer: mcpServer, options: serverOpts, } // Choose transport based on configuration if c.Mcp.UseStreamable { s.setupStreamableTransport() } else { s.setupSSETransport() } return s } // Start implements McpServer. func (s *mcpServerImpl) Start() { logx.Infof("Starting MCP server %s v%s on %s:%d", s.conf.Mcp.Name, s.conf.Mcp.Version, s.conf.Host, s.conf.Port) s.httpServer.Start() } // Stop implements McpServer. func (s *mcpServerImpl) Stop() { logx.Info("Stopping MCP server") s.httpServer.Stop() } // setupSSETransport configures the server to use SSE transport (2024-11-05 spec) func (s *mcpServerImpl) setupSSETransport() { // Create SSE handler that returns our MCP server for each connection handler := sdkmcp.NewSSEHandler(func(r *http.Request) *sdkmcp.Server { logx.Infof("New SSE connection from %s", r.RemoteAddr) return s.mcpServer }, nil) s.registerRoutes(s.wrapRequestMetadata(handler), s.conf.Mcp.SseEndpoint) } // setupStreamableTransport configures the server to use Streamable HTTP transport (2025-03-26 spec) func (s *mcpServerImpl) setupStreamableTransport() { // Create Streamable HTTP handler handler := sdkmcp.NewStreamableHTTPHandler(func(r *http.Request) *sdkmcp.Server { logx.Infof("New streamable connection from %s", r.RemoteAddr) return s.mcpServer }, nil) s.registerRoutes(s.wrapRequestMetadata(handler), s.conf.Mcp.MessageEndpoint) } func (s *mcpServerImpl) registerRoutes(handler http.Handler, endpoint string) { // Register the endpoint (handles both GET for SSE and POST for messages) s.httpServer.AddRoute(rest.Route{ Method: http.MethodGet, Path: endpoint, Handler: handler.ServeHTTP, }, rest.WithSSE(), rest.WithTimeout(s.conf.Mcp.SseTimeout)) s.httpServer.AddRoute(rest.Route{ Method: http.MethodPost, Path: endpoint, Handler: handler.ServeHTTP, }, rest.WithTimeout(s.conf.Mcp.MessageTimeout)) } func (s *mcpServerImpl) wrapRequestMetadata(next http.Handler) http.Handler { extractor := s.options.requestMetadataExtractor if extractor == nil { return next } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { metadata := normalizeRequestMetadata(extractor(r)) ctx := context.WithValue(r.Context(), requestMetadataCtxKey{}, metadata) next.ServeHTTP(w, r.WithContext(ctx)) }) }