Add Support for Jackson 3 (#742)

This change adds Jackson 3 support and aligns package naming in the existing Jackson 2 module.

Specific changes:
* Deprecated classes for removal in `io.modelcontextprotocol.json.jackson`/`io.modelcontextprotocol.json.schema.jackson`
* Copied the above to `io.modelcontextprotocol.json.jackson2`/`io.modelcontextprotocol.json.schema.jackson2` in non-deprecated form.
* Added tests for the default McpJsonMapper and JsonSchemaValidator
This commit is contained in:
Filip Hrisafov 2026-01-26 11:43:35 +01:00 • committed by GitHub
commit 2456a0e433
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 2487 additions and 21 deletions

View file

@ -281,6 +281,13 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
@Override
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
String jsonText;
try {
jsonText = jsonMapper.writeValueAsString(message);
}
catch (IOException e) {
return Mono.error(new RuntimeException("Failed to serialize message", e));
}
return Mono.create(sink -> {
logger.debug("Sending message {}", message);
// Here we attempt to initialize the client.
@ -293,6 +300,7 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
Disposable connection = Flux.deferContextual(ctx -> webClient.post()
.uri(this.endpoint)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON, MediaType.TEXT_EVENT_STREAM)
.header(HttpHeaders.PROTOCOL_VERSION,
ctx.getOrDefault(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION,
@ -300,7 +308,7 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
.headers(httpHeaders -> {
transportSession.sessionId().ifPresent(id -> httpHeaders.add(HttpHeaders.MCP_SESSION_ID, id));
})
.bodyValue(message)
.bodyValue(jsonText)
.exchangeToFlux(response -> {
if (transportSession
.markInitialized(response.headers().asHttpHeaders().getFirst(HttpHeaders.MCP_SESSION_ID))) {

View file

@ -10,9 +10,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.json.jackson.JacksonMcpJsonMapper;
import io.modelcontextprotocol.json.jackson3.JacksonMcpJsonMapper;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest;
import org.junit.jupiter.api.AfterAll;
@ -27,6 +26,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
import reactor.test.StepVerifier;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.reactive.function.client.WebClient;
@ -147,7 +147,7 @@ class WebFluxSseClientTransportTests {
assertThatCode(() -> transport1.closeGracefully().block()).doesNotThrowAnyException();
// Test builder with custom ObjectMapper
ObjectMapper customMapper = new ObjectMapper();
JsonMapper customMapper = JsonMapper.builder().build();
WebFluxSseClientTransport transport2 = WebFluxSseClientTransport.builder(webClientBuilder)
.jsonMapper(new JacksonMcpJsonMapper(customMapper))
.build();

View file

@ -118,7 +118,8 @@ public class WebMvcStatelessServerTransport implements McpStatelessServerTranspo
.handleRequest(transportContext, jsonrpcRequest)
.contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext))
.block();
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(jsonrpcResponse);
String json = jsonMapper.writeValueAsString(jsonrpcResponse);
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(json);
}
catch (Exception e) {
logger.error("Failed to handle request: {}", e.getMessage());