Skip to content

feat: Add Spring WebMVC streamable server transport provider #425

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Jul 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
98990e9
WIP
chemicL Jul 18, 2025
8e4c745
Merge branch 'main' into streamable-server
chemicL Jul 21, 2025
f83db45
More WIP: renaming, steteless server abstractions, client context ext…
chemicL Jul 22, 2025
1645ecb
Formatting
chemicL Jul 22, 2025
a3ddb75
WIP: make it compile
chemicL Jul 22, 2025
cef7aac
WIP: Add session ID to exchange
chemicL Jul 22, 2025
6423fcd
WIP: make it compile
chemicL Jul 22, 2025
8e9ab52
WIP: add SSE id generation and DELETE handling
chemicL Jul 23, 2025
92ca81f
WIP: Handle logging notifications, start with integration tests and a…
chemicL Jul 25, 2025
c0e3129
WIP: Sync server builder tests
chemicL Jul 28, 2025
a0ad5a5
WIP: resolve integration test issues
chemicL Jul 28, 2025
d98b8c9
WIP: Logging notifications tests on exchange adapted
chemicL Jul 28, 2025
6e66602
WIP: Get rid of initialization notification handler
chemicL Jul 28, 2025
b382ac5
refactor: improve MCP server test architecture with parameterized tests
tzolov Jul 28, 2025
0a4ecb0
WIP: stateless server implementation
chemicL Jul 29, 2025
87a018f
WIP: context extractor usage, some javadocs, HttpHeaders constants
chemicL Jul 30, 2025
92be635
WIP: Add remaining javadocs, tiny corrections here and there
chemicL Jul 30, 2025
08900bc
Add accept header check for WebFlux server
chemicL Jul 30, 2025
ca7f870
Use random port in McpCompletionTests
chemicL Jul 30, 2025
b204cd8
feat: Add streamable server transport for Spring WebMVC (#425)
tzolov Jul 26, 2025
f4eb55b
Merge branch 'main' into streamable-server-webmvc
tzolov Jul 30, 2025
8a2e978
refactor: wrap handler invocations with Mono.defer for lazy evaluation
tzolov Jul 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions mcp-spring/mcp-spring-webmvc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,24 @@
<version>0.11.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${springframework.version}</version>
</dependency>

<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-test</artifactId>
<version>0.11.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${springframework.version}</version>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webflux</artifactId>
<version>0.11.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand Down Expand Up @@ -339,6 +340,12 @@ private class WebMvcMcpSessionTransport implements McpServerTransport {

private final SseBuilder sseBuilder;

/**
* Lock to ensure thread-safe access to the SSE builder when sending messages.
* This prevents concurrent modifications that could lead to corrupted SSE events.
*/
private final ReentrantLock sseBuilderLock = new ReentrantLock();

/**
* Creates a new session transport with the specified ID and SSE builder.
* @param sessionId The unique identifier for this session
Expand All @@ -358,6 +365,7 @@ private class WebMvcMcpSessionTransport implements McpServerTransport {
@Override
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
return Mono.fromRunnable(() -> {
sseBuilderLock.lock();
try {
String jsonText = objectMapper.writeValueAsString(message);
sseBuilder.id(sessionId).event(MESSAGE_EVENT_TYPE).data(jsonText);
Expand All @@ -367,6 +375,9 @@ public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
logger.error("Failed to send message to session {}: {}", sessionId, e.getMessage());
sseBuilder.error(e);
}
finally {
sseBuilderLock.unlock();
}
});
}

Expand All @@ -390,13 +401,17 @@ public <T> T unmarshalFrom(Object data, TypeReference<T> typeRef) {
public Mono<Void> closeGracefully() {
return Mono.fromRunnable(() -> {
logger.debug("Closing session transport: {}", sessionId);
sseBuilderLock.lock();
try {
sseBuilder.complete();
logger.debug("Successfully completed SSE builder for session {}", sessionId);
}
catch (Exception e) {
logger.warn("Failed to complete SSE builder for session {}: {}", sessionId, e.getMessage());
}
finally {
sseBuilderLock.unlock();
}
});
}

Expand All @@ -405,13 +420,17 @@ public Mono<Void> closeGracefully() {
*/
@Override
public void close() {
sseBuilderLock.lock();
try {
sseBuilder.complete();
logger.debug("Successfully completed SSE builder for session {}", sessionId);
}
catch (Exception e) {
logger.warn("Failed to complete SSE builder for session {}: {}", sessionId, e.getMessage());
}
finally {
sseBuilderLock.unlock();
}
}

}
Expand Down
Loading
Loading