Skip to content

Commit 37497a4

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 37497a4

File tree

3 files changed

+132
-66
lines changed

3 files changed

+132
-66
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: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,20 @@ func (s *SSEServer) setContextFunc(fn HTTPContextFunc) {
136136
s.contextFunc = fn
137137
}
138138

139-
// SSEOption defines a function type for configuring SSEServer
140-
type SSEOption func(*SSEServer)
139+
// WithBasePath adds a new option for setting a static base path.
140+
//
141+
// Deprecated: Use WithStaticBasePath instead. This will be removed in a future version.
142+
//
143+
//go:deprecated
144+
func WithBasePath(basePath string) SSEOption {
145+
return WithStaticBasePath(basePath)
146+
}
141147

142148
// WithMessageEndpoint sets the message endpoint path
143149
func WithMessageEndpoint(endpoint string) SSEOption {
144-
return func(s *SSEServer) {
150+
return sseOption(func(s *SSEServer) {
145151
s.messageEndpoint = endpoint
146-
}
152+
})
147153
}
148154

149155
// WithAppendQueryToMessageEndpoint configures the SSE server to append the original request's
@@ -152,25 +158,25 @@ func WithMessageEndpoint(endpoint string) SSEOption {
152158
// SSE connection request and carry them over to subsequent message requests, maintaining
153159
// context or authentication details across the communication channel.
154160
func WithAppendQueryToMessageEndpoint() SSEOption {
155-
return func(s *SSEServer) {
161+
return sseOption(func(s *SSEServer) {
156162
s.appendQueryToMessageEndpoint = true
157-
}
163+
})
158164
}
159165

160166
// WithUseFullURLForMessageEndpoint controls whether the SSE server returns a complete URL (including baseURL)
161167
// or just the path portion for the message endpoint. Set to false when clients will concatenate
162168
// the baseURL themselves to avoid malformed URLs like "http://localhost/mcphttp://localhost/mcp/message".
163169
func WithUseFullURLForMessageEndpoint(useFullURLForMessageEndpoint bool) SSEOption {
164-
return func(s *SSEServer) {
170+
return sseOption(func(s *SSEServer) {
165171
s.useFullURLForMessageEndpoint = useFullURLForMessageEndpoint
166-
}
172+
})
167173
}
168174

169175
// WithSSEEndpoint sets the SSE endpoint path
170176
func WithSSEEndpoint(endpoint string) SSEOption {
171-
return func(s *SSEServer) {
177+
return sseOption(func(s *SSEServer) {
172178
s.sseEndpoint = endpoint
173-
}
179+
})
174180
}
175181

176182
// WithSSEContextFunc sets a function that will be called to customise the context
@@ -179,13 +185,13 @@ func WithSSEEndpoint(endpoint string) SSEOption {
179185
// Deprecated: Use WithContextFunc instead. This will be removed in a future version.
180186
//go:deprecated
181187
func WithSSEContextFunc(fn SSEContextFunc) SSEOption {
182-
return func(s *SSEServer) {
183-
WithHTTPContextFunc(HTTPContextFunc(fn))(s)
184-
}
188+
return sseOption(func(s *SSEServer) {
189+
WithHTTPContextFunc(HTTPContextFunc(fn)).applyToSSE(s)
190+
})
185191
}
186192

187193
// NewSSEServer creates a new SSE server instance with the given MCP server and options.
188-
func NewSSEServer(server *MCPServer, opts ...interface{}) *SSEServer {
194+
func NewSSEServer(server *MCPServer, opts ...SSEOption) *SSEServer {
189195
s := &SSEServer{
190196
server: server,
191197
sseEndpoint: "/sse",
@@ -197,22 +203,15 @@ func NewSSEServer(server *MCPServer, opts ...interface{}) *SSEServer {
197203

198204
// Apply all options
199205
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-
}
206+
opt.applyToSSE(s)
208207
}
209208

210209
return s
211210
}
212211

213212
// NewTestServer creates a test server for testing purposes.
214213
// It takes the same options as NewSSEServer (variadic ...interface{}).
215-
func NewTestServer(server *MCPServer, opts ...interface{}) *httptest.Server {
214+
func NewTestServer(server *MCPServer, opts ...SSEOption) *httptest.Server {
216215
sseServer := NewSSEServer(server, opts...)
217216
testServer := httptest.NewServer(sseServer)
218217
sseServer.baseURL = testServer.URL

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)