Basic version negotiation capabilities
- We do not support full version negotiation, but this commit allows the client to the send the correct version to match that of the server. - There is one limitation: the GET /mcp request happens too early, before we have deserialized the initialization response, so we do not know the negotiated version yet. This request will have the client latest version as Mcp-Protocol-Version value. Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
This commit is contained in:
parent
7e950ebb6c
commit
e6045f7287
4 changed files with 53 additions and 27 deletions
|
|
@ -287,7 +287,9 @@ class LifecycleInitializer {
|
|||
this.initializationRef.compareAndSet(newInit, null);
|
||||
return Mono.error(new RuntimeException("Client failed to initialize " + actionName, ex));
|
||||
})
|
||||
.flatMap(operation);
|
||||
.flatMap(res -> operation.apply(res)
|
||||
.contextWrite(c -> c.put(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION,
|
||||
res.initializeResult().protocolVersion())));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -319,6 +321,8 @@ class LifecycleInitializer {
|
|||
}
|
||||
|
||||
return mcpClientSession.sendNotification(McpSchema.METHOD_NOTIFICATION_INITIALIZED, null)
|
||||
.contextWrite(
|
||||
c -> c.put(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION, initializeResult.protocolVersion()))
|
||||
.thenReturn(initializeResult);
|
||||
}).flatMap(initializeResult -> {
|
||||
initialization.cacheResult(initializeResult);
|
||||
|
|
|
|||
|
|
@ -106,6 +106,8 @@ public class McpAsyncClient {
|
|||
public static final TypeRef<McpSchema.ProgressNotification> PROGRESS_NOTIFICATION_TYPE_REF = new TypeRef<>() {
|
||||
};
|
||||
|
||||
public static final String NEGOTIATED_PROTOCOL_VERSION = "io.modelcontextprotocol.client.negotiated-protocol-version";
|
||||
|
||||
/**
|
||||
* Client capabilities.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.modelcontextprotocol.client.McpAsyncClient;
|
||||
import io.modelcontextprotocol.client.transport.ResponseSubscribers.ResponseEvent;
|
||||
import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpClientRequestCustomizer;
|
||||
import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer;
|
||||
|
|
@ -193,7 +194,9 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
|||
.uri(uri)
|
||||
.header("Cache-Control", "no-cache")
|
||||
.header(HttpHeaders.MCP_SESSION_ID, sessionId)
|
||||
.header(HttpHeaders.PROTOCOL_VERSION, this.latestSupportedProtocolVersion)
|
||||
.header(HttpHeaders.PROTOCOL_VERSION,
|
||||
ctx.getOrDefault(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION,
|
||||
this.latestSupportedProtocolVersion))
|
||||
.DELETE();
|
||||
var transportContext = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY);
|
||||
return Mono.from(this.httpRequestCustomizer.customize(builder, "DELETE", uri, null, transportContext));
|
||||
|
|
@ -264,7 +267,9 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
|||
var builder = requestBuilder.uri(uri)
|
||||
.header(HttpHeaders.ACCEPT, TEXT_EVENT_STREAM)
|
||||
.header("Cache-Control", "no-cache")
|
||||
.header(HttpHeaders.PROTOCOL_VERSION, this.latestSupportedProtocolVersion)
|
||||
.header(HttpHeaders.PROTOCOL_VERSION,
|
||||
connectionCtx.getOrDefault(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION,
|
||||
this.latestSupportedProtocolVersion))
|
||||
.GET();
|
||||
var transportContext = connectionCtx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY);
|
||||
return Mono.from(this.httpRequestCustomizer.customize(builder, "GET", uri, null, transportContext));
|
||||
|
|
@ -439,7 +444,9 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
|||
.header(HttpHeaders.ACCEPT, APPLICATION_JSON + ", " + TEXT_EVENT_STREAM)
|
||||
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
|
||||
.header(HttpHeaders.CACHE_CONTROL, "no-cache")
|
||||
.header(HttpHeaders.PROTOCOL_VERSION, this.latestSupportedProtocolVersion)
|
||||
.header(HttpHeaders.PROTOCOL_VERSION,
|
||||
ctx.getOrDefault(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION,
|
||||
this.latestSupportedProtocolVersion))
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonBody));
|
||||
var transportContext = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY);
|
||||
return Mono
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
|
||||
package io.modelcontextprotocol.spec;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
/**
|
||||
* An abstraction of the session as perceived from the MCP transport layer. Not to be
|
||||
* confused with the {@link McpSession} type that operates at the level of the JSON-RPC
|
||||
|
|
|
|||
Loading…
Reference in a new issue