Skip to content
This repository was archived by the owner on Feb 14, 2025. It is now read-only.

Commit c2ece20

Browse files
apappascstzolov
authored andcommitted
feat: Make SSE endpoint configurable in server transports
This commit updates the WebFluxSseServerTransport, WebMvcSseServerTransport, and HttpServletSseServerTransport classes to accept the SSE endpoint path as a constructor argument. This change provides more flexibility in configuring the SSE endpoint, allowing it to be customized instead of being hardcoded to "/sse". It also aligns the Java SDK's behavior with the Python SDK's SseServerTransport, which already supports this customization. A default SSE endpoint of "/sse" is maintained for backward compatibility. Also, addressed minor code quality issues identified by SonarLint and reordered modifiers for static final fields to conform with the Java Language Specification. Signed-off-by: Alexandros Pappas <[email protected]>
1 parent f3e3a65 commit c2ece20

File tree

10 files changed

+111
-58
lines changed

10 files changed

+111
-58
lines changed

mcp-transport/mcp-webflux-sse-transport/src/main/java/org/springframework/ai/mcp/client/transport/WebFluxSseClientTransport.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,31 +72,31 @@
7272
*/
7373
public class WebFluxSseClientTransport implements ClientMcpTransport {
7474

75-
private final static Logger logger = LoggerFactory.getLogger(WebFluxSseClientTransport.class);
75+
private static final Logger logger = LoggerFactory.getLogger(WebFluxSseClientTransport.class);
7676

7777
/**
7878
* Event type for JSON-RPC messages received through the SSE connection. The server
7979
* sends messages with this event type to transmit JSON-RPC protocol data.
8080
*/
81-
private final static String MESSAGE_EVENT_TYPE = "message";
81+
private static final String MESSAGE_EVENT_TYPE = "message";
8282

8383
/**
8484
* Event type for receiving the message endpoint URI from the server. The server MUST
8585
* send this event when a client connects, providing the URI where the client should
8686
* send its messages via HTTP POST.
8787
*/
88-
private final static String ENDPOINT_EVENT_TYPE = "endpoint";
88+
private static final String ENDPOINT_EVENT_TYPE = "endpoint";
8989

9090
/**
9191
* Default SSE endpoint path as specified by the MCP transport specification. This
9292
* endpoint is used to establish the SSE connection with the server.
9393
*/
94-
private final static String SSE_ENDPOINT = "/sse";
94+
private static final String SSE_ENDPOINT = "/sse";
9595

9696
/**
9797
* Type reference for parsing SSE events containing string data.
9898
*/
99-
private final static ParameterizedTypeReference<ServerSentEvent<String>> SSE_TYPE = new ParameterizedTypeReference<>() {
99+
private static final ParameterizedTypeReference<ServerSentEvent<String>> SSE_TYPE = new ParameterizedTypeReference<>() {
100100
};
101101

102102
/**

mcp-transport/mcp-webflux-sse-transport/src/main/java/org/springframework/ai/mcp/server/transport/WebFluxSseServerTransport.java

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,32 +57,35 @@
5757
* {@link Sinks} for thread-safe message broadcasting.
5858
*
5959
* @author Christian Tzolov
60+
* @author Alexandros Pappas
6061
* @see ServerMcpTransport
6162
* @see ServerSentEvent
6263
*/
6364
public class WebFluxSseServerTransport implements ServerMcpTransport {
6465

65-
private final static Logger logger = LoggerFactory.getLogger(WebFluxSseServerTransport.class);
66+
private static final Logger logger = LoggerFactory.getLogger(WebFluxSseServerTransport.class);
6667

6768
/**
6869
* Event type for JSON-RPC messages sent through the SSE connection.
6970
*/
70-
public final static String MESSAGE_EVENT_TYPE = "message";
71+
public static final String MESSAGE_EVENT_TYPE = "message";
7172

7273
/**
7374
* Event type for sending the message endpoint URI to clients.
7475
*/
75-
public final static String ENDPOINT_EVENT_TYPE = "endpoint";
76+
public static final String ENDPOINT_EVENT_TYPE = "endpoint";
7677

7778
/**
7879
* Default SSE endpoint path as specified by the MCP transport specification.
7980
*/
80-
public final static String SSE_ENDPOINT = "/sse";
81+
public static final String DEFAULT_SSE_ENDPOINT = "/sse";
8182

8283
private final ObjectMapper objectMapper;
8384

8485
private final String messageEndpoint;
8586

87+
private final String sseEndpoint;
88+
8689
private final RouterFunction<?> routerFunction;
8790

8891
/**
@@ -106,18 +109,34 @@ public class WebFluxSseServerTransport implements ServerMcpTransport {
106109
* setup. Must not be null.
107110
* @throws IllegalArgumentException if either parameter is null
108111
*/
109-
public WebFluxSseServerTransport(ObjectMapper objectMapper, String messageEndpoint) {
112+
public WebFluxSseServerTransport(ObjectMapper objectMapper, String messageEndpoint, String sseEndpoint) {
110113
Assert.notNull(objectMapper, "ObjectMapper must not be null");
111114
Assert.notNull(messageEndpoint, "Message endpoint must not be null");
115+
Assert.notNull(sseEndpoint, "SSE endpoint must not be null");
112116

113117
this.objectMapper = objectMapper;
114118
this.messageEndpoint = messageEndpoint;
119+
this.sseEndpoint = sseEndpoint;
115120
this.routerFunction = RouterFunctions.route()
116-
.GET(SSE_ENDPOINT, this::handleSseConnection)
117-
.POST(messageEndpoint, this::handleMessage)
121+
.GET(this.sseEndpoint, this::handleSseConnection)
122+
.POST(this.messageEndpoint, this::handleMessage)
118123
.build();
119124
}
120125

126+
/**
127+
* Constructs a new WebFlux SSE server transport instance with the default SSE
128+
* endpoint.
129+
* @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
130+
* of MCP messages. Must not be null.
131+
* @param messageEndpoint The endpoint URI where clients should send their JSON-RPC
132+
* messages. This endpoint will be communicated to clients during SSE connection
133+
* setup. Must not be null.
134+
* @throws IllegalArgumentException if either parameter is null
135+
*/
136+
public WebFluxSseServerTransport(ObjectMapper objectMapper, String messageEndpoint) {
137+
this(objectMapper, messageEndpoint, DEFAULT_SSE_ENDPOINT);
138+
}
139+
121140
/**
122141
* Configures the message handler for this transport. In the WebFlux SSE
123142
* implementation, this method stores the handler for processing incoming messages but
@@ -243,7 +262,7 @@ public Mono<Void> closeGracefully() {
243262
* <p>
244263
* The router function defines two endpoints:
245264
* <ul>
246-
* <li>GET {SSE_ENDPOINT} - For establishing SSE connections</li>
265+
* <li>GET {sseEndpoint} - For establishing SSE connections</li>
247266
* <li>POST {messageEndpoint} - For receiving client messages</li>
248267
* </ul>
249268
* @return The configured {@link RouterFunction} for handling HTTP requests
@@ -343,11 +362,7 @@ private Mono<ServerResponse> handleMessage(ServerRequest request) {
343362
.bodyValue(new McpError(error.getMessage()));
344363
});
345364
}
346-
catch (IllegalArgumentException e) {
347-
logger.error("Failed to deserialize message: {}", e.getMessage());
348-
return ServerResponse.badRequest().bodyValue(new McpError("Invalid message format"));
349-
}
350-
catch (IOException e) {
365+
catch (IllegalArgumentException | IOException e) {
351366
logger.error("Failed to deserialize message: {}", e.getMessage());
352367
return ServerResponse.badRequest().bodyValue(new McpError("Invalid message format"));
353368
}

mcp-transport/mcp-webmvc-sse-transport/src/main/java/org/springframework/ai/mcp/server/transport/WebMvcSseServerTransport.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,32 +70,35 @@
7070
* maintains its own SSE connection.
7171
*
7272
* @author Christian Tzolov
73+
* @author Alexandros Pappas
7374
* @see ServerMcpTransport
7475
* @see RouterFunction
7576
*/
7677
public class WebMvcSseServerTransport implements ServerMcpTransport {
7778

78-
private final static Logger logger = LoggerFactory.getLogger(WebMvcSseServerTransport.class);
79+
private static final Logger logger = LoggerFactory.getLogger(WebMvcSseServerTransport.class);
7980

8081
/**
8182
* Event type for JSON-RPC messages sent through the SSE connection.
8283
*/
83-
public final static String MESSAGE_EVENT_TYPE = "message";
84+
public static final String MESSAGE_EVENT_TYPE = "message";
8485

8586
/**
8687
* Event type for sending the message endpoint URI to clients.
8788
*/
88-
public final static String ENDPOINT_EVENT_TYPE = "endpoint";
89+
public static final String ENDPOINT_EVENT_TYPE = "endpoint";
8990

9091
/**
9192
* Default SSE endpoint path as specified by the MCP transport specification.
9293
*/
93-
public final static String SSE_ENDPOINT = "/sse";
94+
public static final String DEFAULT_SSE_ENDPOINT = "/sse";
9495

9596
private final ObjectMapper objectMapper;
9697

9798
private final String messageEndpoint;
9899

100+
private final String sseEndpoint;
101+
99102
private final RouterFunction<ServerResponse> routerFunction;
100103

101104
/**
@@ -122,18 +125,33 @@ public class WebMvcSseServerTransport implements ServerMcpTransport {
122125
* SSE connection's initial endpoint event.
123126
* @throws IllegalArgumentException if either objectMapper or messageEndpoint is null
124127
*/
125-
public WebMvcSseServerTransport(ObjectMapper objectMapper, String messageEndpoint) {
128+
public WebMvcSseServerTransport(ObjectMapper objectMapper, String messageEndpoint, String sseEndpoint) {
126129
Assert.notNull(objectMapper, "ObjectMapper must not be null");
127130
Assert.notNull(messageEndpoint, "Message endpoint must not be null");
131+
Assert.notNull(sseEndpoint, "SSE endpoint must not be null");
128132

129133
this.objectMapper = objectMapper;
130134
this.messageEndpoint = messageEndpoint;
135+
this.sseEndpoint = sseEndpoint;
131136
this.routerFunction = RouterFunctions.route()
132-
.GET(SSE_ENDPOINT, this::handleSseConnection)
133-
.POST(messageEndpoint, this::handleMessage)
137+
.GET(this.sseEndpoint, this::handleSseConnection)
138+
.POST(this.messageEndpoint, this::handleMessage)
134139
.build();
135140
}
136141

142+
/**
143+
* Constructs a new WebMvcSseServerTransport instance with the default SSE endpoint.
144+
* @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
145+
* of messages.
146+
* @param messageEndpoint The endpoint URI where clients should send their JSON-RPC
147+
* messages via HTTP POST. This endpoint will be communicated to clients through the
148+
* SSE connection's initial endpoint event.
149+
* @throws IllegalArgumentException if either objectMapper or messageEndpoint is null
150+
*/
151+
public WebMvcSseServerTransport(ObjectMapper objectMapper, String messageEndpoint) {
152+
this(objectMapper, messageEndpoint, DEFAULT_SSE_ENDPOINT);
153+
}
154+
137155
/**
138156
* Sets up the message handler for this transport. In the WebMVC SSE implementation,
139157
* this method only stores the handler for later use, as connections are initiated by

mcp/src/main/java/org/springframework/ai/mcp/client/McpAsyncClient.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
*/
6363
public class McpAsyncClient {
6464

65-
private final static Logger logger = LoggerFactory.getLogger(McpAsyncClient.class);
65+
private static final Logger logger = LoggerFactory.getLogger(McpAsyncClient.class);
6666

6767
private static TypeReference<Void> VOID_TYPE_REFERENCE = new TypeReference<>() {
6868
};
@@ -498,7 +498,7 @@ private NotificationHandler toolsChangeNotificationHandler(
498498
logger.error("Error handling tools list change notification", error);
499499
return Mono.empty();
500500
}).then(); // Convert to Mono<Void>
501-
};
501+
}
502502

503503
// --------------------------
504504
// Resources
@@ -619,7 +619,7 @@ private NotificationHandler resourcesChangeNotificationHandler(
619619
logger.error("Error handling resources list change notification", error);
620620
return Mono.empty();
621621
}).then();
622-
};
622+
}
623623

624624
// --------------------------
625625
// Prompts
@@ -680,7 +680,7 @@ private NotificationHandler promptsChangeNotificationHandler(
680680
return Mono.empty();
681681
}).then(); // Convert to Mono<Void>
682682
}; // @formatter:on
683-
};
683+
}
684684

685685
// --------------------------
686686
// Logging

mcp/src/main/java/org/springframework/ai/mcp/client/transport/HttpClientSseClientTransport.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,16 @@
7070
*/
7171
public class HttpClientSseClientTransport implements ClientMcpTransport {
7272

73-
private final static Logger logger = LoggerFactory.getLogger(HttpClientSseClientTransport.class);
73+
private static final Logger logger = LoggerFactory.getLogger(HttpClientSseClientTransport.class);
7474

7575
/** SSE event type for JSON-RPC messages */
76-
private final static String MESSAGE_EVENT_TYPE = "message";
76+
private static final String MESSAGE_EVENT_TYPE = "message";
7777

7878
/** SSE event type for endpoint discovery */
79-
private final static String ENDPOINT_EVENT_TYPE = "endpoint";
79+
private static final String ENDPOINT_EVENT_TYPE = "endpoint";
8080

8181
/** Default SSE endpoint path */
82-
private final static String SSE_ENDPOINT = "/sse";
82+
private static final String SSE_ENDPOINT = "/sse";
8383

8484
/** Base URI for the MCP server */
8585
private final String baseUri;
@@ -170,7 +170,7 @@ else if (MESSAGE_EVENT_TYPE.equals(event.type())) {
170170
handler.apply(Mono.just(message)).subscribe();
171171
}
172172
else {
173-
logger.error("Received unrecognized SSE event type: " + event.type());
173+
logger.error("Received unrecognized SSE event type: {}", event.type());
174174
}
175175
}
176176
catch (IOException e) {

mcp/src/main/java/org/springframework/ai/mcp/server/McpAsyncServer.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
*/
5555
public class McpAsyncServer {
5656

57-
private final static Logger logger = LoggerFactory.getLogger(McpAsyncServer.class);
57+
private static final Logger logger = LoggerFactory.getLogger(McpAsyncServer.class);
5858

5959
/**
6060
* The MCP session implementation that manages bidirectional JSON-RPC communication
@@ -279,7 +279,7 @@ private NotificationHandler rootsListChnagedNotificationHandler(
279279
return Mono.empty();
280280
}).then();
281281
};
282-
};
282+
}
283283

284284
// ---------------------------------------
285285
// Tool Management
@@ -352,9 +352,7 @@ public Mono<Void> notifyToolsListChanged() {
352352
private DefaultMcpSession.RequestHandler toolsListRequestHandler() {
353353
return params -> {
354354

355-
List<Tool> tools = this.tools.stream().map(toolRegistration -> {
356-
return toolRegistration.tool();
357-
}).toList();
355+
List<Tool> tools = this.tools.stream().map(ToolRegistration::tool).toList();
358356

359357
return Mono.just(new McpSchema.ListToolsResult(tools, null));
360358
};

mcp/src/main/java/org/springframework/ai/mcp/server/McpServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ public static Builder using(ServerMcpTransport transport) {
208208
*/
209209
public static class Builder {
210210

211-
private final static McpSchema.Implementation DEFAULT_SERVER_INFO = new McpSchema.Implementation("mcp-server",
211+
private static final McpSchema.Implementation DEFAULT_SERVER_INFO = new McpSchema.Implementation("mcp-server",
212212
"1.0.0");
213213

214214
private final ServerMcpTransport transport;

0 commit comments

Comments
 (0)