fix: improve streamable HTTP session reinitialization (#459)

Implements the MCP spec guidelines for streamable HTTP (re)initialization:
- Server MAY terminate session and MUST respond with HTTP 404 for terminated session IDs
- Client MUST start new session when receiving HTTP 404 for requests with session ID

Changes:
- Replace generic McpError with McpTransportException for transport-layer errors
- Only throw McpTransportSessionNotFoundException when session ID is present in request
  (per spec: 404 with session ID means session terminated, without means general error)
- Enhance error messages with more context (status codes, response events)
- Use RuntimeException for non-transport specific SSE endpoint failures
- Ensure consistent error handling across HTTP client transports

- Improve error handling with standard Java exceptions.
  Replace generic McpError with appropriate standard exceptions:
  - Use IllegalArgumentException for invalid input parameters
  - Use IllegalStateException for state-related issues
  - Use RuntimeException wrapper for initialization failures
  - Use McpError.builder() with proper error codes for protocol errors

Fixes #459

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
Christian Tzolov 2025-08-07 18:35:50 +01:00
commit ea50838d06
3 changed files with 955 additions and 90 deletions

View file

@ -11,6 +11,7 @@ import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Timeout;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@ -29,6 +30,7 @@ import io.modelcontextprotocol.server.McpServer.SingleSessionSyncSpecification;
import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider;
import reactor.core.scheduler.Schedulers;
@Timeout(15)
class WebMvcSseIntegrationTests extends AbstractMcpClientServerIntegrationTests {
private static final int PORT = TestUtil.findAvailablePort();

View file

@ -11,6 +11,7 @@ import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Timeout;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@ -29,6 +30,7 @@ import io.modelcontextprotocol.server.McpServer.StatelessSyncSpecification;
import io.modelcontextprotocol.server.transport.WebMvcStatelessServerTransport;
import reactor.core.scheduler.Schedulers;
@Timeout(15)
class WebMvcStatelessIntegrationTests extends AbstractStatelessIntegrationTests {
private static final int PORT = TestUtil.findAvailablePort();

View file

@ -11,6 +11,7 @@ import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Timeout;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@ -29,6 +30,7 @@ import io.modelcontextprotocol.server.McpServer.SyncSpecification;
import io.modelcontextprotocol.server.transport.WebMvcStreamableServerTransportProvider;
import reactor.core.scheduler.Schedulers;
@Timeout(15)
class WebMvcStreamableIntegrationTests extends AbstractMcpClientServerIntegrationTests {
private static final int PORT = TestUtil.findAvailablePort();