@@ -10,6 +10,7 @@ import (
10
10
"strings"
11
11
"sync"
12
12
"sync/atomic"
13
+ "time"
13
14
14
15
"github.com/google/uuid"
15
16
"github.com/mark3labs/mcp-go/mcp"
@@ -52,15 +53,18 @@ var _ ClientSession = (*sseSession)(nil)
52
53
// SSEServer implements a Server-Sent Events (SSE) based MCP server.
53
54
// It provides real-time communication capabilities over HTTP using the SSE protocol.
54
55
type SSEServer struct {
55
- server * MCPServer
56
- baseURL string
57
- basePath string
58
- messageEndpoint string
59
- useFullURLForMessageEndpoint bool
60
- sseEndpoint string
61
- sessions sync.Map
62
- srv * http.Server
63
- contextFunc SSEContextFunc
56
+ server * MCPServer
57
+ baseURL string
58
+ basePath string
59
+ useFullURLForMessageEndpoint bool
60
+ messageEndpoint string
61
+ sseEndpoint string
62
+ sessions sync.Map
63
+ srv * http.Server
64
+ contextFunc SSEContextFunc
65
+
66
+ keepAlive bool
67
+ keepAliveInterval time.Duration
64
68
}
65
69
66
70
// SSEOption defines a function type for configuring SSEServer
@@ -130,6 +134,19 @@ func WithHTTPServer(srv *http.Server) SSEOption {
130
134
}
131
135
}
132
136
137
+ func WithKeepAliveInterval (keepAliveInterval time.Duration ) SSEOption {
138
+ return func (s * SSEServer ) {
139
+ s .keepAlive = true
140
+ s .keepAliveInterval = keepAliveInterval
141
+ }
142
+ }
143
+
144
+ func WithKeepAlive (keepAlive bool ) SSEOption {
145
+ return func (s * SSEServer ) {
146
+ s .keepAlive = keepAlive
147
+ }
148
+ }
149
+
133
150
// WithContextFunc sets a function that will be called to customise the context
134
151
// to the server using the incoming request.
135
152
func WithSSEContextFunc (fn SSEContextFunc ) SSEOption {
@@ -141,10 +158,12 @@ func WithSSEContextFunc(fn SSEContextFunc) SSEOption {
141
158
// NewSSEServer creates a new SSE server instance with the given MCP server and options.
142
159
func NewSSEServer (server * MCPServer , opts ... SSEOption ) * SSEServer {
143
160
s := & SSEServer {
144
- server : server ,
145
- sseEndpoint : "/sse" ,
146
- messageEndpoint : "/messages" ,
147
- useFullURLForMessageEndpoint : true ,
161
+ server : server ,
162
+ sseEndpoint : "/sse" ,
163
+ messageEndpoint : "/message" ,
164
+ useFullURLForMessageEndpoint : true ,
165
+ keepAlive : false ,
166
+ keepAliveInterval : 10 * time .Second ,
148
167
}
149
168
150
169
// Apply all options
@@ -255,6 +274,26 @@ func (s *SSEServer) handleSSE(w http.ResponseWriter, r *http.Request) {
255
274
}
256
275
}()
257
276
277
+ // Start keep alive : ping
278
+ if s .keepAlive {
279
+ go func () {
280
+ ticker := time .NewTicker (s .keepAliveInterval )
281
+ defer ticker .Stop ()
282
+ for {
283
+ select {
284
+ case <- ticker .C :
285
+ //: ping - 2025-03-27 07:44:38.682659+00:00
286
+ session .eventQueue <- fmt .Sprintf (":ping - %s\n \n " , time .Now ().Format (time .RFC3339 ))
287
+ case <- session .done :
288
+ return
289
+ case <- r .Context ().Done ():
290
+ return
291
+ }
292
+ }
293
+ }()
294
+ }
295
+
296
+
258
297
// Send the initial endpoint event
259
298
fmt .Fprintf (w , "event: endpoint\n data: %s\r \n \r \n " , s .GetMessageEndpointForClient (sessionID ))
260
299
flusher .Flush ()
0 commit comments