@@ -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,16 @@ 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+ 		s .dynamicBasePathFunc  =  fn 
127+ 	}
128+ }
129+ 
110130// WithMessageEndpoint sets the message endpoint path 
111131func  WithMessageEndpoint (endpoint  string ) SSEOption  {
112132	return  func (s  * SSEServer ) {
@@ -308,7 +328,8 @@ func (s *SSEServer) handleSSE(w http.ResponseWriter, r *http.Request) {
308328	}
309329
310330	// Send the initial endpoint event 
311- 	fmt .Fprintf (w , "event: endpoint\n data: %s\r \n \r \n " , s .GetMessageEndpointForClient (sessionID ))
331+ 	endpoint  :=  s .GetMessageEndpointForClient (r , sessionID )
332+ 	fmt .Fprintf (w , "event: endpoint\n data: %s\r \n \r \n " , endpoint )
312333	flusher .Flush ()
313334
314335	// Main event loop - this runs in the HTTP handler goroutine 
@@ -328,13 +349,20 @@ func (s *SSEServer) handleSSE(w http.ResponseWriter, r *http.Request) {
328349}
329350
330351// 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 ()
352+ // for the given request. This is the canonical way to compute the message endpoint for a client. 
353+ // It handles both dynamic and static path modes, and honors the WithUseFullURLForMessageEndpoint flag. 
354+ func  (s  * SSEServer ) GetMessageEndpointForClient (r  * http.Request , sessionID  string ) string  {
355+ 	basePath  :=  s .basePath 
356+ 	if  s .dynamicBasePathFunc  !=  nil  {
357+ 		basePath  =  s .dynamicBasePathFunc (r , sessionID )
358+ 	}
359+ 
360+ 	endpointPath  :=  basePath  +  s .messageEndpoint 
361+ 	if  s .useFullURLForMessageEndpoint  &&  s .baseURL  !=  ""  {
362+ 		endpointPath  =  s .baseURL  +  endpointPath 
336363	}
337- 	return  fmt .Sprintf ("%s?sessionId=%s" , messageEndpoint , sessionID )
364+ 
365+ 	return  fmt .Sprintf ("%s?sessionId=%s" , endpointPath , sessionID )
338366}
339367
340368// handleMessage processes incoming JSON-RPC messages from clients and sends responses 
@@ -447,6 +475,9 @@ func (s *SSEServer) GetUrlPath(input string) (string, error) {
447475}
448476
449477func  (s  * SSEServer ) CompleteSseEndpoint () string  {
478+ 	if  s .dynamicBasePathFunc  !=  nil  {
479+ 		panic ("CompleteSseEndpoint cannot be used with WithDynamicBasePath. Use dynamic path logic in your router." )
480+ 	}
450481	return  s .baseURL  +  s .basePath  +  s .sseEndpoint 
451482}
452483
@@ -459,6 +490,9 @@ func (s *SSEServer) CompleteSsePath() string {
459490}
460491
461492func  (s  * SSEServer ) CompleteMessageEndpoint () string  {
493+ 	if  s .dynamicBasePathFunc  !=  nil  {
494+ 		panic ("CompleteMessageEndpoint cannot be used with WithDynamicBasePath. Use dynamic path logic in your router." )
495+ 	}
462496	return  s .baseURL  +  s .basePath  +  s .messageEndpoint 
463497}
464498
@@ -470,8 +504,69 @@ func (s *SSEServer) CompleteMessagePath() string {
470504	return  path 
471505}
472506
507+ // SSEHandler returns an http.Handler for the SSE endpoint. 
508+ // 
509+ // This method allows you to mount the SSE handler at any arbitrary path 
510+ // using your own router (e.g. net/http, gorilla/mux, chi, etc.). It is 
511+ // intended for advanced scenarios where you want to control the routing or 
512+ // support dynamic segments. 
513+ // 
514+ // IMPORTANT: When using this handler in advanced/dynamic mounting scenarios, 
515+ // you must use the WithDynamicBasePath option to ensure the correct base path 
516+ // is communicated to clients. 
517+ // 
518+ // Example usage: 
519+ // 
520+ //	// Advanced/dynamic: 
521+ //	sseServer := NewSSEServer(mcpServer, 
522+ //		WithDynamicBasePath(func(r *http.Request, sessionID string) string { 
523+ //			tenant := r.PathValue("tenant") 
524+ //			return "/mcp/" + tenant 
525+ //		}), 
526+ //		WithBaseURL("http://localhost:8080") 
527+ //	) 
528+ //	mux.Handle("/mcp/{tenant}/sse", sseServer.SSEHandler()) 
529+ //	mux.Handle("/mcp/{tenant}/message", sseServer.MessageHandler()) 
530+ // 
531+ // For non-dynamic cases, use ServeHTTP method instead. 
532+ func  (s  * SSEServer ) SSEHandler () http.Handler  {
533+ 	return  http .HandlerFunc (s .handleSSE )
534+ }
535+ 
536+ // MessageHandler returns an http.Handler for the message endpoint. 
537+ // 
538+ // This method allows you to mount the message handler at any arbitrary path 
539+ // using your own router (e.g. net/http, gorilla/mux, chi, etc.). It is 
540+ // intended for advanced scenarios where you want to control the routing or 
541+ // support dynamic segments. 
542+ // 
543+ // IMPORTANT: When using this handler in advanced/dynamic mounting scenarios, 
544+ // you must use the WithDynamicBasePath option to ensure the correct base path 
545+ // is communicated to clients. 
546+ // 
547+ // Example usage: 
548+ // 
549+ //	// Advanced/dynamic: 
550+ //	sseServer := NewSSEServer(mcpServer, 
551+ //		WithDynamicBasePath(func(r *http.Request, sessionID string) string { 
552+ //			tenant := r.PathValue("tenant") 
553+ //			return "/mcp/" + tenant 
554+ //		}), 
555+ //		WithBaseURL("http://localhost:8080") 
556+ //	) 
557+ //	mux.Handle("/mcp/{tenant}/sse", sseServer.SSEHandler()) 
558+ //	mux.Handle("/mcp/{tenant}/message", sseServer.MessageHandler()) 
559+ // 
560+ // For non-dynamic cases, use ServeHTTP method instead. 
561+ func  (s  * SSEServer ) MessageHandler () http.Handler  {
562+ 	return  http .HandlerFunc (s .handleMessage )
563+ }
564+ 
473565// ServeHTTP implements the http.Handler interface. 
474566func  (s  * SSEServer ) ServeHTTP (w  http.ResponseWriter , r  * http.Request ) {
567+ 	if  s .dynamicBasePathFunc  !=  nil  {
568+ 		panic ("ServeHTTP cannot be used with WithDynamicBasePath. Use SSEHandler/MessageHandler and mount them with your router." )
569+ 	}
475570	path  :=  r .URL .Path 
476571	// Use exact path matching rather than Contains 
477572	ssePath  :=  s .CompleteSsePath ()
0 commit comments