10000 fix java mcp message endpoint (#75) · mark3labs/mcp-go@6d840a4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 6d840a4

Browse files
a67793581Buf Generate
andauthored
fix java mcp message endpoint (#75)
* fix java mcp message endpoint * fix java mcp message endpoint * fix java mcp message endpoint --------- Co-authored-by: Buf Generate <buf-generate@bondee.com>
1 parent 051cda5 commit 6d840a4

File tree

2 files changed

+113
-14
lines changed

2 files changed

+113
-14
lines changed

server/sse.go

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,15 @@ var _ ClientSession = (*sseSession)(nil)
5252
// SSEServer implements a Server-Sent Events (SSE) based MCP server.
5353
// It provides real-time communication capabilities over HTTP using the SSE protocol.
5454
type SSEServer struct {
55-
server *MCPServer
56-
baseURL string
57-
basePath string
58-
messageEndpoint string
59-
sseEndpoint string
60-
sessions sync.Map
61-
srv *http.Server
62-
contextFunc SSEContextFunc
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
6364
}
6465

6566
// SSEOption defines a function type for configuring SSEServer
@@ -106,6 +107,15 @@ func WithMessageEndpoint(endpoint string) SSEOption {
106107
}
107108
}
108109

110+
// WithUseFullURLForMessageEndpoint controls whether the SSE server returns a complete URL (including baseURL)
111+
// or just the path portion for the message endpoint. Set to false when clients will concatenate
112+
// the baseURL themselves to avoid malformed URLs like "http://localhost/mcphttp://localhost/mcp/message".
113+
func WithUseFullURLForMessageEndpoint(useFullURLForMessageEndpoint bool) SSEOption {
114+
return func(s *SSEServer) {
115+
s.useFullURLForMessageEndpoint = useFullURLForMessageEndpoint
116+
}
117+
}
118+
109119
// WithSSEEndpoint sets the SSE endpoint path
110120
func WithSSEEndpoint(endpoint string) SSEOption {
111121
return func(s *SSEServer) {
@@ -131,9 +141,10 @@ func WithSSEContextFunc(fn SSEContextFunc) SSEOption {
131141
// NewSSEServer creates a new SSE server instance with the given MCP server and options.
132142
func NewSSEServer(server *MCPServer, opts ...SSEOption) *SSEServer {
133143
s := &SSEServer{
134-
server: server,
135-
sseEndpoint: "/sse",
136-
messageEndpoint: "/message",
144+
server: server,
145+
sseEndpoint: "/sse",
146+
messageEndpoint: "/message",
147+
useFullURLForMessageEndpoint: true,
137148
}
138149

139150
// Apply all options
@@ -244,10 +255,8 @@ func (s *SSEServer) handleSSE(w http.ResponseWriter, r *http.Request) {
244255
}
245256
}()
246257

247-
messageEndpoint := fmt.Sprintf("%s?sessionId=%s", s.CompleteMessageEndpoint(), sessionID)
248-
249258
// Send the initial endpoint event
250-
fmt.Fprintf(w, "event: endpoint\ndata: %s\r\n\r\n", messageEndpoint)
259+
fmt.Fprintf(w, "event: endpoint\ndata: %s\r\n\r\n", s.GetMessageEndpointForClient(sessionID))
251260
flusher.Flush()
252261

253262
// Main event loop - this runs in the HTTP handler goroutine
@@ -264,6 +273,16 @@ func (s *SSEServer) handleSSE(w http.ResponseWriter, r *http.Request) {
264273
}
265274
}
266275

276+
// GetMessageEndpointForClient returns the appropriate message endpoint U 8000 RL with session ID
277+
// based on the useFullURLForMessageEndpoint configuration.
278+
func (s *SSEServer) GetMessageEndpointForClient(sessionID string) string {
279+
messageEndpoint := s.messageEndpoint
280+
if s.useFullURLForMessageEndpoint {
281+
messageEndpoint = s.CompleteMessageEndpoint()
282+
}
283+
return fmt.Sprintf("%s?sessionId=%s", messageEndpoint, sessionID)
284+
}
285+
267286
// handleMessage processes incoming JSON-RPC messages from clients and sends responses
268287
// back through both the SSE connection and HTTP response.
269288
func (s *SSEServer) handleMessage(w http.ResponseWriter, r *http.Request) {

server/sse_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,81 @@ func TestSSEServer(t *testing.T) {
418418
cancel()
419419
})
420420

421+
t.Run("test useFullURLForMessageEndpoint", func(t *testing.T) {
422+
mcpServer := NewMCPServer("test", "1.0.0")
423+
sseServer := NewSSEServer(mcpServer)
424+
425+
mux := http.NewServeMux()
426+
mux.Handle("/mcp/", sseServer)
427+
428+
ts := httptest.NewServer(mux)
429+
defer ts.Close()
430+
431+
sseServer.baseURL = ts.URL + "/mcp"
432+
sseServer.useFullURLForMessageEndpoint = false
433+
ctx, cancel := context.WithCancel(context.Background())
434+
defer cancel()
435+
436+
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/sse", sseServer.baseURL), nil)
437+
if err != nil {
438+
t.Fatalf("Failed to create request: %v", err)
439+
}
440+
441+
resp, err := http.DefaultClient.Do(req)
442+
if err != nil {
443+
t.Fatalf("Failed to connect to SSE endpoint: %v", err)
444+
}
445+
defer resp.Body.Close()
446+
447+
if resp.StatusCode != http.StatusOK {
448+
t.Errorf("Expected status 200, got %d", resp.StatusCode)
449+
}
450+
451+
// Read the endpoint event
452+
buf := make([]byte, 1024)
453+
n, err := resp.Body.Read(buf)
454+
if err != nil {
455+
t.Fatalf("Failed to read SSE response: %v", err)
456+
}
457+
458+
endpointEvent := string(buf[:n])
459+
messageURL := strings.TrimSpace(
460+
strings.Split(strings.Split(endpointEvent, "data: ")[1], "\n")[0],
461+
)
462+
if !strings.HasPrefix(messageURL, sseServer.messageEndpoint) {
463+
t.Errorf("Expected messageURL to be %s, got %s", sseServer.messageEndpoint, messageURL)
464+
}
465+
466+
// The messageURL should already be correct since we set the baseURL correctly
467+
// Test message endpoint
468+
initRequest := map[string]interface{}{
469+
"jsonrpc": "2.0",
470+
"id": 1,
471+
"method": "initialize",
472+
"params": map[string]interface{}{
473+
"protocolVersion": "2024-11-05",
474+
"clientInfo": map[string]interface{}{
475+
"name": "test-client",
476+
"version": "1.0.0",
477+
},
478+
},
479+
}
480+
requestBody, _ := json.Marshal(initRequest)
481+
482+
resp, err = http.Post(sseServer.baseURL+messageURL, "application/json", bytes.NewBuffer(requestBody))
483+
if err != nil {
484+
t.Fatalf("Failed to send message: %v", err)
485+
}
486+
defer resp.Body.Close()
487+
488+
if resp.StatusCode != http.StatusAccepted {
489+
t.Errorf("Expected status 202, got %d", resp.StatusCode)
490+
}
491+
492+
// Clean up SSE connection
493+
cancel()
494+
})
495+
421496
t.Run("works as http.Handler with custom basePath", func(t *testing.T) {
422497
mcpServer := NewMCPServer("test", "1.0.0")
423498
sseServer := NewSSEServer(mcpServer, WithBasePath("/mcp"))
@@ -621,11 +696,13 @@ func TestSSEServer(t *testing.T) {
621696
baseURL := "http://localhost:8080/test"
622697
messageEndpoint := "/message-test"
623698
sseEndpoint := "/sse-test"
699+
useFullURLForMessageEndpoint := false
624700
srv := &http.Server{}
625701
rands := []SSEOption{
626702
WithBasePath(basePath),
627703
WithBaseURL(baseURL),
628704
WithMessageEndpoint(messageEndpoint),
705+
WithUseFullURLForMessageEndpoint(useFullURLForMessageEndpoint),
629706
WithSSEEndpoint(sseEndpoint),
630707
WithHTTPServer(srv),
631708
}
@@ -641,6 +718,9 @@ func TestSSEServer(t *testing.T) {
641718
if sseServer.basePath != basePath {
642719
t.Fatalf("basePath %v, got: %v", basePath, sseServer.basePath)
643720
}
721+
if sseServer.useFullURLForMessageEndpoint != useFullURLForMessageEndpoint {
722+
t.Fatalf("useFullURLForMessageEndpoint %v, got: %v", useFullURLForMessageEndpoint, sseServer.useFullURLForMessageEndpoint)
723+
}
644724

645725
if sseServer.baseURL != baseURL {
646726
t.Fatalf("baseURL %v, got: %v", baseURL, sseServer.baseURL)

0 commit comments

Comments
 (0)
0