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
27 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.DefaultMcpTransportSession;
|
||||||
import io.modelcontextprotocol.spec.DefaultMcpTransportStream;
|
import io.modelcontextprotocol.spec.DefaultMcpTransportStream;
|
||||||
|
import io.modelcontextprotocol.spec.HttpHeaders;
|
||||||
import io.modelcontextprotocol.spec.McpClientTransport;
|
import io.modelcontextprotocol.spec.McpClientTransport;
|
||||||
import io.modelcontextprotocol.spec.McpError;
|
import io.modelcontextprotocol.spec.McpError;
|
||||||
import io.modelcontextprotocol.spec.McpSchema;
|
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 Logger logger = LoggerFactory.getLogger(WebClientStreamableHttpTransport.class);
|
||||||
|
|
||||||
|
private static final String MCP_PROTOCOL_VERSION = "2025-03-26";
|
||||||
|
|
||||||
private static final String DEFAULT_ENDPOINT = "/mcp";
|
private static final String DEFAULT_ENDPOINT = "/mcp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -103,6 +106,11 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
|
||||||
this.activeSession.set(createTransportSession());
|
this.activeSession.set(createTransportSession());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String protocolVersion() {
|
||||||
|
return MCP_PROTOCOL_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a stateful builder for creating {@link WebClientStreamableHttpTransport}
|
* Create a stateful builder for creating {@link WebClientStreamableHttpTransport}
|
||||||
* instances.
|
* instances.
|
||||||
|
|
@ -128,12 +136,20 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
|
||||||
|
|
||||||
private DefaultMcpTransportSession createTransportSession() {
|
private DefaultMcpTransportSession createTransportSession() {
|
||||||
Function<String, Publisher<Void>> onClose = sessionId -> sessionId == null ? Mono.empty()
|
Function<String, Publisher<Void>> onClose = sessionId -> sessionId == null ? Mono.empty()
|
||||||
: webClient.delete().uri(this.endpoint).headers(httpHeaders -> {
|
: webClient.delete()
|
||||||
httpHeaders.add("mcp-session-id", sessionId);
|
.uri(this.endpoint)
|
||||||
}).retrieve().toBodilessEntity().onErrorComplete(e -> {
|
.header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION)
|
||||||
logger.warn("Got error when closing transport", e);
|
.headers(httpHeaders -> {
|
||||||
return true;
|
httpHeaders.add(HttpHeaders.MCP_SESSION_ID, sessionId);
|
||||||
}).then();
|
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);
|
return new DefaultMcpTransportSession(onClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,10 +202,11 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
|
||||||
Disposable connection = webClient.get()
|
Disposable connection = webClient.get()
|
||||||
.uri(this.endpoint)
|
.uri(this.endpoint)
|
||||||
.accept(MediaType.TEXT_EVENT_STREAM)
|
.accept(MediaType.TEXT_EVENT_STREAM)
|
||||||
|
.header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION)
|
||||||
.headers(httpHeaders -> {
|
.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) {
|
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 -> {
|
.exchangeToFlux(response -> {
|
||||||
|
|
@ -246,13 +263,14 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
|
||||||
Disposable connection = webClient.post()
|
Disposable connection = webClient.post()
|
||||||
.uri(this.endpoint)
|
.uri(this.endpoint)
|
||||||
.accept(MediaType.APPLICATION_JSON, MediaType.TEXT_EVENT_STREAM)
|
.accept(MediaType.APPLICATION_JSON, MediaType.TEXT_EVENT_STREAM)
|
||||||
|
.header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION)
|
||||||
.headers(httpHeaders -> {
|
.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)
|
.bodyValue(message)
|
||||||
.exchangeToFlux(response -> {
|
.exchangeToFlux(response -> {
|
||||||
if (transportSession
|
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
|
// Once we have a session, we try to open an async stream for
|
||||||
// the server to send notifications and requests out-of-band.
|
// the server to send notifications and requests out-of-band.
|
||||||
reconnect(null).contextWrite(sink.contextView()).subscribe();
|
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.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import io.modelcontextprotocol.spec.HttpHeaders;
|
||||||
import io.modelcontextprotocol.spec.McpClientTransport;
|
import io.modelcontextprotocol.spec.McpClientTransport;
|
||||||
import io.modelcontextprotocol.spec.McpError;
|
import io.modelcontextprotocol.spec.McpError;
|
||||||
import io.modelcontextprotocol.spec.McpSchema;
|
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 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
|
* 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.
|
* sends messages with this event type to transmit JSON-RPC protocol data.
|
||||||
|
|
@ -166,6 +170,11 @@ public class WebFluxSseClientTransport implements McpClientTransport {
|
||||||
this.sseEndpoint = sseEndpoint;
|
this.sseEndpoint = sseEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String protocolVersion() {
|
||||||
|
return MCP_PROTOCOL_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establishes a connection to the MCP server using Server-Sent Events (SSE). This
|
* 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.
|
* method initiates the SSE connection and sets up the message processing pipeline.
|
||||||
|
|
@ -250,6 +259,7 @@ public class WebFluxSseClientTransport implements McpClientTransport {
|
||||||
return webClient.post()
|
return webClient.post()
|
||||||
.uri(messageEndpointUri)
|
.uri(messageEndpointUri)
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION)
|
||||||
.bodyValue(jsonText)
|
.bodyValue(jsonText)
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.toBodilessEntity()
|
.toBodilessEntity()
|
||||||
|
|
@ -282,6 +292,7 @@ public class WebFluxSseClientTransport implements McpClientTransport {
|
||||||
.get()
|
.get()
|
||||||
.uri(this.sseEndpoint)
|
.uri(this.sseEndpoint)
|
||||||
.accept(MediaType.TEXT_EVENT_STREAM)
|
.accept(MediaType.TEXT_EVENT_STREAM)
|
||||||
|
.header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION)
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToFlux(SSE_TYPE)
|
.bodyToFlux(SSE_TYPE)
|
||||||
.retryWhen(Retry.from(retrySignal -> retrySignal.handle(inboundRetryHandler)));
|
.retryWhen(Retry.from(retrySignal -> retrySignal.handle(inboundRetryHandler)));
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,8 @@ public class WebFluxSseServerTransportProvider implements McpServerTransportProv
|
||||||
*/
|
*/
|
||||||
public static final String ENDPOINT_EVENT_TYPE = "endpoint";
|
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.
|
* 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
|
@Override
|
||||||
public void setSessionFactory(McpServerSession.Factory sessionFactory) {
|
public void setSessionFactory(McpServerSession.Factory sessionFactory) {
|
||||||
this.sessionFactory = sessionFactory;
|
this.sessionFactory = sessionFactory;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of a WebFlux based {@link McpStatelessServerTransport}.
|
* 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
|
@Override
|
||||||
public void setSessionFactory(McpStreamableServerSession.Factory sessionFactory) {
|
public void setSessionFactory(McpStreamableServerSession.Factory sessionFactory) {
|
||||||
this.sessionFactory = sessionFactory;
|
this.sessionFactory = sessionFactory;
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,11 @@ public class WebMvcSseServerTransportProvider implements McpServerTransportProvi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String protocolVersion() {
|
||||||
|
return "2024-11-05";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSessionFactory(McpServerSession.Factory sessionFactory) {
|
public void setSessionFactory(McpServerSession.Factory sessionFactory) {
|
||||||
this.sessionFactory = sessionFactory;
|
this.sessionFactory = sessionFactory;
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,11 @@ public class WebMvcStreamableServerTransportProvider implements McpStreamableSer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String protocolVersion() {
|
||||||
|
return "2025-03-26";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSessionFactory(McpStreamableServerSession.Factory sessionFactory) {
|
public void setSessionFactory(McpStreamableServerSession.Factory sessionFactory) {
|
||||||
this.sessionFactory = sessionFactory;
|
this.sessionFactory = sessionFactory;
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,15 @@ class WebMvcSseCustomContextPathTests {
|
||||||
@Bean
|
@Bean
|
||||||
public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider() {
|
public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider() {
|
||||||
|
|
||||||
return new WebMvcSseServerTransportProvider(new ObjectMapper(), CUSTOM_CONTEXT_PATH, MESSAGE_ENDPOINT,
|
return WebMvcSseServerTransportProvider.builder()
|
||||||
WebMvcSseServerTransportProvider.DEFAULT_SSE_ENDPOINT);
|
.objectMapper(new ObjectMapper())
|
||||||
|
.baseUrl(CUSTOM_CONTEXT_PATH)
|
||||||
|
.messageEndpoint(MESSAGE_ENDPOINT)
|
||||||
|
.sseEndpoint(WebMvcSseServerTransportProvider.DEFAULT_SSE_ENDPOINT)
|
||||||
|
.build();
|
||||||
|
// return new WebMvcSseServerTransportProvider(new ObjectMapper(),
|
||||||
|
// CUSTOM_CONTEXT_PATH, MESSAGE_ENDPOINT,
|
||||||
|
// WebMvcSseServerTransportProvider.DEFAULT_SSE_ENDPOINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
||||||
|
|
@ -272,9 +272,8 @@ public class McpAsyncClient {
|
||||||
asyncProgressNotificationHandler(progressConsumersFinal));
|
asyncProgressNotificationHandler(progressConsumersFinal));
|
||||||
|
|
||||||
this.initializer = new LifecycleInitializer(clientCapabilities, clientInfo,
|
this.initializer = new LifecycleInitializer(clientCapabilities, clientInfo,
|
||||||
List.of(McpSchema.LATEST_PROTOCOL_VERSION), initializationTimeout,
|
List.of(transport.protocolVersion()), initializationTimeout, ctx -> new McpClientSession(requestTimeout,
|
||||||
ctx -> new McpClientSession(requestTimeout, transport, requestHandlers, notificationHandlers,
|
transport, requestHandlers, notificationHandlers, con -> con.contextWrite(ctx)));
|
||||||
con -> con.contextWrite(ctx)));
|
|
||||||
this.transport.setExceptionHandler(this.initializer::handleException);
|
this.transport.setExceptionHandler(this.initializer::handleException);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,10 @@ import reactor.core.publisher.Sinks;
|
||||||
*/
|
*/
|
||||||
public class HttpClientSseClientTransport implements McpClientTransport {
|
public class HttpClientSseClientTransport implements McpClientTransport {
|
||||||
|
|
||||||
|
private static final String MCP_PROTOCOL_VERSION = "2024-11-05";
|
||||||
|
|
||||||
|
private static final String MCP_PROTOCOL_VERSION_HEADER_NAME = "MCP-Protocol-Version";
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(HttpClientSseClientTransport.class);
|
private static final Logger logger = LoggerFactory.getLogger(HttpClientSseClientTransport.class);
|
||||||
|
|
||||||
/** SSE event type for JSON-RPC messages */
|
/** SSE event type for JSON-RPC messages */
|
||||||
|
|
@ -211,6 +215,11 @@ public class HttpClientSseClientTransport implements McpClientTransport {
|
||||||
this.httpRequestCustomizer = httpRequestCustomizer;
|
this.httpRequestCustomizer = httpRequestCustomizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String protocolVersion() {
|
||||||
|
return MCP_PROTOCOL_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new builder for {@link HttpClientSseClientTransport}.
|
* Creates a new builder for {@link HttpClientSseClientTransport}.
|
||||||
* @param baseUri the base URI of the MCP server
|
* @param baseUri the base URI of the MCP server
|
||||||
|
|
@ -391,6 +400,7 @@ public class HttpClientSseClientTransport implements McpClientTransport {
|
||||||
.uri(uri)
|
.uri(uri)
|
||||||
.header("Accept", "text/event-stream")
|
.header("Accept", "text/event-stream")
|
||||||
.header("Cache-Control", "no-cache")
|
.header("Cache-Control", "no-cache")
|
||||||
|
.header(MCP_PROTOCOL_VERSION_HEADER_NAME, MCP_PROTOCOL_VERSION)
|
||||||
.GET();
|
.GET();
|
||||||
return Mono.from(this.httpRequestCustomizer.customize(builder, "GET", uri, null));
|
return Mono.from(this.httpRequestCustomizer.customize(builder, "GET", uri, null));
|
||||||
}).flatMap(requestBuilder -> Mono.create(sink -> {
|
}).flatMap(requestBuilder -> Mono.create(sink -> {
|
||||||
|
|
@ -516,7 +526,10 @@ public class HttpClientSseClientTransport implements McpClientTransport {
|
||||||
private Mono<HttpResponse<String>> sendHttpPost(final String endpoint, final String body) {
|
private Mono<HttpResponse<String>> sendHttpPost(final String endpoint, final String body) {
|
||||||
final URI requestUri = Utils.resolveUri(baseUri, endpoint);
|
final URI requestUri = Utils.resolveUri(baseUri, endpoint);
|
||||||
return Mono.defer(() -> {
|
return Mono.defer(() -> {
|
||||||
var builder = this.requestBuilder.copy().uri(requestUri).POST(HttpRequest.BodyPublishers.ofString(body));
|
var builder = this.requestBuilder.copy()
|
||||||
|
.uri(requestUri)
|
||||||
|
.header(MCP_PROTOCOL_VERSION_HEADER_NAME, MCP_PROTOCOL_VERSION)
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(body));
|
||||||
return Mono.from(this.httpRequestCustomizer.customize(builder, "POST", requestUri, body));
|
return Mono.from(this.httpRequestCustomizer.customize(builder, "POST", requestUri, body));
|
||||||
}).flatMap(customizedBuilder -> {
|
}).flatMap(customizedBuilder -> {
|
||||||
var request = customizedBuilder.build();
|
var request = customizedBuilder.build();
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.modelcontextprotocol.client.transport.ResponseSubscribers.ResponseEvent;
|
import io.modelcontextprotocol.client.transport.ResponseSubscribers.ResponseEvent;
|
||||||
import io.modelcontextprotocol.spec.DefaultMcpTransportSession;
|
import io.modelcontextprotocol.spec.DefaultMcpTransportSession;
|
||||||
import io.modelcontextprotocol.spec.DefaultMcpTransportStream;
|
import io.modelcontextprotocol.spec.DefaultMcpTransportStream;
|
||||||
|
import io.modelcontextprotocol.spec.HttpHeaders;
|
||||||
import io.modelcontextprotocol.spec.McpClientTransport;
|
import io.modelcontextprotocol.spec.McpClientTransport;
|
||||||
import io.modelcontextprotocol.spec.McpError;
|
import io.modelcontextprotocol.spec.McpError;
|
||||||
import io.modelcontextprotocol.spec.McpSchema;
|
import io.modelcontextprotocol.spec.McpSchema;
|
||||||
|
|
@ -72,6 +73,8 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(HttpClientStreamableHttpTransport.class);
|
private static final Logger logger = LoggerFactory.getLogger(HttpClientStreamableHttpTransport.class);
|
||||||
|
|
||||||
|
private static final String MCP_PROTOCOL_VERSION = "2025-03-26";
|
||||||
|
|
||||||
private static final String DEFAULT_ENDPOINT = "/mcp";
|
private static final String DEFAULT_ENDPOINT = "/mcp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -131,6 +134,11 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
||||||
this.httpRequestCustomizer = httpRequestCustomizer;
|
this.httpRequestCustomizer = httpRequestCustomizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String protocolVersion() {
|
||||||
|
return MCP_PROTOCOL_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
public static Builder builder(String baseUri) {
|
public static Builder builder(String baseUri) {
|
||||||
return new Builder(baseUri);
|
return new Builder(baseUri);
|
||||||
}
|
}
|
||||||
|
|
@ -157,12 +165,14 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Publisher<Void> createDelete(String sessionId) {
|
private Publisher<Void> createDelete(String sessionId) {
|
||||||
|
|
||||||
var uri = Utils.resolveUri(this.baseUri, this.endpoint);
|
var uri = Utils.resolveUri(this.baseUri, this.endpoint);
|
||||||
return Mono.defer(() -> {
|
return Mono.defer(() -> {
|
||||||
var builder = this.requestBuilder.copy()
|
var builder = this.requestBuilder.copy()
|
||||||
.uri(uri)
|
.uri(uri)
|
||||||
.header("Cache-Control", "no-cache")
|
.header("Cache-Control", "no-cache")
|
||||||
.header("mcp-session-id", sessionId)
|
.header(HttpHeaders.MCP_SESSION_ID, sessionId)
|
||||||
|
.header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION)
|
||||||
.DELETE();
|
.DELETE();
|
||||||
return Mono.from(this.httpRequestCustomizer.customize(builder, "DELETE", uri, null));
|
return Mono.from(this.httpRequestCustomizer.customize(builder, "DELETE", uri, null));
|
||||||
}).flatMap(requestBuilder -> {
|
}).flatMap(requestBuilder -> {
|
||||||
|
|
@ -221,16 +231,18 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
||||||
HttpRequest.Builder requestBuilder = this.requestBuilder.copy();
|
HttpRequest.Builder requestBuilder = this.requestBuilder.copy();
|
||||||
|
|
||||||
if (transportSession != null && transportSession.sessionId().isPresent()) {
|
if (transportSession != null && transportSession.sessionId().isPresent()) {
|
||||||
requestBuilder = requestBuilder.header("mcp-session-id", transportSession.sessionId().get());
|
requestBuilder = requestBuilder.header(HttpHeaders.MCP_SESSION_ID,
|
||||||
|
transportSession.sessionId().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream != null && stream.lastId().isPresent()) {
|
if (stream != null && stream.lastId().isPresent()) {
|
||||||
requestBuilder = requestBuilder.header("last-event-id", stream.lastId().get());
|
requestBuilder = requestBuilder.header(HttpHeaders.LAST_EVENT_ID, stream.lastId().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
var builder = requestBuilder.uri(uri)
|
var builder = requestBuilder.uri(uri)
|
||||||
.header("Accept", TEXT_EVENT_STREAM)
|
.header("Accept", TEXT_EVENT_STREAM)
|
||||||
.header("Cache-Control", "no-cache")
|
.header("Cache-Control", "no-cache")
|
||||||
|
.header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION)
|
||||||
.GET();
|
.GET();
|
||||||
return Mono.from(this.httpRequestCustomizer.customize(builder, "GET", uri, null));
|
return Mono.from(this.httpRequestCustomizer.customize(builder, "GET", uri, null));
|
||||||
})
|
})
|
||||||
|
|
@ -377,13 +389,15 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
||||||
HttpRequest.Builder requestBuilder = this.requestBuilder.copy();
|
HttpRequest.Builder requestBuilder = this.requestBuilder.copy();
|
||||||
|
|
||||||
if (transportSession != null && transportSession.sessionId().isPresent()) {
|
if (transportSession != null && transportSession.sessionId().isPresent()) {
|
||||||
requestBuilder = requestBuilder.header("mcp-session-id", transportSession.sessionId().get());
|
requestBuilder = requestBuilder.header(HttpHeaders.MCP_SESSION_ID,
|
||||||
|
transportSession.sessionId().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
var builder = requestBuilder.uri(uri)
|
var builder = requestBuilder.uri(uri)
|
||||||
.header("Accept", APPLICATION_JSON + ", " + TEXT_EVENT_STREAM)
|
.header("Accept", APPLICATION_JSON + ", " + TEXT_EVENT_STREAM)
|
||||||
.header("Content-Type", APPLICATION_JSON)
|
.header("Content-Type", APPLICATION_JSON)
|
||||||
.header("Cache-Control", "no-cache")
|
.header("Cache-Control", "no-cache")
|
||||||
|
.header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION)
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(jsonBody));
|
.POST(HttpRequest.BodyPublishers.ofString(jsonBody));
|
||||||
return Mono.from(this.httpRequestCustomizer.customize(builder, "GET", uri, jsonBody));
|
return Mono.from(this.httpRequestCustomizer.customize(builder, "GET", uri, jsonBody));
|
||||||
}).flatMapMany(requestBuilder -> Flux.<ResponseEvent>create(responseEventSink -> {
|
}).flatMapMany(requestBuilder -> Flux.<ResponseEvent>create(responseEventSink -> {
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ public class McpAsyncServer {
|
||||||
|
|
||||||
private final ConcurrentHashMap<McpSchema.CompleteReference, McpServerFeatures.AsyncCompletionSpecification> completions = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<McpSchema.CompleteReference, McpServerFeatures.AsyncCompletionSpecification> completions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private List<String> protocolVersions = List.of(McpSchema.LATEST_PROTOCOL_VERSION);
|
private List<String> protocolVersions;
|
||||||
|
|
||||||
private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
|
private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
|
||||||
|
|
||||||
|
|
@ -145,6 +145,8 @@ public class McpAsyncServer {
|
||||||
Map<String, McpRequestHandler<?>> requestHandlers = prepareRequestHandlers();
|
Map<String, McpRequestHandler<?>> requestHandlers = prepareRequestHandlers();
|
||||||
Map<String, McpNotificationHandler> notificationHandlers = prepareNotificationHandlers(features);
|
Map<String, McpNotificationHandler> notificationHandlers = prepareNotificationHandlers(features);
|
||||||
|
|
||||||
|
this.protocolVersions = List.of(mcpTransportProvider.protocolVersion());
|
||||||
|
|
||||||
mcpTransportProvider.setSessionFactory(transport -> new McpServerSession(UUID.randomUUID().toString(),
|
mcpTransportProvider.setSessionFactory(transport -> new McpServerSession(UUID.randomUUID().toString(),
|
||||||
requestTimeout, transport, this::asyncInitializeRequestHandler, requestHandlers, notificationHandlers));
|
requestTimeout, transport, this::asyncInitializeRequestHandler, requestHandlers, notificationHandlers));
|
||||||
}
|
}
|
||||||
|
|
@ -168,6 +170,8 @@ public class McpAsyncServer {
|
||||||
Map<String, McpRequestHandler<?>> requestHandlers = prepareRequestHandlers();
|
Map<String, McpRequestHandler<?>> requestHandlers = prepareRequestHandlers();
|
||||||
Map<String, McpNotificationHandler> notificationHandlers = prepareNotificationHandlers(features);
|
Map<String, McpNotificationHandler> notificationHandlers = prepareNotificationHandlers(features);
|
||||||
|
|
||||||
|
this.protocolVersions = List.of(mcpTransportProvider.protocolVersion());
|
||||||
|
|
||||||
mcpTransportProvider.setSessionFactory(new DefaultMcpStreamableServerSessionFactory(requestTimeout,
|
mcpTransportProvider.setSessionFactory(new DefaultMcpStreamableServerSessionFactory(requestTimeout,
|
||||||
this::asyncInitializeRequestHandler, requestHandlers, notificationHandlers));
|
this::asyncInitializeRequestHandler, requestHandlers, notificationHandlers));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ public class McpStatelessAsyncServer {
|
||||||
|
|
||||||
private final ConcurrentHashMap<McpSchema.CompleteReference, McpStatelessServerFeatures.AsyncCompletionSpecification> completions = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<McpSchema.CompleteReference, McpStatelessServerFeatures.AsyncCompletionSpecification> completions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private List<String> protocolVersions = List.of(McpSchema.LATEST_PROTOCOL_VERSION);
|
private List<String> protocolVersions;
|
||||||
|
|
||||||
private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
|
private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
|
||||||
|
|
||||||
|
|
@ -118,6 +118,8 @@ public class McpStatelessAsyncServer {
|
||||||
requestHandlers.put(McpSchema.METHOD_COMPLETION_COMPLETE, completionCompleteRequestHandler());
|
requestHandlers.put(McpSchema.METHOD_COMPLETION_COMPLETE, completionCompleteRequestHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.protocolVersions = List.of(mcpTransport.protocolVersion());
|
||||||
|
|
||||||
McpStatelessServerHandler handler = new DefaultMcpStatelessServerHandler(requestHandlers, Map.of());
|
McpStatelessServerHandler handler = new DefaultMcpStatelessServerHandler(requestHandlers, Map.of());
|
||||||
mcpTransport.setMcpHandler(handler);
|
mcpTransport.setMcpHandler(handler);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,11 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String protocolVersion() {
|
||||||
|
return "2024-11-05";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new HttpServletSseServerTransportProvider instance with the default SSE
|
* Creates a new HttpServletSseServerTransportProvider instance with the default SSE
|
||||||
* endpoint.
|
* endpoint.
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,11 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String protocolVersion() {
|
||||||
|
return "2025-03-26";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSessionFactory(McpStreamableServerSession.Factory sessionFactory) {
|
public void setSessionFactory(McpStreamableServerSession.Factory sessionFactory) {
|
||||||
this.sessionFactory = sessionFactory;
|
this.sessionFactory = sessionFactory;
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,11 @@ public class StdioServerTransportProvider implements McpServerTransportProvider
|
||||||
this.outputStream = outputStream;
|
this.outputStream = outputStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String protocolVersion() {
|
||||||
|
return "2024-11-05";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSessionFactory(McpServerSession.Factory sessionFactory) {
|
public void setSessionFactory(McpServerSession.Factory sessionFactory) {
|
||||||
// Create a single session for the stdio connection
|
// Create a single session for the stdio connection
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,9 @@ public interface HttpHeaders {
|
||||||
*/
|
*/
|
||||||
String LAST_EVENT_ID = "last-event-id";
|
String LAST_EVENT_ID = "last-event-id";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies the MCP protocol version.
|
||||||
|
*/
|
||||||
|
String PROTOCOL_VERSION = "MCP-Protocol-Version";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ public final class McpSchema {
|
||||||
private McpSchema() {
|
private McpSchema() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static final String LATEST_PROTOCOL_VERSION = "2025-03-26";
|
public static final String LATEST_PROTOCOL_VERSION = "2025-03-26";
|
||||||
|
|
||||||
public static final String JSONRPC_VERSION = "2.0";
|
public static final String JSONRPC_VERSION = "2.0";
|
||||||
|
|
|
||||||
|
|
@ -55,4 +55,12 @@ public interface McpServerTransportProviderBase {
|
||||||
*/
|
*/
|
||||||
Mono<Void> closeGracefully();
|
Mono<Void> closeGracefully();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the protocol version supported by this transport provider.
|
||||||
|
* @return the protocol version as a string
|
||||||
|
*/
|
||||||
|
default String protocolVersion() {
|
||||||
|
return "2024-11-05";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,4 +22,8 @@ public interface McpStatelessServerTransport {
|
||||||
*/
|
*/
|
||||||
Mono<Void> closeGracefully();
|
Mono<Void> closeGracefully();
|
||||||
|
|
||||||
|
default String protocolVersion() {
|
||||||
|
return "2025-03-26";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue