feat: implement MCP protocol version per transport support (#404)
- Add protocolVersion() method to transport and transport provider interfaces and implementations - Replace hardcoded HTTP header strings with HttpHeaders constants - Update protocol versions to be transport-specific rather than global - Deprecate McpSchema.LATEST_PROTOCOL_VERSION in favor of transport-specific versions - Standardize header names: MCP-Protocol-Version, mcp-session-id, last-event-id - Update clients and servers to use transport.protocolVersion() for initialization - Refactor tests to use transport-specific protocol versions - Include MCP-Protocol-Version header in all GET/POST/DELETE requests - Update WebClientStreamableHttpTransport, WebFluxSseClientTransport, HttpClientSseClientTransport, and HttpClientStreamableHttpTransport Related to #398 , #363 , #250 Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
parent
67de84b300
commit
9d58621918
5 changed files with 169 additions and 34 deletions
|
|
@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||
|
||||
import io.modelcontextprotocol.spec.DefaultMcpTransportSession;
|
||||
import io.modelcontextprotocol.spec.DefaultMcpTransportStream;
|
||||
import io.modelcontextprotocol.spec.HttpHeaders;
|
||||
import io.modelcontextprotocol.spec.McpClientTransport;
|
||||
import io.modelcontextprotocol.spec.McpError;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
|
|
@ -66,6 +67,8 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
|
|||
|
||||
private static final Logger logger = LoggerFactory.getLogger(WebClientStreamableHttpTransport.class);
|
||||
|
||||
private static final String MCP_PROTOCOL_VERSION = "2025-03-26";
|
||||
|
||||
private static final String DEFAULT_ENDPOINT = "/mcp";
|
||||
|
||||
/**
|
||||
|
|
@ -103,6 +106,11 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
|
|||
this.activeSession.set(createTransportSession());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String protocolVersion() {
|
||||
return MCP_PROTOCOL_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stateful builder for creating {@link WebClientStreamableHttpTransport}
|
||||
* instances.
|
||||
|
|
@ -128,12 +136,20 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
|
|||
|
||||
private DefaultMcpTransportSession createTransportSession() {
|
||||
Function<String, Publisher<Void>> onClose = sessionId -> sessionId == null ? Mono.empty()
|
||||
: webClient.delete().uri(this.endpoint).headers(httpHeaders -> {
|
||||
httpHeaders.add("mcp-session-id", sessionId);
|
||||
}).retrieve().toBodilessEntity().onErrorComplete(e -> {
|
||||
logger.warn("Got error when closing transport", e);
|
||||
return true;
|
||||
}).then();
|
||||
: webClient.delete()
|
||||
.uri(this.endpoint)
|
||||
.header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION)
|
||||
.headers(httpHeaders -> {
|
||||
httpHeaders.add(HttpHeaders.MCP_SESSION_ID, sessionId);
|
||||
httpHeaders.add(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION);
|
||||
})
|
||||
.retrieve()
|
||||
.toBodilessEntity()
|
||||
.onErrorComplete(e -> {
|
||||
logger.warn("Got error when closing transport", e);
|
||||
return true;
|
||||
})
|
||||
.then();
|
||||
return new DefaultMcpTransportSession(onClose);
|
||||
}
|
||||
|
||||
|
|
@ -186,10 +202,11 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
|
|||
Disposable connection = webClient.get()
|
||||
.uri(this.endpoint)
|
||||
.accept(MediaType.TEXT_EVENT_STREAM)
|
||||
.header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION)
|
||||
.headers(httpHeaders -> {
|
||||
transportSession.sessionId().ifPresent(id -> httpHeaders.add("mcp-session-id", id));
|
||||
transportSession.sessionId().ifPresent(id -> httpHeaders.add(HttpHeaders.MCP_SESSION_ID, id));
|
||||
if (stream != null) {
|
||||
stream.lastId().ifPresent(id -> httpHeaders.add("last-event-id", id));
|
||||
stream.lastId().ifPresent(id -> httpHeaders.add(HttpHeaders.LAST_EVENT_ID, id));
|
||||
}
|
||||
})
|
||||
.exchangeToFlux(response -> {
|
||||
|
|
@ -246,13 +263,14 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
|
|||
Disposable connection = webClient.post()
|
||||
.uri(this.endpoint)
|
||||
.accept(MediaType.APPLICATION_JSON, MediaType.TEXT_EVENT_STREAM)
|
||||
.header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION)
|
||||
.headers(httpHeaders -> {
|
||||
transportSession.sessionId().ifPresent(id -> httpHeaders.add("mcp-session-id", id));
|
||||
transportSession.sessionId().ifPresent(id -> httpHeaders.add(HttpHeaders.MCP_SESSION_ID, id));
|
||||
})
|
||||
.bodyValue(message)
|
||||
.exchangeToFlux(response -> {
|
||||
if (transportSession
|
||||
.markInitialized(response.headers().asHttpHeaders().getFirst("mcp-session-id"))) {
|
||||
.markInitialized(response.headers().asHttpHeaders().getFirst(HttpHeaders.MCP_SESSION_ID))) {
|
||||
// Once we have a session, we try to open an async stream for
|
||||
// the server to send notifications and requests out-of-band.
|
||||
reconnect(null).contextWrite(sink.contextView()).subscribe();
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import java.util.function.Function;
|
|||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import io.modelcontextprotocol.spec.HttpHeaders;
|
||||
import io.modelcontextprotocol.spec.McpClientTransport;
|
||||
import io.modelcontextprotocol.spec.McpError;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
|
|
@ -62,6 +64,8 @@ public class WebFluxSseClientTransport implements McpClientTransport {
|
|||
|
||||
private static final Logger logger = LoggerFactory.getLogger(WebFluxSseClientTransport.class);
|
||||
|
||||
private static final String MCP_PROTOCOL_VERSION = "2024-11-05";
|
||||
|
||||
/**
|
||||
* Event type for JSON-RPC messages received through the SSE connection. The server
|
||||
* sends messages with this event type to transmit JSON-RPC protocol data.
|
||||
|
|
@ -166,6 +170,11 @@ public class WebFluxSseClientTransport implements McpClientTransport {
|
|||
this.sseEndpoint = sseEndpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String protocolVersion() {
|
||||
return MCP_PROTOCOL_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes a connection to the MCP server using Server-Sent Events (SSE). This
|
||||
* method initiates the SSE connection and sets up the message processing pipeline.
|
||||
|
|
@ -250,6 +259,7 @@ public class WebFluxSseClientTransport implements McpClientTransport {
|
|||
return webClient.post()
|
||||
.uri(messageEndpointUri)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION)
|
||||
.bodyValue(jsonText)
|
||||
.retrieve()
|
||||
.toBodilessEntity()
|
||||
|
|
@ -282,6 +292,7 @@ public class WebFluxSseClientTransport implements McpClientTransport {
|
|||
.get()
|
||||
.uri(this.sseEndpoint)
|
||||
.accept(MediaType.TEXT_EVENT_STREAM)
|
||||
.header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION)
|
||||
.retrieve()
|
||||
.bodyToFlux(SSE_TYPE)
|
||||
.retryWhen(Retry.from(retrySignal -> retrySignal.handle(inboundRetryHandler)));
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ public class WebFluxSseServerTransportProvider implements McpServerTransportProv
|
|||
*/
|
||||
public static final String ENDPOINT_EVENT_TYPE = "endpoint";
|
||||
|
||||
private static final String MCP_PROTOCOL_VERSION = "2025-06-18";
|
||||
|
||||
/**
|
||||
* Default SSE endpoint path as specified by the MCP transport specification.
|
||||
*/
|
||||
|
|
@ -212,6 +214,11 @@ public class WebFluxSseServerTransportProvider implements McpServerTransportProv
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String protocolVersion() {
|
||||
return "2024-11-05";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionFactory(McpServerSession.Factory sessionFactory) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import reactor.core.publisher.Mono;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Implementation of a WebFlux based {@link McpStatelessServerTransport}.
|
||||
|
|
|
|||
|
|
@ -95,6 +95,11 @@ public class WebFluxStreamableServerTransportProvider implements McpStreamableSe
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String protocolVersion() {
|
||||
return "2025-03-26";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionFactory(McpStreamableServerSession.Factory sessionFactory) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
|
|
|
|||
Loading…
Reference in a new issue