feat: Introduce McpJsonMapper interface to decouple from Jackson (#543)

This pull request creates two modules, `mcp-json` and `mcp-json-jackson`. It removes the `com.fasterxml.jackson.core:jackson-databind` and `com.networknt:json-schema-validator` dependencies from the `mcp` module. The `mcp` module now only depends on `com.fasterxml.jackson.core:jackson-annotations`.

To use Jackson, you have to add `mcp-jackson` to your dependencies in addition to `mcp`. I added the dependency `mcp-jackson` to both `mcp-spring-mvc` and `mcp-spring-webflux` to avoid a breaking change in those modules.

It provides two [SPI](https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html) `JsonSchemaValidatorSupplier` and `JacksonJsonSchemaValidatorSupplier` to allow easy replacement for consumers who don't want to use Jackson.

This pull request also ensures no `McpJsonMapper` is instantiated if one is provided via a builder method. Only if the builders don't receive a `McpJsonMapper` mapper, one is instantiated in the `build` method of the builder.
The logic behind this is to allow frameworks to provide a `McpJsonMapper` mapper singleton implementation and feed it to the builders without paying the price of instantiating `McpJsonMappers`, which will not be used. The goal is to be able to use the `ObjectMapper` singleton of an application also for the MCP code.

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
Graeme Rocher 2025-09-10 12:26:17 +02:00 • committed by Christian Tzolov
commit 80d0ad82a6
22 changed files with 2043 additions and 1421 deletions

View file

@ -18,11 +18,10 @@ import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.type.TypeReference;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.spec.McpClientSession;
import io.modelcontextprotocol.spec.McpClientTransport;
import io.modelcontextprotocol.spec.McpError;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.ClientCapabilities;
import io.modelcontextprotocol.spec.McpSchema.CreateMessageRequest;
@ -85,25 +84,25 @@ public class McpAsyncClient {
private static final Logger logger = LoggerFactory.getLogger(McpAsyncClient.class);
private static final TypeReference<Void> VOID_TYPE_REFERENCE = new TypeReference<>() {
private static final TypeRef<Void> VOID_TYPE_REFERENCE = new TypeRef<>() {
};
public static final TypeReference<Object> OBJECT_TYPE_REF = new TypeReference<>() {
public static final TypeRef<Object> OBJECT_TYPE_REF = new TypeRef<>() {
};
public static final TypeReference<PaginatedRequest> PAGINATED_REQUEST_TYPE_REF = new TypeReference<>() {
public static final TypeRef<PaginatedRequest> PAGINATED_REQUEST_TYPE_REF = new TypeRef<>() {
};
public static final TypeReference<McpSchema.InitializeResult> INITIALIZE_RESULT_TYPE_REF = new TypeReference<>() {
public static final TypeRef<McpSchema.InitializeResult> INITIALIZE_RESULT_TYPE_REF = new TypeRef<>() {
};
public static final TypeReference<CreateMessageRequest> CREATE_MESSAGE_REQUEST_TYPE_REF = new TypeReference<>() {
public static final TypeRef<CreateMessageRequest> CREATE_MESSAGE_REQUEST_TYPE_REF = new TypeRef<>() {
};
public static final TypeReference<LoggingMessageNotification> LOGGING_MESSAGE_NOTIFICATION_TYPE_REF = new TypeReference<>() {
public static final TypeRef<LoggingMessageNotification> LOGGING_MESSAGE_NOTIFICATION_TYPE_REF = new TypeRef<>() {
};
public static final TypeReference<McpSchema.ProgressNotification> PROGRESS_NOTIFICATION_TYPE_REF = new TypeReference<>() {
public static final TypeRef<McpSchema.ProgressNotification> PROGRESS_NOTIFICATION_TYPE_REF = new TypeRef<>() {
};
/**
@ -512,7 +511,7 @@ public class McpAsyncClient {
// --------------------------
private RequestHandler<ElicitResult> elicitationCreateHandler() {
return params -> {
ElicitRequest request = transport.unmarshalFrom(params, new TypeReference<>() {
ElicitRequest request = transport.unmarshalFrom(params, new TypeRef<>() {
});
return this.elicitationHandler.apply(request);
@ -522,10 +521,10 @@ public class McpAsyncClient {
// --------------------------
// Tools
// --------------------------
private static final TypeReference<McpSchema.CallToolResult> CALL_TOOL_RESULT_TYPE_REF = new TypeReference<>() {
private static final TypeRef<McpSchema.CallToolResult> CALL_TOOL_RESULT_TYPE_REF = new TypeRef<>() {
};
private static final TypeReference<McpSchema.ListToolsResult> LIST_TOOLS_RESULT_TYPE_REF = new TypeReference<>() {
private static final TypeRef<McpSchema.ListToolsResult> LIST_TOOLS_RESULT_TYPE_REF = new TypeRef<>() {
};
/**
@ -596,13 +595,13 @@ public class McpAsyncClient {
// Resources
// --------------------------
private static final TypeReference<McpSchema.ListResourcesResult> LIST_RESOURCES_RESULT_TYPE_REF = new TypeReference<>() {
private static final TypeRef<McpSchema.ListResourcesResult> LIST_RESOURCES_RESULT_TYPE_REF = new TypeRef<>() {
};
private static final TypeReference<McpSchema.ReadResourceResult> READ_RESOURCE_RESULT_TYPE_REF = new TypeReference<>() {
private static final TypeRef<McpSchema.ReadResourceResult> READ_RESOURCE_RESULT_TYPE_REF = new TypeRef<>() {
};
private static final TypeReference<McpSchema.ListResourceTemplatesResult> LIST_RESOURCE_TEMPLATES_RESULT_TYPE_REF = new TypeReference<>() {
private static final TypeRef<McpSchema.ListResourceTemplatesResult> LIST_RESOURCE_TEMPLATES_RESULT_TYPE_REF = new TypeRef<>() {
};
/**
@ -756,7 +755,7 @@ public class McpAsyncClient {
List<Function<List<McpSchema.ResourceContents>, Mono<Void>>> resourcesUpdateConsumers) {
return params -> {
McpSchema.ResourcesUpdatedNotification resourcesUpdatedNotification = transport.unmarshalFrom(params,
new TypeReference<>() {
new TypeRef<>() {
});
return readResource(new McpSchema.ReadResourceRequest(resourcesUpdatedNotification.uri()))
@ -773,10 +772,10 @@ public class McpAsyncClient {
// --------------------------
// Prompts
// --------------------------
private static final TypeReference<McpSchema.ListPromptsResult> LIST_PROMPTS_RESULT_TYPE_REF = new TypeReference<>() {
private static final TypeRef<McpSchema.ListPromptsResult> LIST_PROMPTS_RESULT_TYPE_REF = new TypeRef<>() {
};
private static final TypeReference<McpSchema.GetPromptResult> GET_PROMPT_RESULT_TYPE_REF = new TypeReference<>() {
private static final TypeRef<McpSchema.GetPromptResult> GET_PROMPT_RESULT_TYPE_REF = new TypeRef<>() {
};
/**
@ -911,7 +910,7 @@ public class McpAsyncClient {
// --------------------------
// Completions
// --------------------------
private static final TypeReference<McpSchema.CompleteResult> COMPLETION_COMPLETE_RESULT_TYPE_REF = new TypeReference<>() {
private static final TypeRef<McpSchema.CompleteResult> COMPLETION_COMPLETE_RESULT_TYPE_REF = new TypeRef<>() {
};
/**

View file

@ -19,8 +19,8 @@ import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpClientRequestCustomizer;
import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer;
@ -97,8 +97,8 @@ public class HttpClientSseClientTransport implements McpClientTransport {
/** HTTP request builder for building requests to send messages to the server */
private final HttpRequest.Builder requestBuilder;
/** JSON object mapper for message serialization/deserialization */
protected ObjectMapper objectMapper;
/** JSON mapper for message serialization/deserialization */
protected McpJsonMapper jsonMapper;
/** Flag indicating if the transport is in closing state */
private volatile boolean isClosing = false;
@ -117,65 +117,6 @@ public class HttpClientSseClientTransport implements McpClientTransport {
*/
private final McpAsyncHttpClientRequestCustomizer httpRequestCustomizer;
/**
* Creates a new transport instance with default HTTP client and object mapper.
* @param baseUri the base URI of the MCP server
* @deprecated Use {@link HttpClientSseClientTransport#builder(String)} instead. This
* constructor will be removed in future versions.
*/
@Deprecated(forRemoval = true)
public HttpClientSseClientTransport(String baseUri) {
this(HttpClient.newBuilder(), baseUri, new ObjectMapper());
}
/**
* Creates a new transport instance with custom HTTP client builder and object mapper.
* @param clientBuilder the HTTP client builder to use
* @param baseUri the base URI of the MCP server
* @param objectMapper the object mapper for JSON serialization/deserialization
* @throws IllegalArgumentException if objectMapper or clientBuilder is null
* @deprecated Use {@link HttpClientSseClientTransport#builder(String)} instead. This
* constructor will be removed in future versions.
*/
@Deprecated(forRemoval = true)
public HttpClientSseClientTransport(HttpClient.Builder clientBuilder, String baseUri, ObjectMapper objectMapper) {
this(clientBuilder, baseUri, DEFAULT_SSE_ENDPOINT, objectMapper);
}
/**
* Creates a new transport instance with custom HTTP client builder and object mapper.
* @param clientBuilder the HTTP client builder to use
* @param baseUri the base URI of the MCP server
* @param sseEndpoint the SSE endpoint path
* @param objectMapper the object mapper for JSON serialization/deserialization
* @throws IllegalArgumentException if objectMapper or clientBuilder is null
* @deprecated Use {@link HttpClientSseClientTransport#builder(String)} instead. This
* constructor will be removed in future versions.
*/
@Deprecated(forRemoval = true)
public HttpClientSseClientTransport(HttpClient.Builder clientBuilder, String baseUri, String sseEndpoint,
ObjectMapper objectMapper) {
this(clientBuilder, HttpRequest.newBuilder(), baseUri, sseEndpoint, objectMapper);
}
/**
* Creates a new transport instance with custom HTTP client builder, object mapper,
* and headers.
* @param clientBuilder the HTTP client builder to use
* @param requestBuilder the HTTP request builder to use
* @param baseUri the base URI of the MCP server
* @param sseEndpoint the SSE endpoint path
* @param objectMapper the object mapper for JSON serialization/deserialization
* @throws IllegalArgumentException if objectMapper, clientBuilder, or headers is null
* @deprecated Use {@link HttpClientSseClientTransport#builder(String)} instead. This
* constructor will be removed in future versions.
*/
@Deprecated(forRemoval = true)
public HttpClientSseClientTransport(HttpClient.Builder clientBuilder, HttpRequest.Builder requestBuilder,
String baseUri, String sseEndpoint, ObjectMapper objectMapper) {
this(clientBuilder.build(), requestBuilder, baseUri, sseEndpoint, objectMapper);
}
/**
* Creates a new transport instance with custom HTTP client builder, object mapper,
* and headers.
@ -183,30 +124,14 @@ public class HttpClientSseClientTransport implements McpClientTransport {
* @param requestBuilder the HTTP request builder to use
* @param baseUri the base URI of the MCP server
* @param sseEndpoint the SSE endpoint path
* @param objectMapper the object mapper for JSON serialization/deserialization
* @throws IllegalArgumentException if objectMapper, clientBuilder, or headers is null
*/
@Deprecated(forRemoval = true)
HttpClientSseClientTransport(HttpClient httpClient, HttpRequest.Builder requestBuilder, String baseUri,
String sseEndpoint, ObjectMapper objectMapper) {
this(httpClient, requestBuilder, baseUri, sseEndpoint, objectMapper, McpAsyncHttpClientRequestCustomizer.NOOP);
}
/**
* Creates a new transport instance with custom HTTP client builder, object mapper,
* and headers.
* @param httpClient the HTTP client to use
* @param requestBuilder the HTTP request builder to use
* @param baseUri the base URI of the MCP server
* @param sseEndpoint the SSE endpoint path
* @param objectMapper the object mapper for JSON serialization/deserialization
* @param jsonMapper the object mapper for JSON serialization/deserialization
* @param httpRequestCustomizer customizer for the requestBuilder before executing
* requests
* @throws IllegalArgumentException if objectMapper, clientBuilder, or headers is null
*/
HttpClientSseClientTransport(HttpClient httpClient, HttpRequest.Builder requestBuilder, String baseUri,
String sseEndpoint, ObjectMapper objectMapper, McpAsyncHttpClientRequestCustomizer httpRequestCustomizer) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
String sseEndpoint, McpJsonMapper jsonMapper, McpAsyncHttpClientRequestCustomizer httpRequestCustomizer) {
Assert.notNull(jsonMapper, "jsonMapper must not be null");
Assert.hasText(baseUri, "baseUri must not be empty");
Assert.hasText(sseEndpoint, "sseEndpoint must not be empty");
Assert.notNull(httpClient, "httpClient must not be null");
@ -214,7 +139,7 @@ public class HttpClientSseClientTransport implements McpClientTransport {
Assert.notNull(httpRequestCustomizer, "httpRequestCustomizer must not be null");
this.baseUri = URI.create(baseUri);
this.sseEndpoint = sseEndpoint;
this.objectMapper = objectMapper;
this.jsonMapper = jsonMapper;
this.httpClient = httpClient;
this.requestBuilder = requestBuilder;
this.httpRequestCustomizer = httpRequestCustomizer;
@ -245,7 +170,7 @@ public class HttpClientSseClientTransport implements McpClientTransport {
private HttpClient.Builder clientBuilder = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1);
private ObjectMapper objectMapper = new ObjectMapper();
private McpJsonMapper jsonMapper;
private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
@ -340,13 +265,13 @@ public class HttpClientSseClientTransport implements McpClientTransport {
}
/**
* Sets the object mapper for JSON serialization/deserialization.
* @param objectMapper the object mapper
* Sets the JSON mapper implementation to use for serialization/deserialization.
* @param jsonMapper the JSON mapper
* @return this builder
*/
public Builder objectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "objectMapper must not be null");
this.objectMapper = objectMapper;
public Builder jsonMapper(McpJsonMapper jsonMapper) {
Assert.notNull(jsonMapper, "jsonMapper must not be null");
this.jsonMapper = jsonMapper;
return this;
}
@ -402,8 +327,8 @@ public class HttpClientSseClientTransport implements McpClientTransport {
*/
public HttpClientSseClientTransport build() {
HttpClient httpClient = this.clientBuilder.connectTimeout(this.connectTimeout).build();
return new HttpClientSseClientTransport(httpClient, requestBuilder, baseUri, sseEndpoint, objectMapper,
httpRequestCustomizer);
return new HttpClientSseClientTransport(httpClient, requestBuilder, baseUri, sseEndpoint,
jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, httpRequestCustomizer);
}
}
@ -450,7 +375,7 @@ public class HttpClientSseClientTransport implements McpClientTransport {
}
}
else if (MESSAGE_EVENT_TYPE.equals(responseEvent.sseEvent().event())) {
JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper,
JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper,
responseEvent.sseEvent().data());
sink.success();
return Flux.just(message);
@ -531,7 +456,7 @@ public class HttpClientSseClientTransport implements McpClientTransport {
private Mono<String> serializeMessage(final JSONRPCMessage message) {
return Mono.defer(() -> {
try {
return Mono.just(objectMapper.writeValueAsString(message));
return Mono.just(jsonMapper.writeValueAsString(message));
}
catch (IOException e) {
return Mono.error(new McpTransportException("Failed to serialize message", e));
@ -582,8 +507,8 @@ public class HttpClientSseClientTransport implements McpClientTransport {
* @return the unmarshalled object
*/
@Override
public <T> T unmarshalFrom(Object data, TypeReference<T> typeRef) {
return this.objectMapper.convertValue(data, typeRef);
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return this.jsonMapper.convertValue(data, typeRef);
}
}

View file

@ -22,8 +22,8 @@ import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpClientRequestCustomizer;
import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer;
@ -106,7 +106,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
public static int BAD_REQUEST = 400;
private final ObjectMapper objectMapper;
private final McpJsonMapper jsonMapper;
private final URI baseUri;
@ -124,10 +124,10 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
private final AtomicReference<Consumer<Throwable>> exceptionHandler = new AtomicReference<>();
private HttpClientStreamableHttpTransport(ObjectMapper objectMapper, HttpClient httpClient,
private HttpClientStreamableHttpTransport(McpJsonMapper jsonMapper, HttpClient httpClient,
HttpRequest.Builder requestBuilder, String baseUri, String endpoint, boolean resumableStreams,
boolean openConnectionOnStartup, McpAsyncHttpClientRequestCustomizer httpRequestCustomizer) {
this.objectMapper = objectMapper;
this.jsonMapper = jsonMapper;
this.httpClient = httpClient;
this.requestBuilder = requestBuilder;
this.baseUri = URI.create(baseUri);
@ -278,7 +278,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
// won't since the next version considers
// removing it.
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(
this.objectMapper, responseEvent.sseEvent().data());
this.jsonMapper, responseEvent.sseEvent().data());
Tuple2<Optional<String>, Iterable<McpSchema.JSONRPCMessage>> idWithMessages = Tuples
.of(Optional.ofNullable(responseEvent.sseEvent().id()),
@ -393,7 +393,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
public String toString(McpSchema.JSONRPCMessage message) {
try {
return this.objectMapper.writeValueAsString(message);
return this.jsonMapper.writeValueAsString(message);
}
catch (IOException e) {
throw new RuntimeException("Failed to serialize JSON-RPC message", e);
@ -479,7 +479,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
// since the
// next version considers removing it.
McpSchema.JSONRPCMessage message = McpSchema
.deserializeJsonRpcMessage(this.objectMapper, sseEvent.data());
.deserializeJsonRpcMessage(this.jsonMapper, sseEvent.data());
Tuple2<Optional<String>, Iterable<McpSchema.JSONRPCMessage>> idWithMessages = Tuples
.of(Optional.ofNullable(sseEvent.id()), List.of(message));
@ -508,7 +508,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
}
try {
return Mono.just(McpSchema.deserializeJsonRpcMessage(objectMapper, data));
return Mono.just(McpSchema.deserializeJsonRpcMessage(jsonMapper, data));
}
catch (IOException e) {
return Mono.error(new McpTransportException(
@ -582,8 +582,8 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
}
@Override
public <T> T unmarshalFrom(Object data, TypeReference<T> typeRef) {
return this.objectMapper.convertValue(data, typeRef);
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return this.jsonMapper.convertValue(data, typeRef);
}
/**
@ -593,7 +593,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
private final String baseUri;
private ObjectMapper objectMapper;
private McpJsonMapper jsonMapper;
private HttpClient.Builder clientBuilder = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1);
@ -663,13 +663,13 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
}
/**
* Configure the {@link ObjectMapper} to use.
* @param objectMapper instance to use
* Configure a custom {@link McpJsonMapper} implementation to use.
* @param jsonMapper instance to use
* @return the builder instance
*/
public Builder objectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
public Builder jsonMapper(McpJsonMapper jsonMapper) {
Assert.notNull(jsonMapper, "jsonMapper must not be null");
this.jsonMapper = jsonMapper;
return this;
}
@ -763,12 +763,10 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
* @return a new instance of {@link HttpClientStreamableHttpTransport}
*/
public HttpClientStreamableHttpTransport build() {
ObjectMapper objectMapper = this.objectMapper != null ? this.objectMapper : new ObjectMapper();
HttpClient httpClient = this.clientBuilder.connectTimeout(this.connectTimeout).build();
return new HttpClientStreamableHttpTransport(objectMapper, httpClient, requestBuilder, baseUri, endpoint,
resumableStreams, openConnectionOnStartup, httpRequestCustomizer);
return new HttpClientStreamableHttpTransport(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
httpClient, requestBuilder, baseUri, endpoint, resumableStreams, openConnectionOnStartup,
httpRequestCustomizer);
}
}

View file

@ -15,8 +15,8 @@ import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.Function;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.spec.McpClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.JSONRPCMessage;
@ -48,7 +48,7 @@ public class StdioClientTransport implements McpClientTransport {
/** The server process being communicated with */
private Process process;
private ObjectMapper objectMapper;
private McpJsonMapper jsonMapper;
/** Scheduler for handling inbound messages from the server process */
private Scheduler inboundScheduler;
@ -70,29 +70,20 @@ public class StdioClientTransport implements McpClientTransport {
private Consumer<String> stdErrorHandler = error -> logger.info("STDERR Message received: {}", error);
/**
* Creates a new StdioClientTransport with the specified parameters and default
* ObjectMapper.
* Creates a new StdioClientTransport with the specified parameters and JsonMapper.
* @param params The parameters for configuring the server process
* @param jsonMapper The JsonMapper to use for JSON serialization/deserialization
*/
public StdioClientTransport(ServerParameters params) {
this(params, new ObjectMapper());
}
/**
* Creates a new StdioClientTransport with the specified parameters and ObjectMapper.
* @param params The parameters for configuring the server process
* @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
*/
public StdioClientTransport(ServerParameters params, ObjectMapper objectMapper) {
public StdioClientTransport(ServerParameters params, McpJsonMapper jsonMapper) {
Assert.notNull(params, "The params can not be null");
Assert.notNull(objectMapper, "The ObjectMapper can not be null");
Assert.notNull(jsonMapper, "The JsonMapper can not be null");
this.inboundSink = Sinks.many().unicast().onBackpressureBuffer();
this.outboundSink = Sinks.many().unicast().onBackpressureBuffer();
this.params = params;
this.objectMapper = objectMapper;
this.jsonMapper = jsonMapper;
this.errorSink = Sinks.many().unicast().onBackpressureBuffer();
@ -259,7 +250,7 @@ public class StdioClientTransport implements McpClientTransport {
String line;
while (!isClosing && (line = processReader.readLine()) != null) {
try {
JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.objectMapper, line);
JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.jsonMapper, line);
if (!this.inboundSink.tryEmitNext(message).isSuccess()) {
if (!isClosing) {
logger.error("Failed to enqueue inbound message: {}", message);
@ -300,7 +291,7 @@ public class StdioClientTransport implements McpClientTransport {
.handle((message, s) -> {
if (message != null && !isClosing) {
try {
String jsonMessage = objectMapper.writeValueAsString(message);
String jsonMessage = jsonMapper.writeValueAsString(message);
// Escape any embedded newlines in the JSON message as per spec:
// https://spec.modelcontextprotocol.io/specification/basic/transports/#stdio
// - Messages are delimited by newlines, and MUST NOT contain
@ -392,8 +383,8 @@ public class StdioClientTransport implements McpClientTransport {
}
@Override
public <T> T unmarshalFrom(Object data, TypeReference<T> typeRef) {
return this.objectMapper.convertValue(data, typeRef);
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return this.jsonMapper.convertValue(data, typeRef);
}
}

View file

@ -15,10 +15,16 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiFunction;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.spec.DefaultMcpStreamableServerSessionFactory;
import io.modelcontextprotocol.spec.JsonSchemaValidator;
import io.modelcontextprotocol.spec.McpServerTransportProviderBase;
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.json.schema.JsonSchemaValidator;
import io.modelcontextprotocol.spec.McpClientSession;
import io.modelcontextprotocol.spec.McpError;
import io.modelcontextprotocol.spec.McpSchema;
@ -90,7 +96,7 @@ public class McpAsyncServer {
private final McpServerTransportProviderBase mcpTransportProvider;
private final ObjectMapper objectMapper;
private final McpJsonMapper jsonMapper;
private final JsonSchemaValidator jsonSchemaValidator;
@ -123,13 +129,13 @@ public class McpAsyncServer {
* @param mcpTransportProvider The transport layer implementation for MCP
* communication.
* @param features The MCP server supported features.
* @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
* @param jsonMapper The JsonMapper to use for JSON serialization/deserialization
*/
McpAsyncServer(McpServerTransportProvider mcpTransportProvider, ObjectMapper objectMapper,
McpAsyncServer(McpServerTransportProvider mcpTransportProvider, McpJsonMapper jsonMapper,
McpServerFeatures.Async features, Duration requestTimeout,
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator) {
this.mcpTransportProvider = mcpTransportProvider;
this.objectMapper = objectMapper;
this.jsonMapper = jsonMapper;
this.serverInfo = features.serverInfo();
this.serverCapabilities = features.serverCapabilities().mutate().logging().build();
this.instructions = features.instructions();
@ -150,11 +156,11 @@ public class McpAsyncServer {
requestTimeout, transport, this::asyncInitializeRequestHandler, requestHandlers, notificationHandlers));
}
McpAsyncServer(McpStreamableServerTransportProvider mcpTransportProvider, ObjectMapper objectMapper,
McpAsyncServer(McpStreamableServerTransportProvider mcpTransportProvider, McpJsonMapper jsonMapper,
McpServerFeatures.Async features, Duration requestTimeout,
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator) {
this.mcpTransportProvider = mcpTransportProvider;
this.objectMapper = objectMapper;
this.jsonMapper = jsonMapper;
this.serverInfo = features.serverInfo();
this.serverCapabilities = features.serverCapabilities().mutate().logging().build();
this.instructions = features.instructions();
@ -505,8 +511,8 @@ public class McpAsyncServer {
private McpRequestHandler<CallToolResult> toolsCallRequestHandler() {
return (exchange, params) -> {
McpSchema.CallToolRequest callToolRequest = objectMapper.convertValue(params,
new TypeReference<McpSchema.CallToolRequest>() {
McpSchema.CallToolRequest callToolRequest = jsonMapper.convertValue(params,
new TypeRef<McpSchema.CallToolRequest>() {
});
Optional<McpServerFeatures.AsyncToolSpecification> toolSpecification = this.tools.stream()
@ -633,8 +639,8 @@ public class McpAsyncServer {
private McpRequestHandler<McpSchema.ReadResourceResult> resourcesReadRequestHandler() {
return (exchange, params) -> {
McpSchema.ReadResourceRequest resourceRequest = objectMapper.convertValue(params,
new TypeReference<McpSchema.ReadResourceRequest>() {
McpSchema.ReadResourceRequest resourceRequest = jsonMapper.convertValue(params,
new TypeRef<McpSchema.ReadResourceRequest>() {
});
var resourceUri = resourceRequest.uri();
@ -742,8 +748,8 @@ public class McpAsyncServer {
private McpRequestHandler<McpSchema.GetPromptResult> promptsGetRequestHandler() {
return (exchange, params) -> {
McpSchema.GetPromptRequest promptRequest = objectMapper.convertValue(params,
new TypeReference<McpSchema.GetPromptRequest>() {
McpSchema.GetPromptRequest promptRequest = jsonMapper.convertValue(params,
new TypeRef<McpSchema.GetPromptRequest>() {
});
// Implement prompt retrieval logic here
@ -790,9 +796,8 @@ public class McpAsyncServer {
return (exchange, params) -> {
return Mono.defer(() -> {
SetLevelRequest newMinLoggingLevel = objectMapper.convertValue(params,
new TypeReference<SetLevelRequest>() {
});
SetLevelRequest newMinLoggingLevel = jsonMapper.convertValue(params, new TypeRef<SetLevelRequest>() {
});
exchange.setMinLoggingLevel(newMinLoggingLevel.level());
@ -914,4 +919,4 @@ public class McpAsyncServer {
this.protocolVersions = protocolVersions;
}
}
}

View file

@ -8,7 +8,7 @@ import io.modelcontextprotocol.common.McpTransportContext;
import java.util.ArrayList;
import java.util.Collections;
import com.fasterxml.jackson.core.type.TypeReference;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.spec.McpError;
import io.modelcontextprotocol.spec.McpLoggableSession;
import io.modelcontextprotocol.spec.McpSchema;
@ -37,16 +37,16 @@ public class McpAsyncServerExchange {
private final McpTransportContext transportContext;
private static final TypeReference<McpSchema.CreateMessageResult> CREATE_MESSAGE_RESULT_TYPE_REF = new TypeReference<>() {
private static final TypeRef<McpSchema.CreateMessageResult> CREATE_MESSAGE_RESULT_TYPE_REF = new TypeRef<>() {
};
private static final TypeReference<McpSchema.ListRootsResult> LIST_ROOTS_RESULT_TYPE_REF = new TypeReference<>() {
private static final TypeRef<McpSchema.ListRootsResult> LIST_ROOTS_RESULT_TYPE_REF = new TypeRef<>() {
};
private static final TypeReference<McpSchema.ElicitResult> ELICITATION_RESULT_TYPE_REF = new TypeReference<>() {
private static final TypeRef<McpSchema.ElicitResult> ELICITATION_RESULT_TYPE_REF = new TypeRef<>() {
};
public static final TypeReference<Object> OBJECT_TYPE_REF = new TypeReference<>() {
public static final TypeRef<Object> OBJECT_TYPE_REF = new TypeRef<>() {
};
/**

View file

@ -11,13 +11,13 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.spec.DefaultJsonSchemaValidator;
import io.modelcontextprotocol.spec.JsonSchemaValidator;
import io.modelcontextprotocol.json.schema.JsonSchemaValidator;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
import io.modelcontextprotocol.spec.McpSchema.ResourceTemplate;
@ -67,7 +67,7 @@ import reactor.core.publisher.Mono;
* Example of creating a basic synchronous server: <pre>{@code
* McpServer.sync(transportProvider)
* .serverInfo("my-server", "1.0.0")
* .tool(new Tool("calculator", "Performs calculations", schema),
* .tool(Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
* (exchange, args) -> new CallToolResult("Result: " + calculate(args)))
* .build();
* }</pre>
@ -75,7 +75,7 @@ import reactor.core.publisher.Mono;
* Example of creating a basic asynchronous server: <pre>{@code
* McpServer.async(transportProvider)
* .serverInfo("my-server", "1.0.0")
* .tool(new Tool("calculator", "Performs calculations", schema),
* .tool(Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
* (exchange, args) -> Mono.fromSupplier(() -> calculate(args))
* .map(result -> new CallToolResult("Result: " + result)))
* .build();
@ -227,11 +227,12 @@ public interface McpServer {
var features = new McpServerFeatures.Async(this.serverInfo, this.serverCapabilities, this.tools,
this.resources, this.resourceTemplates, this.prompts, this.completions, this.rootsChangeHandlers,
this.instructions);
var mapper = this.objectMapper != null ? this.objectMapper : new ObjectMapper();
var jsonSchemaValidator = this.jsonSchemaValidator != null ? this.jsonSchemaValidator
: new DefaultJsonSchemaValidator(mapper);
return new McpAsyncServer(this.transportProvider, mapper, features, this.requestTimeout,
this.uriTemplateManagerFactory, jsonSchemaValidator);
var jsonSchemaValidator = (this.jsonSchemaValidator != null) ? this.jsonSchemaValidator
: JsonSchemaValidator.getDefault();
return new McpAsyncServer(transportProvider, jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
features, requestTimeout, uriTemplateManagerFactory, jsonSchemaValidator);
}
}
@ -254,11 +255,10 @@ public interface McpServer {
var features = new McpServerFeatures.Async(this.serverInfo, this.serverCapabilities, this.tools,
this.resources, this.resourceTemplates, this.prompts, this.completions, this.rootsChangeHandlers,
this.instructions);
var mapper = this.objectMapper != null ? this.objectMapper : new ObjectMapper();
var jsonSchemaValidator = this.jsonSchemaValidator != null ? this.jsonSchemaValidator
: new DefaultJsonSchemaValidator(mapper);
return new McpAsyncServer(this.transportProvider, mapper, features, this.requestTimeout,
this.uriTemplateManagerFactory, jsonSchemaValidator);
: JsonSchemaValidator.getDefault();
return new McpAsyncServer(transportProvider, jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
features, requestTimeout, uriTemplateManagerFactory, jsonSchemaValidator);
}
}
@ -270,7 +270,7 @@ public interface McpServer {
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
ObjectMapper objectMapper;
McpJsonMapper jsonMapper;
McpSchema.Implementation serverInfo = DEFAULT_SERVER_INFO;
@ -417,7 +417,7 @@ public interface McpServer {
* <p>
* Example usage: <pre>{@code
* .tool(
* new Tool("calculator", "Performs calculations", schema),
* Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
* (exchange, args) -> Mono.fromSupplier(() -> calculate(args))
* .map(result -> new CallToolResult("Result: " + result))
* )
@ -765,14 +765,14 @@ public interface McpServer {
}
/**
* Sets the object mapper to use for serializing and deserializing JSON messages.
* @param objectMapper the instance to use. Must not be null.
* Sets the JsonMapper to use for serializing and deserializing JSON messages.
* @param jsonMapper the mapper to use. Must not be null.
* @return This builder instance for method chaining.
* @throws IllegalArgumentException if objectMapper is null
* @throws IllegalArgumentException if jsonMapper is null
*/
public AsyncSpecification<S> objectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
public AsyncSpecification<S> jsonMapper(McpJsonMapper jsonMapper) {
Assert.notNull(jsonMapper, "JsonMapper must not be null");
this.jsonMapper = jsonMapper;
return this;
}
@ -813,13 +813,11 @@ public interface McpServer {
this.rootsChangeHandlers, this.instructions);
McpServerFeatures.Async asyncFeatures = McpServerFeatures.Async.fromSync(syncFeatures,
this.immediateExecution);
var mapper = this.objectMapper != null ? this.objectMapper : new ObjectMapper();
var jsonSchemaValidator = this.jsonSchemaValidator != null ? this.jsonSchemaValidator
: new DefaultJsonSchemaValidator(mapper);
var asyncServer = new McpAsyncServer(this.transportProvider, mapper, asyncFeatures, this.requestTimeout,
this.uriTemplateManagerFactory, jsonSchemaValidator);
var asyncServer = new McpAsyncServer(transportProvider,
jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, asyncFeatures, requestTimeout,
uriTemplateManagerFactory,
jsonSchemaValidator != null ? jsonSchemaValidator : JsonSchemaValidator.getDefault());
return new McpSyncServer(asyncServer, this.immediateExecution);
}
@ -846,13 +844,11 @@ public interface McpServer {
this.rootsChangeHandlers, this.instructions);
McpServerFeatures.Async asyncFeatures = McpServerFeatures.Async.fromSync(syncFeatures,
this.immediateExecution);
var mapper = this.objectMapper != null ? this.objectMapper : new ObjectMapper();
var jsonSchemaValidator = this.jsonSchemaValidator != null ? this.jsonSchemaValidator
: new DefaultJsonSchemaValidator(mapper);
var asyncServer = new McpAsyncServer(this.transportProvider, mapper, asyncFeatures, this.requestTimeout,
: JsonSchemaValidator.getDefault();
var asyncServer = new McpAsyncServer(transportProvider,
jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, asyncFeatures, this.requestTimeout,
this.uriTemplateManagerFactory, jsonSchemaValidator);
return new McpSyncServer(asyncServer, this.immediateExecution);
}
@ -865,7 +861,7 @@ public interface McpServer {
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
ObjectMapper objectMapper;
McpJsonMapper jsonMapper;
McpSchema.Implementation serverInfo = DEFAULT_SERVER_INFO;
@ -1014,7 +1010,7 @@ public interface McpServer {
* <p>
* Example usage: <pre>{@code
* .tool(
* new Tool("calculator", "Performs calculations", schema),
* Tool.builder().name("calculator").title("Performs calculations".inputSchema(schema).build(),
* (exchange, args) -> new CallToolResult("Result: " + calculate(args))
* )
* }</pre>
@ -1363,14 +1359,14 @@ public interface McpServer {
}
/**
* Sets the object mapper to use for serializing and deserializing JSON messages.
* @param objectMapper the instance to use. Must not be null.
* Sets the JsonMapper to use for serializing and deserializing JSON messages.
* @param jsonMapper the mapper to use. Must not be null.
* @return This builder instance for method chaining.
* @throws IllegalArgumentException if objectMapper is null
* @throws IllegalArgumentException if jsonMapper is null
*/
public SyncSpecification<S> objectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
public SyncSpecification<S> jsonMapper(McpJsonMapper jsonMapper) {
Assert.notNull(jsonMapper, "JsonMapper must not be null");
this.jsonMapper = jsonMapper;
return this;
}
@ -1404,7 +1400,7 @@ public interface McpServer {
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
ObjectMapper objectMapper;
McpJsonMapper jsonMapper;
McpSchema.Implementation serverInfo = DEFAULT_SERVER_INFO;
@ -1821,14 +1817,14 @@ public interface McpServer {
}
/**
* Sets the object mapper to use for serializing and deserializing JSON messages.
* @param objectMapper the instance to use. Must not be null.
* Sets the JsonMapper to use for serializing and deserializing JSON messages.
* @param jsonMapper the mapper to use. Must not be null.
* @return This builder instance for method chaining.
* @throws IllegalArgumentException if objectMapper is null
* @throws IllegalArgumentException if jsonMapper is null
*/
public StatelessAsyncSpecification objectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
public StatelessAsyncSpecification jsonMapper(McpJsonMapper jsonMapper) {
Assert.notNull(jsonMapper, "JsonMapper must not be null");
this.jsonMapper = jsonMapper;
return this;
}
@ -1849,11 +1845,9 @@ public interface McpServer {
public McpStatelessAsyncServer build() {
var features = new McpStatelessServerFeatures.Async(this.serverInfo, this.serverCapabilities, this.tools,
this.resources, this.resourceTemplates, this.prompts, this.completions, this.instructions);
var mapper = this.objectMapper != null ? this.objectMapper : new ObjectMapper();
var jsonSchemaValidator = this.jsonSchemaValidator != null ? this.jsonSchemaValidator
: new DefaultJsonSchemaValidator(mapper);
return new McpStatelessAsyncServer(this.transport, mapper, features, this.requestTimeout,
this.uriTemplateManagerFactory, jsonSchemaValidator);
return new McpStatelessAsyncServer(transport, jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
features, requestTimeout, uriTemplateManagerFactory,
jsonSchemaValidator != null ? jsonSchemaValidator : JsonSchemaValidator.getDefault());
}
}
@ -1866,7 +1860,7 @@ public interface McpServer {
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
ObjectMapper objectMapper;
McpJsonMapper jsonMapper;
McpSchema.Implementation serverInfo = DEFAULT_SERVER_INFO;
@ -2283,14 +2277,14 @@ public interface McpServer {
}
/**
* Sets the object mapper to use for serializing and deserializing JSON messages.
* @param objectMapper the instance to use. Must not be null.
* Sets the JsonMapper to use for serializing and deserializing JSON messages.
* @param jsonMapper the mapper to use. Must not be null.
* @return This builder instance for method chaining.
* @throws IllegalArgumentException if objectMapper is null
* @throws IllegalArgumentException if jsonMapper is null
*/
public StatelessSyncSpecification objectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
public StatelessSyncSpecification jsonMapper(McpJsonMapper jsonMapper) {
Assert.notNull(jsonMapper, "JsonMapper must not be null");
this.jsonMapper = jsonMapper;
return this;
}
@ -2325,31 +2319,13 @@ public interface McpServer {
}
public McpStatelessSyncServer build() {
/*
* McpServerFeatures.Sync syncFeatures = new
* McpServerFeatures.Sync(this.serverInfo, this.serverCapabilities,
* this.tools, this.resources, this.resourceTemplates, this.prompts,
* this.completions, this.rootsChangeHandlers, this.instructions);
* McpServerFeatures.Async asyncFeatures =
* McpServerFeatures.Async.fromSync(syncFeatures, this.immediateExecution);
* var mapper = this.objectMapper != null ? this.objectMapper : new
* ObjectMapper(); var jsonSchemaValidator = this.jsonSchemaValidator != null
* ? this.jsonSchemaValidator : new DefaultJsonSchemaValidator(mapper);
*
* var asyncServer = new McpAsyncServer(this.transportProvider, mapper,
* asyncFeatures, this.requestTimeout, this.uriTemplateManagerFactory,
* jsonSchemaValidator);
*
* return new McpSyncServer(asyncServer, this.immediateExecution);
*/
var syncFeatures = new McpStatelessServerFeatures.Sync(this.serverInfo, this.serverCapabilities, this.tools,
this.resources, this.resourceTemplates, this.prompts, this.completions, this.instructions);
var asyncFeatures = McpStatelessServerFeatures.Async.fromSync(syncFeatures, this.immediateExecution);
var mapper = this.objectMapper != null ? this.objectMapper : new ObjectMapper();
var jsonSchemaValidator = this.jsonSchemaValidator != null ? this.jsonSchemaValidator
: new DefaultJsonSchemaValidator(mapper);
var asyncServer = new McpStatelessAsyncServer(this.transport, mapper, asyncFeatures, this.requestTimeout,
this.uriTemplateManagerFactory, jsonSchemaValidator);
var asyncServer = new McpStatelessAsyncServer(transport,
jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, asyncFeatures, requestTimeout,
uriTemplateManagerFactory,
this.jsonSchemaValidator != null ? this.jsonSchemaValidator : JsonSchemaValidator.getDefault());
return new McpStatelessSyncServer(asyncServer, this.immediateExecution);
}

View file

@ -453,12 +453,13 @@ public class McpServerFeatures {
*
* <pre>{@code
* McpServerFeatures.SyncToolSpecification.builder()
* .tool(new Tool(
* "calculator",
* "Performs mathematical calculations",
* new JsonSchemaObject()
* .tool(Tool.builder()
* .name("calculator")
* .title("Performs mathematical calculations")
* .inputSchema(new JsonSchemaObject()
* .required("expression")
* .property("expression", JsonSchemaType.STRING)))
* .property("expression", JsonSchemaType.STRING))
* .build()
* .toolHandler((exchange, req) -> {
* String expr = (String) req.arguments().get("expression");
* return new CallToolResult("Result: " + evaluate(expr));

View file

@ -4,16 +4,15 @@
package io.modelcontextprotocol.server;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.spec.JsonSchemaValidator;
import io.modelcontextprotocol.json.schema.JsonSchemaValidator;
import io.modelcontextprotocol.spec.McpError;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
import io.modelcontextprotocol.spec.McpSchema.JSONRPCResponse;
import io.modelcontextprotocol.spec.McpSchema.ResourceTemplate;
import io.modelcontextprotocol.spec.McpSchema.TextContent;
import io.modelcontextprotocol.spec.McpSchema.Tool;
import io.modelcontextprotocol.spec.McpStatelessServerTransport;
import io.modelcontextprotocol.util.Assert;
@ -48,7 +47,7 @@ public class McpStatelessAsyncServer {
private final McpStatelessServerTransport mcpTransportProvider;
private final ObjectMapper objectMapper;
private final McpJsonMapper jsonMapper;
private final McpSchema.ServerCapabilities serverCapabilities;
@ -72,11 +71,11 @@ public class McpStatelessAsyncServer {
private final JsonSchemaValidator jsonSchemaValidator;
McpStatelessAsyncServer(McpStatelessServerTransport mcpTransport, ObjectMapper objectMapper,
McpStatelessAsyncServer(McpStatelessServerTransport mcpTransport, McpJsonMapper jsonMapper,
McpStatelessServerFeatures.Async features, Duration requestTimeout,
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator) {
this.mcpTransportProvider = mcpTransport;
this.objectMapper = objectMapper;
this.jsonMapper = jsonMapper;
this.serverInfo = features.serverInfo();
this.serverCapabilities = features.serverCapabilities();
this.instructions = features.instructions();
@ -132,7 +131,7 @@ public class McpStatelessAsyncServer {
// ---------------------------------------
private McpStatelessRequestHandler<McpSchema.InitializeResult> asyncInitializeRequestHandler() {
return (ctx, req) -> Mono.defer(() -> {
McpSchema.InitializeRequest initializeRequest = this.objectMapper.convertValue(req,
McpSchema.InitializeRequest initializeRequest = this.jsonMapper.convertValue(req,
McpSchema.InitializeRequest.class);
logger.info("Client initialize request - Protocol: {}, Capabilities: {}, Info: {}",
@ -376,8 +375,8 @@ public class McpStatelessAsyncServer {
private McpStatelessRequestHandler<CallToolResult> toolsCallRequestHandler() {
return (ctx, params) -> {
McpSchema.CallToolRequest callToolRequest = objectMapper.convertValue(params,
new TypeReference<McpSchema.CallToolRequest>() {
McpSchema.CallToolRequest callToolRequest = jsonMapper.convertValue(params,
new TypeRef<McpSchema.CallToolRequest>() {
});
Optional<McpStatelessServerFeatures.AsyncToolSpecification> toolSpecification = this.tools.stream()
@ -479,8 +478,8 @@ public class McpStatelessAsyncServer {
private McpStatelessRequestHandler<McpSchema.ReadResourceResult> resourcesReadRequestHandler() {
return (ctx, params) -> {
McpSchema.ReadResourceRequest resourceRequest = objectMapper.convertValue(params,
new TypeReference<McpSchema.ReadResourceRequest>() {
McpSchema.ReadResourceRequest resourceRequest = jsonMapper.convertValue(params,
new TypeRef<McpSchema.ReadResourceRequest>() {
});
var resourceUri = resourceRequest.uri();
@ -569,8 +568,8 @@ public class McpStatelessAsyncServer {
private McpStatelessRequestHandler<McpSchema.GetPromptResult> promptsGetRequestHandler() {
return (ctx, params) -> {
McpSchema.GetPromptRequest promptRequest = objectMapper.convertValue(params,
new TypeReference<McpSchema.GetPromptRequest>() {
McpSchema.GetPromptRequest promptRequest = jsonMapper.convertValue(params,
new TypeRef<McpSchema.GetPromptRequest>() {
});
// Implement prompt retrieval logic here

View file

@ -14,8 +14,8 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.server.McpTransportContextExtractor;
import io.modelcontextprotocol.spec.McpError;
@ -89,8 +89,8 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
public static final String DEFAULT_BASE_URL = "";
/** JSON object mapper for serialization/deserialization */
private final ObjectMapper objectMapper;
/** JSON mapper for serialization/deserialization */
private final McpJsonMapper jsonMapper;
/** Base URL for the server transport */
private final String baseUrl;
@ -121,60 +121,7 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
/**
* Creates a new HttpServletSseServerTransportProvider instance with a custom SSE
* endpoint.
* @param objectMapper The JSON object mapper to use for message
* serialization/deserialization
* @param messageEndpoint The endpoint path where clients will send their messages
* @param sseEndpoint The endpoint path where clients will establish SSE connections
* @deprecated Use the builder {@link #builder()} instead for better configuration
* options.
*/
@Deprecated
public HttpServletSseServerTransportProvider(ObjectMapper objectMapper, String messageEndpoint,
String sseEndpoint) {
this(objectMapper, DEFAULT_BASE_URL, messageEndpoint, sseEndpoint);
}
/**
* Creates a new HttpServletSseServerTransportProvider instance with a custom SSE
* endpoint.
* @param objectMapper The JSON object mapper to use for message
* serialization/deserialization
* @param baseUrl The base URL for the server transport
* @param messageEndpoint The endpoint path where clients will send their messages
* @param sseEndpoint The endpoint path where clients will establish SSE connections
* @deprecated Use the builder {@link #builder()} instead for better configuration
* options.
*/
@Deprecated
public HttpServletSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint,
String sseEndpoint) {
this(objectMapper, baseUrl, messageEndpoint, sseEndpoint, null, (serverRequest) -> McpTransportContext.EMPTY);
}
/**
* Creates a new HttpServletSseServerTransportProvider instance with a custom SSE
* endpoint.
* @param objectMapper The JSON object mapper to use for message
* serialization/deserialization
* @param baseUrl The base URL for the server transport
* @param messageEndpoint The endpoint path where clients will send their messages
* @param sseEndpoint The endpoint path where clients will establish SSE connections
* @param keepAliveInterval The interval for keep-alive pings, or null to disable
* keep-alive functionality
* @deprecated Use the builder {@link #builder()} instead for better configuration
* options.
*/
@Deprecated
public HttpServletSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint,
String sseEndpoint, Duration keepAliveInterval) {
this(objectMapper, baseUrl, messageEndpoint, sseEndpoint, keepAliveInterval,
(serverRequest) -> McpTransportContext.EMPTY);
}
/**
* Creates a new HttpServletSseServerTransportProvider instance with a custom SSE
* endpoint.
* @param objectMapper The JSON object mapper to use for message
* @param jsonMapper The JSON object mapper to use for message
* serialization/deserialization
* @param baseUrl The base URL for the server transport
* @param messageEndpoint The endpoint path where clients will send their messages
@ -185,16 +132,16 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
* @deprecated Use the builder {@link #builder()} instead for better configuration
* options.
*/
private HttpServletSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint,
private HttpServletSseServerTransportProvider(McpJsonMapper jsonMapper, String baseUrl, String messageEndpoint,
String sseEndpoint, Duration keepAliveInterval,
McpTransportContextExtractor<HttpServletRequest> contextExtractor) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
Assert.notNull(jsonMapper, "JsonMapper must not be null");
Assert.notNull(messageEndpoint, "messageEndpoint must not be null");
Assert.notNull(sseEndpoint, "sseEndpoint must not be null");
Assert.notNull(contextExtractor, "Context extractor must not be null");
this.objectMapper = objectMapper;
this.jsonMapper = jsonMapper;
this.baseUrl = baseUrl;
this.messageEndpoint = messageEndpoint;
this.sseEndpoint = sseEndpoint;
@ -217,17 +164,6 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
return List.of(ProtocolVersions.MCP_2024_11_05);
}
/**
* Creates a new HttpServletSseServerTransportProvider instance with the default SSE
* endpoint.
* @param objectMapper The JSON object mapper to use for message
* serialization/deserialization
* @param messageEndpoint The endpoint path where clients will send their messages
*/
public HttpServletSseServerTransportProvider(ObjectMapper objectMapper, String messageEndpoint) {
this(objectMapper, messageEndpoint, DEFAULT_SSE_ENDPOINT);
}
/**
* Sets the session factory for creating new sessions.
* @param sessionFactory The session factory to use
@ -342,7 +278,7 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
response.setContentType(APPLICATION_JSON);
response.setCharacterEncoding(UTF_8);
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
String jsonError = objectMapper.writeValueAsString(new McpError("Session ID missing in message endpoint"));
String jsonError = jsonMapper.writeValueAsString(new McpError("Session ID missing in message endpoint"));
PrintWriter writer = response.getWriter();
writer.write(jsonError);
writer.flush();
@ -355,7 +291,7 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
response.setContentType(APPLICATION_JSON);
response.setCharacterEncoding(UTF_8);
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
String jsonError = objectMapper.writeValueAsString(new McpError("Session not found: " + sessionId));
String jsonError = jsonMapper.writeValueAsString(new McpError("Session not found: " + sessionId));
PrintWriter writer = response.getWriter();
writer.write(jsonError);
writer.flush();
@ -371,7 +307,7 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
}
final McpTransportContext transportContext = this.contextExtractor.extract(request);
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper, body.toString());
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body.toString());
// Process the message through the session's handle method
// Block for Servlet compatibility
@ -386,7 +322,7 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
response.setContentType(APPLICATION_JSON);
response.setCharacterEncoding(UTF_8);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
String jsonError = objectMapper.writeValueAsString(mcpError);
String jsonError = jsonMapper.writeValueAsString(mcpError);
PrintWriter writer = response.getWriter();
writer.write(jsonError);
writer.flush();
@ -482,7 +418,7 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
return Mono.fromRunnable(() -> {
try {
String jsonText = objectMapper.writeValueAsString(message);
String jsonText = jsonMapper.writeValueAsString(message);
sendEvent(writer, MESSAGE_EVENT_TYPE, jsonText);
logger.debug("Message sent to session {}", sessionId);
}
@ -495,15 +431,15 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
}
/**
* Converts data from one type to another using the configured ObjectMapper.
* Converts data from one type to another using the configured JsonMapper.
* @param data The source data object to convert
* @param typeRef The target type reference
* @return The converted object of type T
* @param <T> The target type
*/
@Override
public <T> T unmarshalFrom(Object data, TypeReference<T> typeRef) {
return objectMapper.convertValue(data, typeRef);
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return jsonMapper.convertValue(data, typeRef);
}
/**
@ -559,7 +495,7 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
*/
public static class Builder {
private ObjectMapper objectMapper = new ObjectMapper();
private McpJsonMapper jsonMapper;
private String baseUrl = DEFAULT_BASE_URL;
@ -573,13 +509,15 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
private Duration keepAliveInterval;
/**
* Sets the JSON object mapper to use for message serialization/deserialization.
* @param objectMapper The object mapper to use
* Sets the JsonMapper implementation to use for serialization/deserialization. If
* not specified, a JacksonJsonMapper will be created from the configured
* ObjectMapper.
* @param jsonMapper The JsonMapper to use
* @return This builder instance for method chaining
*/
public Builder objectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
public Builder jsonMapper(McpJsonMapper jsonMapper) {
Assert.notNull(jsonMapper, "JsonMapper must not be null");
this.jsonMapper = jsonMapper;
return this;
}
@ -648,16 +586,14 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
* Builds a new instance of HttpServletSseServerTransportProvider with the
* configured settings.
* @return A new HttpServletSseServerTransportProvider instance
* @throws IllegalStateException if objectMapper or messageEndpoint is not set
* @throws IllegalStateException if jsonMapper or messageEndpoint is not set
*/
public HttpServletSseServerTransportProvider build() {
if (objectMapper == null) {
throw new IllegalStateException("ObjectMapper must be set");
}
if (messageEndpoint == null) {
throw new IllegalStateException("MessageEndpoint must be set");
}
return new HttpServletSseServerTransportProvider(objectMapper, baseUrl, messageEndpoint, sseEndpoint,
return new HttpServletSseServerTransportProvider(
jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, baseUrl, messageEndpoint, sseEndpoint,
keepAliveInterval, contextExtractor);
}

View file

@ -11,7 +11,7 @@ import java.io.PrintWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.server.McpStatelessServerHandler;
@ -48,7 +48,7 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
public static final String FAILED_TO_SEND_ERROR_RESPONSE = "Failed to send error response: {}";
private final ObjectMapper objectMapper;
private final McpJsonMapper jsonMapper;
private final String mcpEndpoint;
@ -58,13 +58,13 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
private volatile boolean isClosing = false;
private HttpServletStatelessServerTransport(ObjectMapper objectMapper, String mcpEndpoint,
private HttpServletStatelessServerTransport(McpJsonMapper jsonMapper, String mcpEndpoint,
McpTransportContextExtractor<HttpServletRequest> contextExtractor) {
Assert.notNull(objectMapper, "objectMapper must not be null");
Assert.notNull(jsonMapper, "jsonMapper must not be null");
Assert.notNull(mcpEndpoint, "mcpEndpoint must not be null");
Assert.notNull(contextExtractor, "contextExtractor must not be null");
this.objectMapper = objectMapper;
this.jsonMapper = jsonMapper;
this.mcpEndpoint = mcpEndpoint;
this.contextExtractor = contextExtractor;
}
@ -139,7 +139,7 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
body.append(line);
}
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper, body.toString());
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body.toString());
if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
try {
@ -152,7 +152,7 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
response.setCharacterEncoding(UTF_8);
response.setStatus(HttpServletResponse.SC_OK);
String jsonResponseText = objectMapper.writeValueAsString(jsonrpcResponse);
String jsonResponseText = jsonMapper.writeValueAsString(jsonrpcResponse);
PrintWriter writer = response.getWriter();
writer.write(jsonResponseText);
writer.flush();
@ -203,7 +203,7 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
response.setContentType(APPLICATION_JSON);
response.setCharacterEncoding(UTF_8);
response.setStatus(httpCode);
String jsonError = objectMapper.writeValueAsString(mcpError);
String jsonError = jsonMapper.writeValueAsString(mcpError);
PrintWriter writer = response.getWriter();
writer.write(jsonError);
writer.flush();
@ -236,7 +236,7 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
*/
public static class Builder {
private ObjectMapper objectMapper;
private McpJsonMapper jsonMapper;
private String mcpEndpoint = "/mcp";
@ -248,15 +248,15 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
}
/**
* Sets the ObjectMapper to use for JSON serialization/deserialization of MCP
* Sets the JsonMapper to use for JSON serialization/deserialization of MCP
* messages.
* @param objectMapper The ObjectMapper instance. Must not be null.
* @param jsonMapper The JsonMapper instance. Must not be null.
* @return this builder instance
* @throws IllegalArgumentException if objectMapper is null
* @throws IllegalArgumentException if jsonMapper is null
*/
public Builder objectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
public Builder jsonMapper(McpJsonMapper jsonMapper) {
Assert.notNull(jsonMapper, "JsonMapper must not be null");
this.jsonMapper = jsonMapper;
return this;
}
@ -295,10 +295,9 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
* @throws IllegalStateException if required parameters are not set
*/
public HttpServletStatelessServerTransport build() {
Assert.notNull(objectMapper, "ObjectMapper must be set");
Assert.notNull(mcpEndpoint, "Message endpoint must be set");
return new HttpServletStatelessServerTransport(objectMapper, mcpEndpoint, contextExtractor);
return new HttpServletStatelessServerTransport(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
mcpEndpoint, contextExtractor);
}
}

View file

@ -16,8 +16,7 @@ import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.server.McpTransportContextExtractor;
@ -29,6 +28,7 @@ import io.modelcontextprotocol.spec.McpStreamableServerTransport;
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
import io.modelcontextprotocol.spec.ProtocolVersions;
import io.modelcontextprotocol.util.Assert;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.util.KeepAliveScheduler;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.ServletException;
@ -97,7 +97,7 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
*/
private final boolean disallowDelete;
private final ObjectMapper objectMapper;
private final McpJsonMapper jsonMapper;
private McpStreamableServerSession.Factory sessionFactory;
@ -121,22 +121,22 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
/**
* Constructs a new HttpServletStreamableServerTransportProvider instance.
* @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
* of messages.
* @param jsonMapper The JsonMapper to use for JSON serialization/deserialization of
* messages.
* @param mcpEndpoint The endpoint URI where clients should send their JSON-RPC
* messages via HTTP. This endpoint will handle GET, POST, and DELETE requests.
* @param disallowDelete Whether to disallow DELETE requests on the endpoint.
* @param contextExtractor The extractor for transport context from the request.
* @throws IllegalArgumentException if any parameter is null
*/
private HttpServletStreamableServerTransportProvider(ObjectMapper objectMapper, String mcpEndpoint,
private HttpServletStreamableServerTransportProvider(McpJsonMapper jsonMapper, String mcpEndpoint,
boolean disallowDelete, McpTransportContextExtractor<HttpServletRequest> contextExtractor,
Duration keepAliveInterval) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
Assert.notNull(jsonMapper, "JsonMapper must not be null");
Assert.notNull(mcpEndpoint, "MCP endpoint must not be null");
Assert.notNull(contextExtractor, "Context extractor must not be null");
this.objectMapper = objectMapper;
this.jsonMapper = jsonMapper;
this.mcpEndpoint = mcpEndpoint;
this.disallowDelete = disallowDelete;
this.contextExtractor = contextExtractor;
@ -392,7 +392,7 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
body.append(line);
}
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper, body.toString());
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body.toString());
// Handle initialization request
if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest
@ -403,8 +403,8 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
return;
}
McpSchema.InitializeRequest initializeRequest = objectMapper.convertValue(jsonrpcRequest.params(),
new TypeReference<McpSchema.InitializeRequest>() {
McpSchema.InitializeRequest initializeRequest = jsonMapper.convertValue(jsonrpcRequest.params(),
new TypeRef<McpSchema.InitializeRequest>() {
});
McpStreamableServerSession.McpStreamableServerSessionInit init = this.sessionFactory
.startSession(initializeRequest);
@ -418,7 +418,7 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
response.setHeader(HttpHeaders.MCP_SESSION_ID, init.session().getId());
response.setStatus(HttpServletResponse.SC_OK);
String jsonResponse = objectMapper.writeValueAsString(new McpSchema.JSONRPCResponse(
String jsonResponse = jsonMapper.writeValueAsString(new McpSchema.JSONRPCResponse(
McpSchema.JSONRPC_VERSION, jsonrpcRequest.id(), initResult, null));
PrintWriter writer = response.getWriter();
@ -578,7 +578,7 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
response.setContentType(APPLICATION_JSON);
response.setCharacterEncoding(UTF_8);
response.setStatus(httpCode);
String jsonError = objectMapper.writeValueAsString(mcpError);
String jsonError = jsonMapper.writeValueAsString(mcpError);
PrintWriter writer = response.getWriter();
writer.write(jsonError);
writer.flush();
@ -685,7 +685,7 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
return;
}
String jsonText = objectMapper.writeValueAsString(message);
String jsonText = jsonMapper.writeValueAsString(message);
HttpServletStreamableServerTransportProvider.this.sendEvent(writer, MESSAGE_EVENT_TYPE, jsonText,
messageId != null ? messageId : this.sessionId);
logger.debug("Message sent to session {} with ID {}", this.sessionId, messageId);
@ -702,15 +702,15 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
}
/**
* Converts data from one type to another using the configured ObjectMapper.
* Converts data from one type to another using the configured JsonMapper.
* @param data The source data object to convert
* @param typeRef The target type reference
* @return The converted object of type T
* @param <T> The target type
*/
@Override
public <T> T unmarshalFrom(Object data, TypeReference<T> typeRef) {
return objectMapper.convertValue(data, typeRef);
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return jsonMapper.convertValue(data, typeRef);
}
/**
@ -762,7 +762,7 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
*/
public static class Builder {
private ObjectMapper objectMapper;
private McpJsonMapper jsonMapper;
private String mcpEndpoint = "/mcp";
@ -774,15 +774,15 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
private Duration keepAliveInterval;
/**
* Sets the ObjectMapper to use for JSON serialization/deserialization of MCP
* Sets the JsonMapper to use for JSON serialization/deserialization of MCP
* messages.
* @param objectMapper The ObjectMapper instance. Must not be null.
* @param jsonMapper The JsonMapper instance. Must not be null.
* @return this builder instance
* @throws IllegalArgumentException if objectMapper is null
* @throws IllegalArgumentException if JsonMapper is null
*/
public Builder objectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
public Builder jsonMapper(McpJsonMapper jsonMapper) {
Assert.notNull(jsonMapper, "JsonMapper must not be null");
this.jsonMapper = jsonMapper;
return this;
}
@ -839,11 +839,10 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
* @throws IllegalStateException if required parameters are not set
*/
public HttpServletStreamableServerTransportProvider build() {
Assert.notNull(this.objectMapper, "ObjectMapper must be set");
Assert.notNull(this.mcpEndpoint, "MCP endpoint must be set");
return new HttpServletStreamableServerTransportProvider(this.objectMapper, this.mcpEndpoint,
this.disallowDelete, this.contextExtractor, this.keepAliveInterval);
return new HttpServletStreamableServerTransportProvider(
jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, mcpEndpoint, disallowDelete,
contextExtractor, keepAliveInterval);
}
}

View file

@ -15,8 +15,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.spec.McpError;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.JSONRPCMessage;
@ -25,6 +24,7 @@ import io.modelcontextprotocol.spec.McpServerTransport;
import io.modelcontextprotocol.spec.McpServerTransportProvider;
import io.modelcontextprotocol.spec.ProtocolVersions;
import io.modelcontextprotocol.util.Assert;
import io.modelcontextprotocol.json.McpJsonMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
@ -44,7 +44,7 @@ public class StdioServerTransportProvider implements McpServerTransportProvider
private static final Logger logger = LoggerFactory.getLogger(StdioServerTransportProvider.class);
private final ObjectMapper objectMapper;
private final McpJsonMapper jsonMapper;
private final InputStream inputStream;
@ -56,36 +56,28 @@ public class StdioServerTransportProvider implements McpServerTransportProvider
private final Sinks.One<Void> inboundReady = Sinks.one();
/**
* Creates a new StdioServerTransportProvider with a default ObjectMapper and System
* streams.
*/
public StdioServerTransportProvider() {
this(new ObjectMapper());
}
/**
* Creates a new StdioServerTransportProvider with the specified ObjectMapper and
* System streams.
* @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
* @param jsonMapper The JsonMapper to use for JSON serialization/deserialization
*/
public StdioServerTransportProvider(ObjectMapper objectMapper) {
this(objectMapper, System.in, System.out);
public StdioServerTransportProvider(McpJsonMapper jsonMapper) {
this(jsonMapper, System.in, System.out);
}
/**
* Creates a new StdioServerTransportProvider with the specified ObjectMapper and
* streams.
* @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
* @param jsonMapper The JsonMapper to use for JSON serialization/deserialization
* @param inputStream The input stream to read from
* @param outputStream The output stream to write to
*/
public StdioServerTransportProvider(ObjectMapper objectMapper, InputStream inputStream, OutputStream outputStream) {
Assert.notNull(objectMapper, "The ObjectMapper can not be null");
public StdioServerTransportProvider(McpJsonMapper jsonMapper, InputStream inputStream, OutputStream outputStream) {
Assert.notNull(jsonMapper, "The JsonMapper can not be null");
Assert.notNull(inputStream, "The InputStream can not be null");
Assert.notNull(outputStream, "The OutputStream can not be null");
this.objectMapper = objectMapper;
this.jsonMapper = jsonMapper;
this.inputStream = inputStream;
this.outputStream = outputStream;
}
@ -165,8 +157,8 @@ public class StdioServerTransportProvider implements McpServerTransportProvider
}
@Override
public <T> T unmarshalFrom(Object data, TypeReference<T> typeRef) {
return objectMapper.convertValue(data, typeRef);
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return jsonMapper.convertValue(data, typeRef);
}
@Override
@ -219,7 +211,7 @@ public class StdioServerTransportProvider implements McpServerTransportProvider
logger.debug("Received JSON message: {}", line);
try {
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper,
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper,
line);
if (!this.inboundSink.tryEmitNext(message).isSuccess()) {
// logIfNotClosing("Failed to enqueue message");
@ -263,7 +255,7 @@ public class StdioServerTransportProvider implements McpServerTransportProvider
.handle((message, sink) -> {
if (message != null && !isClosing.get()) {
try {
String jsonMessage = objectMapper.writeValueAsString(message);
String jsonMessage = jsonMapper.writeValueAsString(message);
// Escape any embedded newlines in the JSON message as per spec
jsonMessage = jsonMessage.replace("\r\n", "\\n").replace("\n", "\\n").replace("\r", "\\n");

View file

@ -1,170 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*/
package io.modelcontextprotocol.spec;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SpecVersion;
import com.networknt.schema.ValidationMessage;
import io.modelcontextprotocol.util.Assert;
/**
* Default implementation of the {@link JsonSchemaValidator} interface. This class
* provides methods to validate structured content against a JSON schema. It uses the
* NetworkNT JSON Schema Validator library for validation.
*
* @author Christian Tzolov
*/
public class DefaultJsonSchemaValidator implements JsonSchemaValidator {
private static final Logger logger = LoggerFactory.getLogger(DefaultJsonSchemaValidator.class);
private final ObjectMapper objectMapper;
private final JsonSchemaFactory schemaFactory;
// TODO: Implement a strategy to purge the cache (TTL, size limit, etc.)
private final ConcurrentHashMap<String, JsonSchema> schemaCache;
public DefaultJsonSchemaValidator() {
this(new ObjectMapper());
}
public DefaultJsonSchemaValidator(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
this.schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
this.schemaCache = new ConcurrentHashMap<>();
}
@Override
public ValidationResponse validate(Map<String, Object> schema, Object structuredContent) {
Assert.notNull(schema, "Schema must not be null");
Assert.notNull(structuredContent, "Structured content must not be null");
try {
JsonNode jsonStructuredOutput = this.objectMapper.valueToTree(structuredContent);
Set<ValidationMessage> validationResult = this.getOrCreateJsonSchema(schema).validate(jsonStructuredOutput);
// Check if validation passed
if (!validationResult.isEmpty()) {
return ValidationResponse
.asInvalid("Validation failed: structuredContent does not match tool outputSchema. "
+ "Validation errors: " + validationResult);
}
return ValidationResponse.asValid(jsonStructuredOutput.toString());
}
catch (JsonProcessingException e) {
logger.error("Failed to validate CallToolResult: Error parsing schema: {}", e);
return ValidationResponse.asInvalid("Error parsing tool JSON Schema: " + e.getMessage());
}
catch (Exception e) {
logger.error("Failed to validate CallToolResult: Unexpected error: {}", e);
return ValidationResponse.asInvalid("Unexpected validation error: " + e.getMessage());
}
}
/**
* Gets a cached JsonSchema or creates and caches a new one.
* @param schema the schema map to convert
* @return the compiled JsonSchema
* @throws JsonProcessingException if schema processing fails
*/
private JsonSchema getOrCreateJsonSchema(Map<String, Object> schema) throws JsonProcessingException {
// Generate cache key based on schema content
String cacheKey = this.generateCacheKey(schema);
// Try to get from cache first
JsonSchema cachedSchema = this.schemaCache.get(cacheKey);
if (cachedSchema != null) {
return cachedSchema;
}
// Create new schema if not in cache
JsonSchema newSchema = this.createJsonSchema(schema);
// Cache the schema
JsonSchema existingSchema = this.schemaCache.putIfAbsent(cacheKey, newSchema);
return existingSchema != null ? existingSchema : newSchema;
}
/**
* Creates a new JsonSchema from the given schema map.
* @param schema the schema map
* @return the compiled JsonSchema
* @throws JsonProcessingException if schema processing fails
*/
private JsonSchema createJsonSchema(Map<String, Object> schema) throws JsonProcessingException {
// Convert schema map directly to JsonNode (more efficient than string
// serialization)
JsonNode schemaNode = this.objectMapper.valueToTree(schema);
// Handle case where ObjectMapper might return null (e.g., in mocked scenarios)
if (schemaNode == null) {
throw new JsonProcessingException("Failed to convert schema to JsonNode") {
};
}
// Handle additionalProperties setting
if (schemaNode.isObject()) {
ObjectNode objectSchemaNode = (ObjectNode) schemaNode;
if (!objectSchemaNode.has("additionalProperties")) {
// Clone the node before modification to avoid mutating the original
objectSchemaNode = objectSchemaNode.deepCopy();
objectSchemaNode.put("additionalProperties", false);
schemaNode = objectSchemaNode;
}
}
return this.schemaFactory.getSchema(schemaNode);
}
/**
* Generates a cache key for the given schema map.
* @param schema the schema map
* @return a cache key string
*/
protected String generateCacheKey(Map<String, Object> schema) {
if (schema.containsKey("$id")) {
// Use the (optional) "$id" field as the cache key if present
return "" + schema.get("$id");
}
// Fall back to schema's hash code as a simple cache key
// For more sophisticated caching, could use content-based hashing
return String.valueOf(schema.hashCode());
}
/**
* Clears the schema cache. Useful for testing or memory management.
*/
public void clearCache() {
this.schemaCache.clear();
}
/**
* Returns the current size of the schema cache.
* @return the number of cached schemas
*/
public int getCacheSize() {
return this.schemaCache.size();
}
}

View file

@ -4,7 +4,7 @@
package io.modelcontextprotocol.spec;
import com.fasterxml.jackson.core.type.TypeReference;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.util.Assert;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
@ -254,7 +254,7 @@ public class McpClientSession implements McpSession {
* @return A Mono containing the response
*/
@Override
public <T> Mono<T> sendRequest(String method, Object requestParams, TypeReference<T> typeRef) {
public <T> Mono<T> sendRequest(String method, Object requestParams, TypeRef<T> typeRef) {
String requestId = this.generateRequestId();
return Mono.deferContextual(ctx -> Mono.<McpSchema.JSONRPCResponse>create(pendingResponseSink -> {

View file

@ -18,8 +18,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.util.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -109,8 +110,6 @@ public final class McpSchema {
// Elicitation Methods
public static final String METHOD_ELICITATION_CREATE = "elicitation/create";
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
// ---------------------------
// JSON-RPC Error Codes
// ---------------------------
@ -176,12 +175,12 @@ public final class McpSchema {
}
private static final TypeReference<HashMap<String, Object>> MAP_TYPE_REF = new TypeReference<>() {
private static final TypeRef<HashMap<String, Object>> MAP_TYPE_REF = new TypeRef<>() {
};
/**
* Deserializes a JSON string into a JSONRPCMessage object.
* @param objectMapper The ObjectMapper instance to use for deserialization
* @param jsonMapper The JsonMapper instance to use for deserialization
* @param jsonText The JSON string to deserialize
* @return A JSONRPCMessage instance using either the {@link JSONRPCRequest},
* {@link JSONRPCNotification}, or {@link JSONRPCResponse} classes.
@ -189,22 +188,22 @@ public final class McpSchema {
* @throws IllegalArgumentException If the JSON structure doesn't match any known
* message type
*/
public static JSONRPCMessage deserializeJsonRpcMessage(ObjectMapper objectMapper, String jsonText)
public static JSONRPCMessage deserializeJsonRpcMessage(McpJsonMapper jsonMapper, String jsonText)
throws IOException {
logger.debug("Received JSON message: {}", jsonText);
var map = objectMapper.readValue(jsonText, MAP_TYPE_REF);
var map = jsonMapper.readValue(jsonText, MAP_TYPE_REF);
// Determine message type based on specific JSON structure
if (map.containsKey("method") && map.containsKey("id")) {
return objectMapper.convertValue(map, JSONRPCRequest.class);
return jsonMapper.convertValue(map, JSONRPCRequest.class);
}
else if (map.containsKey("method") && !map.containsKey("id")) {
return objectMapper.convertValue(map, JSONRPCNotification.class);
return jsonMapper.convertValue(map, JSONRPCNotification.class);
}
else if (map.containsKey("result") || map.containsKey("error")) {
return objectMapper.convertValue(map, JSONRPCResponse.class);
return jsonMapper.convertValue(map, JSONRPCResponse.class);
}
throw new IllegalArgumentException("Cannot deserialize JSONRPCMessage: " + jsonText);
@ -1270,53 +1269,6 @@ public final class McpSchema {
@JsonProperty("annotations") ToolAnnotations annotations,
@JsonProperty("_meta") Map<String, Object> meta) { // @formatter:on
/**
* @deprecated Only exists for backwards-compatibility purposes. Use
* {@link Tool#builder()} instead.
*/
@Deprecated
public Tool(String name, String description, JsonSchema inputSchema, ToolAnnotations annotations) {
this(name, null, description, inputSchema, null, annotations, null);
}
/**
* @deprecated Only exists for backwards-compatibility purposes. Use
* {@link Tool#builder()} instead.
*/
@Deprecated
public Tool(String name, String description, String inputSchema) {
this(name, null, description, parseSchema(inputSchema), null, null, null);
}
/**
* @deprecated Only exists for backwards-compatibility purposes. Use
* {@link Tool#builder()} instead.
*/
@Deprecated
public Tool(String name, String description, String schema, ToolAnnotations annotations) {
this(name, null, description, parseSchema(schema), null, annotations, null);
}
/**
* @deprecated Only exists for backwards-compatibility purposes. Use
* {@link Tool#builder()} instead.
*/
@Deprecated
public Tool(String name, String description, String inputSchema, String outputSchema,
ToolAnnotations annotations) {
this(name, null, description, parseSchema(inputSchema), schemaToMap(outputSchema), annotations, null);
}
/**
* @deprecated Only exists for backwards-compatibility purposes. Use
* {@link Tool#builder()} instead.
*/
@Deprecated
public Tool(String name, String title, String description, String inputSchema, String outputSchema,
ToolAnnotations annotations) {
this(name, title, description, parseSchema(inputSchema), schemaToMap(outputSchema), annotations, null);
}
public static Builder builder() {
return new Builder();
}
@ -1357,8 +1309,8 @@ public final class McpSchema {
return this;
}
public Builder inputSchema(String inputSchema) {
this.inputSchema = parseSchema(inputSchema);
public Builder inputSchema(McpJsonMapper jsonMapper, String inputSchema) {
this.inputSchema = parseSchema(jsonMapper, inputSchema);
return this;
}
@ -1367,8 +1319,8 @@ public final class McpSchema {
return this;
}
public Builder outputSchema(String outputSchema) {
this.outputSchema = schemaToMap(outputSchema);
public Builder outputSchema(McpJsonMapper jsonMapper, String outputSchema) {
this.outputSchema = schemaToMap(jsonMapper, outputSchema);
return this;
}
@ -1390,18 +1342,18 @@ public final class McpSchema {
}
}
private static Map<String, Object> schemaToMap(String schema) {
private static Map<String, Object> schemaToMap(McpJsonMapper jsonMapper, String schema) {
try {
return OBJECT_MAPPER.readValue(schema, MAP_TYPE_REF);
return jsonMapper.readValue(schema, MAP_TYPE_REF);
}
catch (IOException e) {
throw new IllegalArgumentException("Invalid schema: " + schema, e);
}
}
private static JsonSchema parseSchema(String schema) {
private static JsonSchema parseSchema(McpJsonMapper jsonMapper, String schema) {
try {
return OBJECT_MAPPER.readValue(schema, JsonSchema.class);
return jsonMapper.readValue(schema, JsonSchema.class);
}
catch (IOException e) {
throw new IllegalArgumentException("Invalid schema: " + schema, e);
@ -1425,17 +1377,17 @@ public final class McpSchema {
@JsonProperty("arguments") Map<String, Object> arguments,
@JsonProperty("_meta") Map<String, Object> meta) implements Request { // @formatter:on
public CallToolRequest(String name, String jsonArguments) {
this(name, parseJsonArguments(jsonArguments), null);
public CallToolRequest(McpJsonMapper jsonMapper, String name, String jsonArguments) {
this(name, parseJsonArguments(jsonMapper, jsonArguments), null);
}
public CallToolRequest(String name, Map<String, Object> arguments) {
this(name, arguments, null);
}
private static Map<String, Object> parseJsonArguments(String jsonArguments) {
private static Map<String, Object> parseJsonArguments(McpJsonMapper jsonMapper, String jsonArguments) {
try {
return OBJECT_MAPPER.readValue(jsonArguments, MAP_TYPE_REF);
return jsonMapper.readValue(jsonArguments, MAP_TYPE_REF);
}
catch (IOException e) {
throw new IllegalArgumentException("Invalid arguments: " + jsonArguments, e);
@ -1464,8 +1416,8 @@ public final class McpSchema {
return this;
}
public Builder arguments(String jsonArguments) {
this.arguments = parseJsonArguments(jsonArguments);
public Builder arguments(McpJsonMapper jsonMapper, String jsonArguments) {
this.arguments = parseJsonArguments(jsonMapper, jsonArguments);
return this;
}
@ -1576,10 +1528,10 @@ public final class McpSchema {
return this;
}
public Builder structuredContent(String structuredContent) {
public Builder structuredContent(McpJsonMapper jsonMapper, String structuredContent) {
Assert.hasText(structuredContent, "structuredContent must not be empty");
try {
this.structuredContent = OBJECT_MAPPER.readValue(structuredContent, MAP_TYPE_REF);
this.structuredContent = jsonMapper.readValue(structuredContent, MAP_TYPE_REF);
}
catch (IOException e) {
throw new IllegalArgumentException("Invalid structured content: " + structuredContent, e);

View file

@ -11,12 +11,12 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.core.type.TypeReference;
import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.server.McpAsyncServerExchange;
import io.modelcontextprotocol.server.McpInitRequestHandler;
import io.modelcontextprotocol.server.McpNotificationHandler;
import io.modelcontextprotocol.server.McpRequestHandler;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.util.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -153,7 +153,7 @@ public class McpServerSession implements McpLoggableSession {
}
@Override
public <T> Mono<T> sendRequest(String method, Object requestParams, TypeReference<T> typeRef) {
public <T> Mono<T> sendRequest(String method, Object requestParams, TypeRef<T> typeRef) {
String requestId = this.generateRequestId();
return Mono.<McpSchema.JSONRPCResponse>create(sink -> {
@ -259,7 +259,7 @@ public class McpServerSession implements McpLoggableSession {
if (McpSchema.METHOD_INITIALIZE.equals(request.method())) {
// TODO handle situation where already initialized!
McpSchema.InitializeRequest initializeRequest = transport.unmarshalFrom(request.params(),
new TypeReference<McpSchema.InitializeRequest>() {
new TypeRef<McpSchema.InitializeRequest>() {
});
this.state.lazySet(STATE_INITIALIZING);

View file

@ -4,7 +4,7 @@
package io.modelcontextprotocol.spec;
import com.fasterxml.jackson.core.type.TypeReference;
import io.modelcontextprotocol.json.TypeRef;
import reactor.core.publisher.Mono;
/**
@ -37,7 +37,7 @@ public interface McpSession {
* @param typeRef the TypeReference describing the expected response type
* @return a Mono that will emit the response when received
*/
<T> Mono<T> sendRequest(String method, Object requestParams, TypeReference<T> typeRef);
<T> Mono<T> sendRequest(String method, Object requestParams, TypeRef<T> typeRef);
/**
* Sends a notification to the model client or server without parameters.

View file

@ -15,7 +15,7 @@ import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.type.TypeReference;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.server.McpAsyncServerExchange;
@ -110,7 +110,7 @@ public class McpStreamableServerSession implements McpLoggableSession {
}
@Override
public <T> Mono<T> sendRequest(String method, Object requestParams, TypeReference<T> typeRef) {
public <T> Mono<T> sendRequest(String method, Object requestParams, TypeRef<T> typeRef) {
return Mono.defer(() -> {
McpLoggableSession listeningStream = this.listeningStreamRef.get();
return listeningStream.sendRequest(method, requestParams, typeRef);
@ -347,7 +347,7 @@ public class McpStreamableServerSession implements McpLoggableSession {
}
@Override
public <T> Mono<T> sendRequest(String method, Object requestParams, TypeReference<T> typeRef) {
public <T> Mono<T> sendRequest(String method, Object requestParams, TypeRef<T> typeRef) {
String requestId = McpStreamableServerSession.this.generateRequestId();
McpStreamableServerSession.this.requestIdToStream.put(requestId, this);

View file

@ -6,8 +6,8 @@ package io.modelcontextprotocol.spec;
import java.util.List;
import com.fasterxml.jackson.core.type.TypeReference;
import io.modelcontextprotocol.spec.McpSchema.JSONRPCMessage;
import io.modelcontextprotocol.json.TypeRef;
import reactor.core.publisher.Mono;
/**
@ -77,7 +77,7 @@ public interface McpTransport {
* @param typeRef the type reference for the object to unmarshal
* @return the unmarshalled object
*/
<T> T unmarshalFrom(Object data, TypeReference<T> typeRef);
<T> T unmarshalFrom(Object data, TypeRef<T> typeRef);
default List<String> protocolVersions() {
return List.of(ProtocolVersions.MCP_2024_11_05);

Some files were not shown because too many files have changed in this diff Show more