@@ -33,6 +33,13 @@ type sseSession struct {
3333// content. This can be used to inject context values from headers, for example. 
3434type  SSEContextFunc  func (ctx  context.Context , r  * http.Request ) context.Context 
3535
36+ // DynamicBasePathFunc allows the user to provide a function to generate the 
37+ // base path for a given request and sessionID. This is useful for cases where 
38+ // the base path is not known at the time of SSE server creation, such as when 
39+ // using a reverse proxy or when the base path is dynamically generated. The 
40+ // function should return the base path (e.g., "/mcp/tenant123"). 
41+ type  DynamicBasePathFunc  func (r  * http.Request , sessionID  string ) string 
42+ 
3643func  (s  * sseSession ) SessionID () string  {
3744	return  s .sessionID 
3845}
@@ -68,6 +75,9 @@ type SSEServer struct {
6875	keepAliveInterval  time.Duration 
6976
7077	mu  sync.RWMutex 
78+ 
79+ 	// user-provided function for determining the dynamic base path 
80+ 	dynamicBasePathFunc  DynamicBasePathFunc 
7181}
7282
7383// SSEOption defines a function type for configuring SSEServer 
@@ -96,7 +106,7 @@ func WithBaseURL(baseURL string) SSEOption {
96106	}
97107}
98108
99- // Add a new option for setting base path 
109+ // Add a new option for setting a static  base path 
100110func  WithBasePath (basePath  string ) SSEOption  {
101111	return  func (s  * SSEServer ) {
102112		// Ensure the path starts with / and doesn't end with / 
@@ -107,6 +117,24 @@ func WithBasePath(basePath string) SSEOption {
107117	}
108118}
109119
120+ // WithDynamicBasePath accepts a function for generating the base path. This is 
121+ // useful for cases where the base path is not known at the time of SSE server 
122+ // creation, such as when using a reverse proxy or when the server is mounted 
123+ // at a dynamic path. 
124+ func  WithDynamicBasePath (fn  DynamicBasePathFunc ) SSEOption  {
125+ 	return  func (s  * SSEServer ) {
126+ 		if  fn  !=  nil  {
127+ 			s .dynamicBasePathFunc  =  func (r  * http.Request , sid  string ) string  {
128+ 				bp  :=  fn (r , sid )
129+ 				if  ! strings .HasPrefix (bp , "/" ) {
130+ 					bp  =  "/"  +  bp 
131+ 				}
132+ 				return  strings .TrimSuffix (bp , "/" )
133+ 			}
134+ 		}
135+ 	}
136+ }
137+ 
110138// WithMessageEndpoint sets the message endpoint path 
111139func  WithMessageEndpoint (endpoint  string ) SSEOption  {
112140	return  func (s  * SSEServer ) {
@@ -308,7 +336,8 @@ func (s *SSEServer) handleSSE(w http.ResponseWriter, r *http.Request) {
308336	}
309337
310338	// Send the initial endpoint event 
311- 	fmt .Fprintf (w , "event: endpoint\n data: %s\r \n \r \n " , s .GetMessageEndpointForClient (sessionID ))
339+ 	endpoint  :=  s .GetMessageEndpointForClient (r , sessionID )
340+ 	fmt .Fprintf (w , "event: endpoint\n data: %s\r \n \r \n " , endpoint )
312341	flusher .Flush ()
313342
314343	// Main event loop - this runs in the HTTP handler goroutine 
@@ -328,13 +357,20 @@ func (s *SSEServer) handleSSE(w http.ResponseWriter, r *http.Request) {
328357}
329358
330359// GetMessageEndpointForClient returns the appropriate message endpoint URL with session ID 
331- // based on the useFullURLForMessageEndpoint configuration. 
332- func  (s  * SSEServer ) GetMessageEndpointForClient (sessionID  string ) string  {
333- 	messageEndpoint  :=  s .messageEndpoint 
334- 	if  s .useFullURLForMessageEndpoint  {
335- 		messageEndpoint  =  s .CompleteMessageEndpoint ()
360+ // for the given request. This is the canonical way to compute the message endpoint for a client. 
361+ // It handles both dynamic and static path modes, and honors the WithUseFullURLForMessageEndpoint flag. 
362+ func  (s  * SSEServer ) GetMessageEndpointForClient (r  * http.Request , sessionID  string ) string  {
363+ 	basePath  :=  s .basePath 
364+ 	if  s .dynamicBasePathFunc  !=  nil  {
365+ 		basePath  =  s .dynamicBasePathFunc (r , sessionID )
336366	}
337- 	return  fmt .Sprintf ("%s?sessionId=%s" , messageEndpoint , sessionID )
367+ 
368+ 	endpointPath  :=  basePath  +  s .messageEndpoint 
369+ 	if  s .useFullURLForMessageEndpoint  &&  s .baseURL  !=  ""  {
370+ 		endpointPath  =  s .baseURL  +  endpointPath 
371+ 	}
372+ 
373+ 	return  fmt .Sprintf ("%s?sessionId=%s" , endpointPath , sessionID )
338374}
339375
340376// handleMessage processes incoming JSON-RPC messages from clients and sends responses 
@@ -447,6 +483,9 @@ func (s *SSEServer) GetUrlPath(input string) (string, error) {
447483}
448484
449485func  (s  * SSEServer ) CompleteSseEndpoint () string  {
486+ 	if  s .dynamicBasePathFunc  !=  nil  {
487+ 		panic ("CompleteSseEndpoint cannot be used with WithDynamicBasePath. Use dynamic path logic in your router." )
488+ 	}
450489	return  s .baseURL  +  s .basePath  +  s .sseEndpoint 
451490}
452491
@@ -459,6 +498,9 @@ func (s *SSEServer) CompleteSsePath() string {
459498}
460499
461500func  (s  * SSEServer ) CompleteMessageEndpoint () string  {
501+ 	if  s .dynamicBasePathFunc  !=  nil  {
502+ 		panic ("CompleteMessageEndpoint cannot be used with WithDynamicBasePath. Use dynamic path logic in your router." )
503+ 	}
462504	return  s .baseURL  +  s .basePath  +  s .messageEndpoint 
463505}
464506
@@ -470,8 +512,69 @@ func (s *SSEServer) CompleteMessagePath() string {
470512	return  path 
471513}
472514
515+ // SSEHandler returns an http.Handler for the SSE endpoint. 
516+ // 
517+ // This method allows you to mount the SSE handler at any arbitrary path 
518+ // using your own router (e.g. net/http, gorilla/mux, chi, etc.). It is 
519+ // intended for advanced scenarios where you want to control the routing or 
520+ // support dynamic segments. 
521+ // 
522+ // IMPORTANT: When using this handler in advanced/dynamic mounting scenarios, 
523+ // you must use the WithDynamicBasePath option to ensure the correct base path 
524+ // is communicated to clients. 
525+ // 
526+ // Example usage: 
527+ // 
528+ //	// Advanced/dynamic: 
529+ //	sseServer := NewSSEServer(mcpServer, 
530+ //		WithDynamicBasePath(func(r *http.Request, sessionID string) string { 
531+ //			tenant := r.PathValue("tenant") 
532+ //			return "/mcp/" + tenant 
533+ //		}), 
534+ //		WithBaseURL("http://localhost:8080") 
535+ //	) 
536+ //	mux.Handle("/mcp/{tenant}/sse", sseServer.SSEHandler()) 
537+ //	mux.Handle("/mcp/{tenant}/message", sseServer.MessageHandler()) 
538+ // 
539+ // For non-dynamic cases, use ServeHTTP method instead. 
540+ func  (s  * SSEServer ) SSEHandler () http.Handler  {
541+ 	return  http .HandlerFunc (s .handleSSE )
542+ }
543+ 
544+ // MessageHandler returns an http.Handler for the message endpoint. 
545+ // 
546+ // This method allows you to mount the message handler at any arbitrary path 
547+ // using your own router (e.g. net/http, gorilla/mux, chi, etc.). It is 
548+ // intended for advanced scenarios where you want to control the routing or 
549+ // support dynamic segments. 
550+ // 
551+ // IMPORTANT: When using this handler in advanced/dynamic mounting scenarios, 
552+ // you must use the WithDynamicBasePath option to ensure the correct base path 
553+ // is communicated to clients. 
554+ // 
555+ // Example usage: 
556+ // 
557+ //	// Advanced/dynamic: 
558+ //	sseServer := NewSSEServer(mcpServer, 
559+ //		WithDynamicBasePath(func(r *http.Request, sessionID string) string { 
560+ //			tenant := r.PathValue("tenant") 
561+ //			return "/mcp/" + tenant 
562+ //		}), 
563+ //		WithBaseURL("http://localhost:8080") 
564+ //	) 
565+ //	mux.Handle("/mcp/{tenant}/sse", sseServer.SSEHandler()) 
566+ //	mux.Handle("/mcp/{tenant}/message", sseServer.MessageHandler()) 
567+ // 
568+ // For non-dynamic cases, use ServeHTTP method instead. 
569+ func  (s  * SSEServer ) MessageHandler () http.Handler  {
570+ 	return  http .HandlerFunc (s .handleMessage )
571+ }
572+ 
473573// ServeHTTP implements the http.Handler interface. 
474574func  (s  * SSEServer ) ServeHTTP (w  http.ResponseWriter , r  * http.Request ) {
575+ 	if  s .dynamicBasePathFunc  !=  nil  {
576+ 		panic ("ServeHTTP cannot be used with WithDynamicBasePath. Use SSEHandler/MessageHandler and mount them with your router." )
577+ 	}
475578	path  :=  r .URL .Path 
476579	// Use exact path matching rather than Contains 
477580	ssePath  :=  s .CompleteSsePath ()
0 commit comments