Skip to content

Commit ff78191

Browse files
authored
Close response after sending error in McpStreamableServerSession#responseStream (#1041)
Closes #1040 Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
1 parent f7d9a64 commit ff78191

2 files changed

Lines changed: 55 additions & 3 deletions

File tree

mcp-core/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,13 @@ public Mono<Void> responseStream(McpSchema.JSONRPCRequest jsonrpcRequest, McpStr
214214
// (sink)
215215
if (requestHandler == null) {
216216
MethodNotFoundError error = getMethodNotFoundError(jsonrpcRequest.method());
217-
return transport.sendMessage(
218-
McpSchema.JSONRPCResponse.error(jsonrpcRequest.id(), new McpSchema.JSONRPCResponse.JSONRPCError(
219-
McpSchema.ErrorCodes.METHOD_NOT_FOUND, error.message(), error.data())));
217+
return transport
218+
.sendMessage(
219+
McpSchema.JSONRPCResponse
220+
.error(jsonrpcRequest.id(),
221+
new McpSchema.JSONRPCResponse.JSONRPCError(
222+
McpSchema.ErrorCodes.METHOD_NOT_FOUND, error.message(), error.data())))
223+
.then(transport.closeGracefully());
220224
}
221225
return requestHandler
222226
.handle(new McpAsyncServerExchange(this.id, stream, clientCapabilities.get(), clientInfo.get(),

mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
import java.time.Duration;
88
import java.util.Map;
9+
import java.util.concurrent.atomic.AtomicReference;
10+
import java.util.function.Function;
911
import java.util.stream.Stream;
1012

1113
import io.modelcontextprotocol.AbstractMcpClientServerIntegrationTests;
@@ -16,14 +18,19 @@
1618
import io.modelcontextprotocol.server.McpServer.SyncSpecification;
1719
import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider;
1820
import io.modelcontextprotocol.server.transport.TomcatTestUtil;
21+
import io.modelcontextprotocol.spec.McpSchema;
1922
import jakarta.servlet.http.HttpServletRequest;
2023
import org.apache.catalina.LifecycleException;
2124
import org.apache.catalina.LifecycleState;
2225
import org.apache.catalina.startup.Tomcat;
26+
import org.awaitility.Awaitility;
2327
import org.junit.jupiter.api.AfterEach;
2428
import org.junit.jupiter.api.BeforeEach;
29+
import org.junit.jupiter.api.Test;
2530
import org.junit.jupiter.api.Timeout;
2631
import org.junit.jupiter.params.provider.Arguments;
32+
import reactor.core.publisher.Mono;
33+
import reactor.test.StepVerifier;
2734

2835
import static org.assertj.core.api.Assertions.assertThat;
2936

@@ -96,6 +103,47 @@ public void after() {
96103
}
97104
}
98105

106+
@Test
107+
void testMissingHandlerReturnsMethodNotFoundError() {
108+
var mcpServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
109+
.capabilities(McpSchema.ServerCapabilities.builder().tools(true).build())
110+
.build();
111+
var clientTransport = HttpClientStreamableHttpTransport.builder("http://localhost:" + PORT)
112+
.endpoint(MESSAGE_ENDPOINT)
113+
.build();
114+
115+
try (var mcpClient = McpClient.sync(clientTransport).build()) {
116+
// Create a session using an MCP client
117+
McpSchema.InitializeResult initResult = mcpClient.initialize();
118+
assertThat(initResult).isNotNull();
119+
120+
// Override the response handler in the client to capture responses
121+
AtomicReference<McpSchema.JSONRPCResponse> response = new AtomicReference<>();
122+
var handler = (Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>>) (
123+
message) -> message.doOnNext(r -> {
124+
if (r instanceof McpSchema.JSONRPCResponse resp) {
125+
response.set(resp);
126+
}
127+
});
128+
StepVerifier.create(clientTransport.connect(handler)).verifyComplete();
129+
130+
// Send an incorrect request through the transport
131+
StepVerifier
132+
.create(clientTransport.sendMessage(new McpSchema.JSONRPCRequest("foo/bar", "test-request-123")))
133+
.verifyComplete();
134+
135+
// Wait until we've received the response
136+
Awaitility.await().atMost(Duration.ofSeconds(1)).until(() -> response.get() != null);
137+
138+
assertThat(response.get().error().code()).isEqualTo(McpSchema.ErrorCodes.METHOD_NOT_FOUND);
139+
assertThat(response.get().error().message()).isEqualTo("Method not found: foo/bar");
140+
}
141+
finally {
142+
mcpServer.close();
143+
}
144+
145+
}
146+
99147
static McpTransportContextExtractor<HttpServletRequest> TEST_CONTEXT_EXTRACTOR = (r) -> McpTransportContext
100148
.create(Map.of("important", "value"));
101149

0 commit comments

Comments
 (0)