Skip to content

Commit 389ad2c

Browse files
committed
refactor(server): introduce improved option system for HTTP servers
- Replace variadic interface{} options in NewSSEServer with a type-safe SSEOption interface. - Add HTTPServerOption, SSEOption, StreamableHTTPOption, and CommonHTTPServerOption interfaces to support extensible, type-safe server configuration. - Refactor all With* option constructors in sse.go and http_transport_options.go to use the new option interfaces and wrappers. - Update NewSSEServer and NewTestServer to accept ...SSEOption. - Prepare codebase for future server types (e.g., StreamableHTTPServer) and shared/common options.
1 parent c6a3162 commit 389ad2c

File tree

3 files changed

+180
-65
lines changed

3 files changed

+180
-65
lines changed

server/http_transport_options.go

Lines changed: 108 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,69 @@ type httpTransportConfigurable interface {
2525
// HTTPTransportOption is a function that configures an httpTransportConfigurable.
2626
type HTTPTransportOption func(httpTransportConfigurable)
2727

28+
// Option interfaces and wrappers for server configuration
29+
// Base option interface
30+
type HTTPServerOption interface {
31+
isHTTPServerOption()
32+
}
33+
34+
// SSE-specific option interface
35+
type SSEOption interface {
36+
HTTPServerOption
37+
applyToSSE(*SSEServer)
38+
}
39+
40+
// StreamableHTTP-specific option interface
41+
type StreamableHTTPOption interface {
42+
HTTPServerOption
43+
applyToStreamableHTTP(*StreamableHTTPServer)
44+
}
45+
46+
// Common options that work with both server types
47+
type CommonHTTPServerOption interface {
48+
SSEOption
49+
StreamableHTTPOption
50+
}
51+
52+
// Wrapper for SSE-specific functional options
53+
type sseOption func(*SSEServer)
54+
55+
func (o sseOption) isHTTPServerOption() {}
56+
func (o sseOption) applyToSSE(s *SSEServer) { o(s) }
57+
58+
// Wrapper for StreamableHTTP-specific functional options
59+
type streamableHTTPOption func(*StreamableHTTPServer)
60+
61+
func (o streamableHTTPOption) isHTTPServerOption() {}
62+
func (o streamableHTTPOption) applyToStreamableHTTP(s *StreamableHTTPServer) { o(s) }
63+
64+
// Refactor commonOption to use a single apply func(httpTransportConfigurable)
65+
type commonOption struct {
66+
apply func(httpTransportConfigurable)
67+
}
68+
69+
func (o commonOption) isHTTPServerOption() {}
70+
func (o commonOption) applyToSSE(s *SSEServer) { o.apply(s) }
71+
func (o commonOption) applyToStreamableHTTP(s *StreamableHTTPServer) { o.apply(s) }
72+
73+
// TODO: Implement StreamableHTTPServer in the future
74+
type StreamableHTTPServer struct{}
75+
76+
// Add stub methods to satisfy httpTransportConfigurable
77+
78+
func (s *StreamableHTTPServer) setBasePath(string) {}
79+
func (s *StreamableHTTPServer) setDynamicBasePath(DynamicBasePathFunc) {}
80+
func (s *StreamableHTTPServer) setKeepAliveInterval(time.Duration) {}
81+
func (s *StreamableHTTPServer) setKeepAlive(bool) {}
82+
func (s *StreamableHTTPServer) setContextFunc(HTTPContextFunc) {}
83+
2884
// WithStaticBasePath adds a new option for setting a static base path.
2985
// This is useful for mounting the server at a known, fixed path.
30-
func WithStaticBasePath(basePath string) HTTPTransportOption {
31-
return func(c httpTransportConfigurable) {
32-
c.setBasePath(basePath)
86+
func WithStaticBasePath(basePath string) CommonHTTPServerOption {
87+
return commonOption{
88+
apply: func(c httpTransportConfigurable) {
89+
c.setBasePath(basePath)
90+
},
3391
}
3492
}
3593

@@ -43,71 +101,80 @@ type DynamicBasePathFunc func(r *http.Request, sessionID string) string
43101
// WithDynamicBasePath accepts a function for generating the base path.
44102
// This is useful for cases where the base path is not known at the time of server creation,
45103
// such as when using a reverse proxy or when the server is mounted at a dynamic path.
46-
func WithDynamicBasePath(fn DynamicBasePathFunc) HTTPTransportOption {
47-
return func(c httpTransportConfigurable) {
48-
c.setDynamicBasePath(fn)
104+
func WithDynamicBasePath(fn DynamicBasePathFunc) CommonHTTPServerOption {
105+
return commonOption{
106+
apply: func(c httpTransportConfigurable) {
107+
c.setDynamicBasePath(fn)
108+
},
49109
}
50110
}
51111

52112
// WithKeepAliveInterval sets the keep-alive interval for the transport.
53113
// When enabled, the server will periodically send ping events to keep the connection alive.
54-
func WithKeepAliveInterval(interval time.Duration) HTTPTransportOption {
55-
return func(c httpTransportConfigurable) {
56-
c.setKeepAliveInterval(interval)
114+
func WithKeepAliveInterval(interval time.Duration) CommonHTTPServerOption {
115+
return commonOption{
116+
apply: func(c httpTransportConfigurable) {
117+
c.setKeepAliveInterval(interval)
118+
},
57119
}
58120
}
59121

60122
// WithKeepAlive enables or disables keep-alive for the transport.
61123
// When enabled, the server will send periodic keep-alive events to clients.
62-
func WithKeepAlive(keepAlive bool) HTTPTransportOption {
63-
return func(c httpTransportConfigurable) {
64-
c.setKeepAlive(keepAlive)
124+
func WithKeepAlive(keepAlive bool) CommonHTTPServerOption {
125+
return commonOption{
126+
apply: func(c httpTransportConfigurable) {
127+
c.setKeepAlive(keepAlive)
128+
},
65129
}
66130
}
67131

68132
// WithHTTPContextFunc sets a function that will be called to customize the context
69133
// for the server using the incoming request. This is useful for injecting
70134
// context values from headers or other request properties.
71-
func WithHTTPContextFunc(fn HTTPContextFunc) HTTPTransportOption {
72-
return func(c httpTransportConfigurable) {
73-
c.setContextFunc(fn)
135+
func WithHTTPContextFunc(fn HTTPContextFunc) CommonHTTPServerOption {
136+
return commonOption{
137+
apply: func(c httpTransportConfigurable) {
138+
c.setContextFunc(fn)
139+
},
74140
}
75141
}
76142

77143
// WithBaseURL sets the base URL for the HTTP transport server.
78144
// This is useful for configuring the externally visible base URL for clients.
79-
func WithBaseURL(baseURL string) HTTPTransportOption {
80-
return func(c httpTransportConfigurable) {
81-
if s, ok := c.(*SSEServer); ok {
82-
if baseURL != "" {
83-
u, err := url.Parse(baseURL)
84-
if err != nil {
85-
return
86-
}
87-
if u.Scheme != "http" && u.Scheme != "https" {
88-
return
89-
}
90-
if u.Host == "" || strings.HasPrefix(u.Host, ":") {
91-
return
92-
}
93-
if len(u.Query()) > 0 {
94-
return
145+
func WithBaseURL(baseURL string) CommonHTTPServerOption {
146+
return commonOption{
147+
apply: func(c httpTransportConfigurable) {
148+
if s, ok := c.(*SSEServer); ok {
149+
if baseURL != "" {
150+
u, err := url.Parse(baseURL)
151+
if err != nil {
152+
return
153+
}
154+
if u.Scheme != "http" && u.Scheme != "https" {
155+
return
156+
}
157+
if u.Host == "" || strings.HasPrefix(u.Host, ":") {
158+
return
159+
}
160+
if len(u.Query()) > 0 {
161+
return
162+
}
95163
}
164+
s.baseURL = strings.TrimSuffix(baseURL, "/")
96165
}
97-
s.baseURL = strings.TrimSuffix(baseURL, "/")
98-
}
99-
// For future protocols, add similar logic here
166+
},
100167
}
101168
}
102169

103170
// WithHTTPServer sets the HTTP server instance for the transport.
104171
// This is useful for advanced scenarios where you want to provide your own http.Server.
105-
func WithHTTPServer(srv *http.Server) HTTPTransportOption {
106-
return func(c httpTransportConfigurable) {
107-
if s, ok := c.(*SSEServer); ok {
108-
s.srv = srv
109-
}
110-
// For future protocols, add similar logic here
172+
func WithHTTPServer(srv *http.Server) CommonHTTPServerOption {
173+
return commonOption{
174+
apply: func(c httpTransportConfigurable) {
175+
if s, ok := c.(*SSEServer); ok {
176+
s.srv = srv
177+
}
178+
},
111179
}
112180
}
113-

server/sse.go

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,51 @@ import (
1818
"github.com/mark3labs/mcp-go/mcp"
1919
)
2020

21+
// Option interfaces and wrappers for server configuration
22+
// Base option interface
23+
type HTTPServerOption interface {
24+
isHTTPServerOption()
25+
}
26+
27+
// SSE-specific option interface
28+
type SSEOption interface {
29+
HTTPServerOption
30+
applyToSSE(*SSEServer)
31+
}
32+
33+
// StreamableHTTP-specific option interface
34+
type StreamableHTTPOption interface {
35+
HTTPServerOption
36+
applyToStreamableHTTP(*StreamableHTTPServer)
37+
}
38+
39+
// Common options that work with both server types
40+
type CommonHTTPServerOption interface {
41+
SSEOption
42+
StreamableHTTPOption
43+
}
44+
45+
// Wrapper for SSE-specific functional options
46+
type sseOption func(*SSEServer)
47+
48+
func (o sseOption) isHTTPServerOption() {}
49+
func (o sseOption) applyToSSE(s *SSEServer) { o(s) }
50+
51+
// Wrapper for StreamableHTTP-specific functional options
52+
type streamableHTTPOption func(*StreamableHTTPServer)
53+
54+
func (o streamableHTTPOption) isHTTPServerOption() {}
55+
func (o streamableHTTPOption) applyToStreamableHTTP(s *StreamableHTTPServer) { o(s) }
56+
57+
// Refactor commonOption to use a single apply func(httpTransportConfigurable)
58+
type commonOption struct {
59+
apply func(httpTransportConfigurable)
60+
}
61+
62+
func (o commonOption) isHTTPServerOption() {}
63+
func (o commonOption) applyToSSE(s *SSEServer) { o.apply(s) }
64+
func (o commonOption) applyToStreamableHTTP(s *StreamableHTTPServer) { o.apply(s) }
65+
2166
// sseSession represents an active SSE connection.
2267
type sseSession struct {
2368
writer http.ResponseWriter
@@ -137,13 +182,13 @@ func (s *SSEServer) setContextFunc(fn HTTPContextFunc) {
137182
}
138183

139184
// SSEOption defines a function type for configuring SSEServer
140-
type SSEOption func(*SSEServer)
185+
// type SSEOption func(*SSEServer)
141186

142187
// WithMessageEndpoint sets the message endpoint path
143188
func WithMessageEndpoint(endpoint string) SSEOption {
144-
return func(s *SSEServer) {
189+
return sseOption(func(s *SSEServer) {
145190
s.messageEndpoint = endpoint
146-
}
191+
})
147192
}
148193

149194
// WithAppendQueryToMessageEndpoint configures the SSE server to append the original request's
@@ -152,25 +197,25 @@ func WithMessageEndpoint(endpoint string) SSEOption {
152197
// SSE connection request and carry them over to subsequent message requests, maintaining
153198
// context or authentication details across the communication channel.
154199
func WithAppendQueryToMessageEndpoint() SSEOption {
155-
return func(s *SSEServer) {
200+
return sseOption(func(s *SSEServer) {
156201
s.appendQueryToMessageEndpoint = true
157-
}
202+
})
158203
}
159204

160205
// WithUseFullURLForMessageEndpoint controls whether the SSE server returns a complete URL (including baseURL)
161206
// or just the path portion for the message endpoint. Set to false when clients will concatenate
162207
// the baseURL themselves to avoid malformed URLs like "http://localhost/mcphttp://localhost/mcp/message".
163208
func WithUseFullURLForMessageEndpoint(useFullURLForMessageEndpoint bool) SSEOption {
164-
return func(s *SSEServer) {
209+
return sseOption(func(s *SSEServer) {
165210
s.useFullURLForMessageEndpoint = useFullURLForMessageEndpoint
166-
}
211+
})
167212
}
168213

169214
// WithSSEEndpoint sets the SSE endpoint path
170215
func WithSSEEndpoint(endpoint string) SSEOption {
171-
return func(s *SSEServer) {
216+
return sseOption(func(s *SSEServer) {
172217
s.sseEndpoint = endpoint
173-
}
218+
})
174219
}
175220

176221
// WithSSEContextFunc sets a function that will be called to customise the context
@@ -179,13 +224,13 @@ func WithSSEEndpoint(endpoint string) SSEOption {
179224
// Deprecated: Use WithContextFunc instead. This will be removed in a future version.
180225
//go:deprecated
181226
func WithSSEContextFunc(fn SSEContextFunc) SSEOption {
182-
return func(s *SSEServer) {
183-
WithHTTPContextFunc(HTTPContextFunc(fn))(s)
184-
}
227+
return sseOption(func(s *SSEServer) {
228+
WithHTTPContextFunc(HTTPContextFunc(fn)).applyToSSE(s)
229+
})
185230
}
186231

187232
// NewSSEServer creates a new SSE server instance with the given MCP server and options.
188-
func NewSSEServer(server *MCPServer, opts ...interface{}) *SSEServer {
233+
func NewSSEServer(server *MCPServer, opts ...SSEOption) *SSEServer {
189234
s := &SSEServer{
190235
server: server,
191236
sseEndpoint: "/sse",
@@ -197,22 +242,15 @@ func NewSSEServer(server *MCPServer, opts ...interface{}) *SSEServer {
197242

198243
// Apply all options
199244
for _, opt := range opts {
200-
switch o := opt.(type) {
201-
case SSEOption:
202-
o(s)
203-
case HTTPTransportOption:
204-
o(s)
205-
default:
206-
// Optionally: log or panic for unknown option type
207-
}
245+
opt.applyToSSE(s)
208246
}
209247

210248
return s
211249
}
212250

213251
// NewTestServer creates a test server for testing purposes.
214252
// It takes the same options as NewSSEServer (variadic ...interface{}).
215-
func NewTestServer(server *MCPServer, opts ...interface{}) *httptest.Server {
253+
func NewTestServer(server *MCPServer, opts ...SSEOption) *httptest.Server {
216254
sseServer := NewSSEServer(server, opts...)
217255
testServer := httptest.NewServer(sseServer)
218256
sseServer.baseURL = testServer.URL
@@ -655,3 +693,13 @@ func normalizeURLPath(elem ...string) string {
655693

656694
return joined
657695
}
696+
697+
// TODO: Implement StreamableHTTPServer in the future
698+
type StreamableHTTPServer struct{}
699+
// Add stub methods to satisfy httpTransportConfigurable
700+
701+
func (s *StreamableHTTPServer) setBasePath(string) {}
702+
func (s *StreamableHTTPServer) setDynamicBasePath(DynamicBasePathFunc) {}
703+
func (s *StreamableHTTPServer) setKeepAliveInterval(time.Duration) {}
704+
func (s *StreamableHTTPServer) setKeepAlive(bool) {}
705+
func (s *StreamableHTTPServer) setContextFunc(HTTPContextFunc) {}

server/sse_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ func TestSSEServer(t *testing.T) {
715715
sseEndpoint := "/sse-test"
716716
useFullURLForMessageEndpoint := false
717717
srv := &http.Server{}
718-
rands := []interface {}{
718+
rands := []SSEOption{
719719
WithStaticBasePath(basePath),
720720
WithBaseURL(baseURL),
721721
WithMessageEndpoint(messageEndpoint),
@@ -725,7 +725,7 @@ func TestSSEServer(t *testing.T) {
725725
}
726726
for i := 0; i < 100; i++ {
727727

728-
var options []interface {}
728+
var options []SSEOption
729729
for i2 := 0; i2 < 100; i2++ {
730730
index := rand.Intn(len(rands))
731731
options = append(options, rands[index])

0 commit comments

Comments
 (0)