diff --git a/mcp-bom/pom.xml b/mcp-bom/pom.xml
index ac68e55bc..b7ea52639 100644
--- a/mcp-bom/pom.xml
+++ b/mcp-bom/pom.xml
@@ -33,6 +33,20 @@
${project.version}
+
+
+ io.modelcontextprotocol.sdk
+ mcp-json
+ ${project.version}
+
+
+
+
+ io.modelcontextprotocol.sdk
+ mcp-json-jackson2
+ ${project.version}
+
+
io.modelcontextprotocol.sdk
diff --git a/mcp-json-jackson2/pom.xml b/mcp-json-jackson2/pom.xml
new file mode 100644
index 000000000..25159a9fe
--- /dev/null
+++ b/mcp-json-jackson2/pom.xml
@@ -0,0 +1,53 @@
+
+
+ 4.0.0
+
+ io.modelcontextprotocol.sdk
+ mcp-parent
+ 0.13.0-SNAPSHOT
+
+ mcp-json-jackson2
+ jar
+ Java MCP SDK JSON Jackson
+ Java MCP SDK JSON implementation based on Jackson
+ https://github.com/modelcontextprotocol/java-sdk
+
+ https://github.com/modelcontextprotocol/java-sdk
+ git://github.com/modelcontextprotocol/java-sdk.git
+ git@github.com/modelcontextprotocol/java-sdk.git
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ true
+
+
+
+
+
+
+
+
+ io.modelcontextprotocol.sdk
+ mcp-json
+ 0.13.0-SNAPSHOT
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
+
+ com.networknt
+ json-schema-validator
+ ${json-schema-validator.version}
+
+
+
diff --git a/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/jackson/JacksonMcpJsonMapper.java b/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/jackson/JacksonMcpJsonMapper.java
new file mode 100644
index 000000000..9bcceeae0
--- /dev/null
+++ b/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/jackson/JacksonMcpJsonMapper.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2025 - 2025 the original author or authors.
+ */
+package io.modelcontextprotocol.json.jackson;
+
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.json.McpJsonMapper;
+import io.modelcontextprotocol.json.TypeRef;
+
+import java.io.IOException;
+
+/**
+ * Jackson-based implementation of JsonMapper. Wraps a Jackson ObjectMapper but keeps the
+ * SDK decoupled from Jackson at the API level.
+ */
+public final class JacksonMcpJsonMapper implements McpJsonMapper {
+
+ private final ObjectMapper objectMapper;
+
+ /**
+ * Constructs a new JacksonMcpJsonMapper instance with the given ObjectMapper.
+ * @param objectMapper the ObjectMapper to be used for JSON serialization and
+ * deserialization. Must not be null.
+ * @throws IllegalArgumentException if the provided ObjectMapper is null.
+ */
+ public JacksonMcpJsonMapper(ObjectMapper objectMapper) {
+ if (objectMapper == null) {
+ throw new IllegalArgumentException("ObjectMapper must not be null");
+ }
+ this.objectMapper = objectMapper;
+ }
+
+ /**
+ * Returns the underlying Jackson {@link ObjectMapper} used for JSON serialization and
+ * deserialization.
+ * @return the ObjectMapper instance
+ */
+ public ObjectMapper getObjectMapper() {
+ return objectMapper;
+ }
+
+ @Override
+ public T readValue(String content, Class type) throws IOException {
+ return objectMapper.readValue(content, type);
+ }
+
+ @Override
+ public T readValue(byte[] content, Class type) throws IOException {
+ return objectMapper.readValue(content, type);
+ }
+
+ @Override
+ public T readValue(String content, TypeRef type) throws IOException {
+ JavaType javaType = objectMapper.getTypeFactory().constructType(type.getType());
+ return objectMapper.readValue(content, javaType);
+ }
+
+ @Override
+ public T readValue(byte[] content, TypeRef type) throws IOException {
+ JavaType javaType = objectMapper.getTypeFactory().constructType(type.getType());
+ return objectMapper.readValue(content, javaType);
+ }
+
+ @Override
+ public T convertValue(Object fromValue, Class type) {
+ return objectMapper.convertValue(fromValue, type);
+ }
+
+ @Override
+ public T convertValue(Object fromValue, TypeRef type) {
+ JavaType javaType = objectMapper.getTypeFactory().constructType(type.getType());
+ return objectMapper.convertValue(fromValue, javaType);
+ }
+
+ @Override
+ public String writeValueAsString(Object value) throws IOException {
+ return objectMapper.writeValueAsString(value);
+ }
+
+ @Override
+ public byte[] writeValueAsBytes(Object value) throws IOException {
+ return objectMapper.writeValueAsBytes(value);
+ }
+
+}
diff --git a/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/jackson/JacksonMcpJsonMapperSupplier.java b/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/jackson/JacksonMcpJsonMapperSupplier.java
new file mode 100644
index 000000000..018d73242
--- /dev/null
+++ b/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/jackson/JacksonMcpJsonMapperSupplier.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2025 - 2025 the original author or authors.
+ */
+package io.modelcontextprotocol.json.jackson;
+
+import io.modelcontextprotocol.json.McpJsonMapper;
+import io.modelcontextprotocol.json.McpJsonMapperSupplier;
+
+/**
+ * A supplier of {@link McpJsonMapper} instances that uses the Jackson library for JSON
+ * serialization and deserialization.
+ *
+ * This implementation provides a {@link McpJsonMapper} backed by a Jackson
+ * {@link com.fasterxml.jackson.databind.ObjectMapper}.
+ */
+public class JacksonMcpJsonMapperSupplier implements McpJsonMapperSupplier {
+
+ /**
+ * Returns a new instance of {@link McpJsonMapper} that uses the Jackson library for
+ * JSON serialization and deserialization.
+ *
+ * The returned {@link McpJsonMapper} is backed by a new instance of
+ * {@link com.fasterxml.jackson.databind.ObjectMapper}.
+ * @return a new {@link McpJsonMapper} instance
+ */
+ @Override
+ public McpJsonMapper get() {
+ return new JacksonMcpJsonMapper(new com.fasterxml.jackson.databind.ObjectMapper());
+ }
+
+}
diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/DefaultJsonSchemaValidator.java b/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/DefaultJsonSchemaValidator.java
similarity index 94%
rename from mcp/src/main/java/io/modelcontextprotocol/spec/DefaultJsonSchemaValidator.java
rename to mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/DefaultJsonSchemaValidator.java
index f4bdc02eb..8d2837949 100644
--- a/mcp/src/main/java/io/modelcontextprotocol/spec/DefaultJsonSchemaValidator.java
+++ b/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/DefaultJsonSchemaValidator.java
@@ -1,13 +1,13 @@
/*
* Copyright 2024-2024 the original author or authors.
*/
-
-package io.modelcontextprotocol.spec;
+package io.modelcontextprotocol.json.schema.jackson;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import io.modelcontextprotocol.json.schema.JsonSchemaValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,8 +20,6 @@
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
@@ -52,9 +50,12 @@ public DefaultJsonSchemaValidator(ObjectMapper objectMapper) {
@Override
public ValidationResponse validate(Map schema, Map structuredContent) {
-
- Assert.notNull(schema, "Schema must not be null");
- Assert.notNull(structuredContent, "Structured content must not be null");
+ if (schema == null) {
+ throw new IllegalArgumentException("Schema must not be null");
+ }
+ if (structuredContent == null) {
+ throw new IllegalArgumentException("Structured content must not be null");
+ }
try {
diff --git a/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/JacksonJsonSchemaValidatorSupplier.java b/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/JacksonJsonSchemaValidatorSupplier.java
new file mode 100644
index 000000000..ef3413c58
--- /dev/null
+++ b/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/JacksonJsonSchemaValidatorSupplier.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2025 - 2025 the original author or authors.
+ */
+package io.modelcontextprotocol.json.schema.jackson;
+
+import io.modelcontextprotocol.json.schema.JsonSchemaValidator;
+import io.modelcontextprotocol.json.schema.JsonSchemaValidatorSupplier;
+
+/**
+ * A concrete implementation of {@link JsonSchemaValidatorSupplier} that provides a
+ * {@link JsonSchemaValidator} instance based on the Jackson library.
+ *
+ * @see JsonSchemaValidatorSupplier
+ * @see JsonSchemaValidator
+ */
+public class JacksonJsonSchemaValidatorSupplier implements JsonSchemaValidatorSupplier {
+
+ /**
+ * Returns a new instance of {@link JsonSchemaValidator} that uses the Jackson library
+ * for JSON schema validation.
+ * @return A {@link JsonSchemaValidator} instance.
+ */
+ @Override
+ public JsonSchemaValidator get() {
+ return new DefaultJsonSchemaValidator();
+ }
+
+}
diff --git a/mcp-json-jackson2/src/main/resources/META-INF/services/io.modelcontextprotocol.json.McpJsonMapperSupplier b/mcp-json-jackson2/src/main/resources/META-INF/services/io.modelcontextprotocol.json.McpJsonMapperSupplier
new file mode 100644
index 000000000..8ea66d698
--- /dev/null
+++ b/mcp-json-jackson2/src/main/resources/META-INF/services/io.modelcontextprotocol.json.McpJsonMapperSupplier
@@ -0,0 +1 @@
+io.modelcontextprotocol.json.jackson.JacksonMcpJsonMapperSupplier
\ No newline at end of file
diff --git a/mcp-json-jackson2/src/main/resources/META-INF/services/io.modelcontextprotocol.json.schema.JsonSchemaValidatorSupplier b/mcp-json-jackson2/src/main/resources/META-INF/services/io.modelcontextprotocol.json.schema.JsonSchemaValidatorSupplier
new file mode 100644
index 000000000..0fb0b7e5a
--- /dev/null
+++ b/mcp-json-jackson2/src/main/resources/META-INF/services/io.modelcontextprotocol.json.schema.JsonSchemaValidatorSupplier
@@ -0,0 +1 @@
+io.modelcontextprotocol.json.schema.jackson.JacksonJsonSchemaValidatorSupplier
\ No newline at end of file
diff --git a/mcp-json/pom.xml b/mcp-json/pom.xml
new file mode 100644
index 000000000..037ef2ac4
--- /dev/null
+++ b/mcp-json/pom.xml
@@ -0,0 +1,39 @@
+
+
+ 4.0.0
+
+ io.modelcontextprotocol.sdk
+ mcp-parent
+ 0.13.0-SNAPSHOT
+
+ mcp-json
+ jar
+ Java MCP SDK JSON Support
+ Java MCP SDK JSON Support API
+ https://github.com/modelcontextprotocol/java-sdk
+
+ https://github.com/modelcontextprotocol/java-sdk
+ git://github.com/modelcontextprotocol/java-sdk.git
+ git@github.com/modelcontextprotocol/java-sdk.git
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
diff --git a/mcp-json/src/main/java/io/modelcontextprotocol/json/McpJsonInternal.java b/mcp-json/src/main/java/io/modelcontextprotocol/json/McpJsonInternal.java
new file mode 100644
index 000000000..673cda3c2
--- /dev/null
+++ b/mcp-json/src/main/java/io/modelcontextprotocol/json/McpJsonInternal.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2025 - 2025 the original author or authors.
+ */
+package io.modelcontextprotocol.json;
+
+import java.util.ServiceLoader;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Stream;
+
+/**
+ * Utility class for creating a default {@link McpJsonMapper} instance. This class
+ * provides a single method to create a default mapper using the {@link ServiceLoader}
+ * mechanism.
+ */
+final class McpJsonInternal {
+
+ private static McpJsonMapper defaultJsonMapper = null;
+
+ /**
+ * Returns the cached default {@link McpJsonMapper} instance. If the default mapper
+ * has not been created yet, it will be initialized using the
+ * {@link #createDefaultMapper()} method.
+ * @return the default {@link McpJsonMapper} instance
+ * @throws IllegalStateException if no default {@link McpJsonMapper} implementation is
+ * found
+ */
+ static McpJsonMapper getDefaultMapper() {
+ if (defaultJsonMapper == null) {
+ defaultJsonMapper = McpJsonInternal.createDefaultMapper();
+ }
+ return defaultJsonMapper;
+ }
+
+ /**
+ * Creates a default {@link McpJsonMapper} instance using the {@link ServiceLoader}
+ * mechanism. The default mapper is resolved by loading the first available
+ * {@link McpJsonMapperSupplier} implementation on the classpath.
+ * @return the default {@link McpJsonMapper} instance
+ * @throws IllegalStateException if no default {@link McpJsonMapper} implementation is
+ * found
+ */
+ static McpJsonMapper createDefaultMapper() {
+ AtomicReference ex = new AtomicReference<>();
+ return ServiceLoader.load(McpJsonMapperSupplier.class).stream().flatMap(p -> {
+ try {
+ McpJsonMapperSupplier supplier = p.get();
+ return Stream.ofNullable(supplier);
+ }
+ catch (Exception e) {
+ addException(ex, e);
+ return Stream.empty();
+ }
+ }).flatMap(jsonMapperSupplier -> {
+ try {
+ return Stream.ofNullable(jsonMapperSupplier.get());
+ }
+ catch (Exception e) {
+ addException(ex, e);
+ return Stream.empty();
+ }
+ }).findFirst().orElseThrow(() -> {
+ if (ex.get() != null) {
+ return ex.get();
+ }
+ else {
+ return new IllegalStateException("No default McpJsonMapper implementation found");
+ }
+ });
+ }
+
+ private static void addException(AtomicReference ref, Exception toAdd) {
+ ref.updateAndGet(existing -> {
+ if (existing == null) {
+ return new IllegalStateException("Failed to initialize default McpJsonMapper", toAdd);
+ }
+ else {
+ existing.addSuppressed(toAdd);
+ return existing;
+ }
+ });
+ }
+
+}
diff --git a/mcp-json/src/main/java/io/modelcontextprotocol/json/McpJsonMapper.java b/mcp-json/src/main/java/io/modelcontextprotocol/json/McpJsonMapper.java
new file mode 100644
index 000000000..0d542d897
--- /dev/null
+++ b/mcp-json/src/main/java/io/modelcontextprotocol/json/McpJsonMapper.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2025 - 2025 the original author or authors.
+ */
+package io.modelcontextprotocol.json;
+
+import java.io.IOException;
+
+/**
+ * Abstraction for JSON serialization/deserialization to decouple the SDK from any
+ * specific JSON library. A default implementation backed by Jackson is provided in
+ * io.modelcontextprotocol.spec.json.jackson.JacksonJsonMapper.
+ */
+public interface McpJsonMapper {
+
+ /**
+ * Deserialize JSON string into a target type.
+ * @param content JSON as String
+ * @param type target class
+ * @return deserialized instance
+ * @param generic type
+ * @throws IOException on parse errors
+ */
+ T readValue(String content, Class type) throws IOException;
+
+ /**
+ * Deserialize JSON bytes into a target type.
+ * @param content JSON as bytes
+ * @param type target class
+ * @return deserialized instance
+ * @param generic type
+ * @throws IOException on parse errors
+ */
+ T readValue(byte[] content, Class type) throws IOException;
+
+ /**
+ * Deserialize JSON string into a parameterized target type.
+ * @param content JSON as String
+ * @param type parameterized type reference
+ * @return deserialized instance
+ * @param generic type
+ * @throws IOException on parse errors
+ */
+ T readValue(String content, TypeRef type) throws IOException;
+
+ /**
+ * Deserialize JSON bytes into a parameterized target type.
+ * @param content JSON as bytes
+ * @param type parameterized type reference
+ * @return deserialized instance
+ * @param generic type
+ * @throws IOException on parse errors
+ */
+ T readValue(byte[] content, TypeRef type) throws IOException;
+
+ /**
+ * Convert a value to a given type, useful for mapping nested JSON structures.
+ * @param fromValue source value
+ * @param type target class
+ * @return converted value
+ * @param generic type
+ */
+ T convertValue(Object fromValue, Class type);
+
+ /**
+ * Convert a value to a given parameterized type.
+ * @param fromValue source value
+ * @param type target type reference
+ * @return converted value
+ * @param generic type
+ */
+ T convertValue(Object fromValue, TypeRef type);
+
+ /**
+ * Serialize an object to JSON string.
+ * @param value object to serialize
+ * @return JSON as String
+ * @throws IOException on serialization errors
+ */
+ String writeValueAsString(Object value) throws IOException;
+
+ /**
+ * Serialize an object to JSON bytes.
+ * @param value object to serialize
+ * @return JSON as bytes
+ * @throws IOException on serialization errors
+ */
+ byte[] writeValueAsBytes(Object value) throws IOException;
+
+ /**
+ * Returns the default {@link McpJsonMapper}.
+ * @return The default {@link McpJsonMapper}
+ * @throws IllegalStateException If no {@link McpJsonMapper} implementation exists on
+ * the classpath.
+ */
+ static McpJsonMapper getDefault() {
+ return McpJsonInternal.getDefaultMapper();
+ }
+
+ /**
+ * Creates a new default {@link McpJsonMapper}.
+ * @return The default {@link McpJsonMapper}
+ * @throws IllegalStateException If no {@link McpJsonMapper} implementation exists on
+ * the classpath.
+ */
+ static McpJsonMapper createDefault() {
+ return McpJsonInternal.createDefaultMapper();
+ }
+
+}
diff --git a/mcp-json/src/main/java/io/modelcontextprotocol/json/McpJsonMapperSupplier.java b/mcp-json/src/main/java/io/modelcontextprotocol/json/McpJsonMapperSupplier.java
new file mode 100644
index 000000000..c26bb8bf9
--- /dev/null
+++ b/mcp-json/src/main/java/io/modelcontextprotocol/json/McpJsonMapperSupplier.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2025 - 2025 the original author or authors.
+ */
+package io.modelcontextprotocol.json;
+
+import java.util.function.Supplier;
+
+/**
+ * Strategy interface for resolving a {@link McpJsonMapper}.
+ */
+public interface McpJsonMapperSupplier extends Supplier {
+
+}
diff --git a/mcp-json/src/main/java/io/modelcontextprotocol/json/TypeRef.java b/mcp-json/src/main/java/io/modelcontextprotocol/json/TypeRef.java
new file mode 100644
index 000000000..6ef53137c
--- /dev/null
+++ b/mcp-json/src/main/java/io/modelcontextprotocol/json/TypeRef.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2025 - 2025 the original author or authors.
+ */
+package io.modelcontextprotocol.json;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+/**
+ * Captures generic type information at runtime for parameterized JSON (de)serialization.
+ * Usage: TypeRef> ref = new TypeRef<>(){};
+ */
+public abstract class TypeRef {
+
+ private final Type type;
+
+ /**
+ * Constructs a new TypeRef instance, capturing the generic type information of the
+ * subclass. This constructor should be called from an anonymous subclass to capture
+ * the actual type arguments. For example:
+ * TypeRef<List<Foo>> ref = new TypeRef<>(){};
+ *
+ * @throws IllegalStateException if TypeRef is not subclassed with actual type
+ * information
+ */
+ protected TypeRef() {
+ Type superClass = getClass().getGenericSuperclass();
+ if (superClass instanceof Class) {
+ throw new IllegalStateException("TypeRef constructed without actual type information");
+ }
+ this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
+ }
+
+ /**
+ * Returns the captured type information.
+ * @return the Type representing the actual type argument captured by this TypeRef
+ * instance
+ */
+ public Type getType() {
+ return type;
+ }
+
+}
diff --git a/mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaInternal.java b/mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaInternal.java
new file mode 100644
index 000000000..4beecb51d
--- /dev/null
+++ b/mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaInternal.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2025 - 2025 the original author or authors.
+ */
+package io.modelcontextprotocol.json.schema;
+
+import java.util.ServiceLoader;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Stream;
+
+/**
+ * Internal utility class for creating a default {@link JsonSchemaValidator} instance.
+ * This class uses the {@link ServiceLoader} to discover and instantiate a
+ * {@link JsonSchemaValidatorSupplier} implementation.
+ */
+final class JsonSchemaInternal {
+
+ private static JsonSchemaValidator defaultValidator = null;
+
+ /**
+ * Returns the default {@link JsonSchemaValidator} instance. If the default validator
+ * has not been initialized, it will be created using the {@link ServiceLoader} to
+ * discover and instantiate a {@link JsonSchemaValidatorSupplier} implementation.
+ * @return The default {@link JsonSchemaValidator} instance.
+ * @throws IllegalStateException If no {@link JsonSchemaValidatorSupplier}
+ * implementation exists on the classpath or if an error occurs during instantiation.
+ */
+ static JsonSchemaValidator getDefaultValidator() {
+ if (defaultValidator == null) {
+ defaultValidator = JsonSchemaInternal.createDefaultValidator();
+ }
+ return defaultValidator;
+ }
+
+ /**
+ * Creates a default {@link JsonSchemaValidator} instance by loading a
+ * {@link JsonSchemaValidatorSupplier} implementation using the {@link ServiceLoader}.
+ * @return A default {@link JsonSchemaValidator} instance.
+ * @throws IllegalStateException If no {@link JsonSchemaValidatorSupplier}
+ * implementation is found or if an error occurs during instantiation.
+ */
+ static JsonSchemaValidator createDefaultValidator() {
+ AtomicReference ex = new AtomicReference<>();
+ return ServiceLoader.load(JsonSchemaValidatorSupplier.class).stream().flatMap(p -> {
+ try {
+ JsonSchemaValidatorSupplier supplier = p.get();
+ return Stream.ofNullable(supplier);
+ }
+ catch (Exception e) {
+ addException(ex, e);
+ return Stream.empty();
+ }
+ }).flatMap(jsonMapperSupplier -> {
+ try {
+ return Stream.of(jsonMapperSupplier.get());
+ }
+ catch (Exception e) {
+ addException(ex, e);
+ return Stream.empty();
+ }
+ }).findFirst().orElseThrow(() -> {
+ if (ex.get() != null) {
+ return ex.get();
+ }
+ else {
+ return new IllegalStateException("No default JsonSchemaValidatorSupplier implementation found");
+ }
+ });
+ }
+
+ private static void addException(AtomicReference ref, Exception toAdd) {
+ ref.updateAndGet(existing -> {
+ if (existing == null) {
+ return new IllegalStateException("Failed to initialize default JsonSchemaValidatorSupplier", toAdd);
+ }
+ else {
+ existing.addSuppressed(toAdd);
+ return existing;
+ }
+ });
+ }
+
+}
diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/JsonSchemaValidator.java b/mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidator.java
similarity index 59%
rename from mcp/src/main/java/io/modelcontextprotocol/spec/JsonSchemaValidator.java
rename to mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidator.java
index 572d7c043..9b1a58db8 100644
--- a/mcp/src/main/java/io/modelcontextprotocol/spec/JsonSchemaValidator.java
+++ b/mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidator.java
@@ -1,10 +1,12 @@
/*
* Copyright 2024-2024 the original author or authors.
*/
-
-package io.modelcontextprotocol.spec;
+package io.modelcontextprotocol.json.schema;
import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Stream;
/**
* Interface for validating structured content against a JSON schema. This interface
@@ -22,7 +24,7 @@ public interface JsonSchemaValidator {
* @param jsonStructuredOutput The text structured content in JSON format if the
* validation was successful, otherwise null.
*/
- public record ValidationResponse(boolean valid, String errorMessage, String jsonStructuredOutput) {
+ record ValidationResponse(boolean valid, String errorMessage, String jsonStructuredOutput) {
public static ValidationResponse asValid(String jsonStructuredOutput) {
return new ValidationResponse(true, null, jsonStructuredOutput);
@@ -42,4 +44,24 @@ public static ValidationResponse asInvalid(String message) {
*/
ValidationResponse validate(Map schema, Map structuredContent);
+ /**
+ * Creates the default {@link JsonSchemaValidator}.
+ * @return The default {@link JsonSchemaValidator}
+ * @throws IllegalStateException If no {@link JsonSchemaValidator} implementation
+ * exists on the classpath.
+ */
+ static JsonSchemaValidator createDefault() {
+ return JsonSchemaInternal.createDefaultValidator();
+ }
+
+ /**
+ * Returns the default {@link JsonSchemaValidator}.
+ * @return The default {@link JsonSchemaValidator}
+ * @throws IllegalStateException If no {@link JsonSchemaValidator} implementation
+ * exists on the classpath.
+ */
+ static JsonSchemaValidator getDefault() {
+ return JsonSchemaInternal.getDefaultValidator();
+ }
+
}
diff --git a/mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidatorSupplier.java b/mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidatorSupplier.java
new file mode 100644
index 000000000..f24c76bd8
--- /dev/null
+++ b/mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidatorSupplier.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2025 - 2025 the original author or authors.
+ */
+package io.modelcontextprotocol.json.schema;
+
+import java.util.function.Supplier;
+
+/**
+ * A supplier interface that provides a {@link JsonSchemaValidator} instance.
+ * Implementations of this interface are expected to return a new or cached instance of
+ * {@link JsonSchemaValidator} when {@link #get()} is invoked.
+ *
+ * @see JsonSchemaValidator
+ * @see Supplier
+ */
+public interface JsonSchemaValidatorSupplier extends Supplier {
+
+}
diff --git a/mcp-spring/mcp-spring-webflux/pom.xml b/mcp-spring/mcp-spring-webflux/pom.xml
index c2dac2bf9..eda38881a 100644
--- a/mcp-spring/mcp-spring-webflux/pom.xml
+++ b/mcp-spring/mcp-spring-webflux/pom.xml
@@ -22,7 +22,13 @@
-
+
+ io.modelcontextprotocol.sdk
+ mcp-json-jackson2
+ 0.13.0-SNAPSHOT
+
+
+ io.modelcontextprotocol.sdkmcp0.13.0-SNAPSHOT
diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebClientStreamableHttpTransport.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebClientStreamableHttpTransport.java
index 853aed2bf..154eb4703 100644
--- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebClientStreamableHttpTransport.java
+++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebClientStreamableHttpTransport.java
@@ -22,8 +22,8 @@
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
-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.DefaultMcpTransportSession;
import io.modelcontextprotocol.spec.DefaultMcpTransportStream;
@@ -88,7 +88,7 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
private static final ParameterizedTypeReference> PARAMETERIZED_TYPE_REF = new ParameterizedTypeReference<>() {
};
- private final ObjectMapper objectMapper;
+ private final McpJsonMapper jsonMapper;
private final WebClient webClient;
@@ -104,9 +104,9 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
private final AtomicReference> exceptionHandler = new AtomicReference<>();
- private WebClientStreamableHttpTransport(ObjectMapper objectMapper, WebClient.Builder webClientBuilder,
+ private WebClientStreamableHttpTransport(McpJsonMapper jsonMapper, WebClient.Builder webClientBuilder,
String endpoint, boolean resumableStreams, boolean openConnectionOnStartup) {
- this.objectMapper = objectMapper;
+ this.jsonMapper = jsonMapper;
this.webClient = webClientBuilder.build();
this.endpoint = endpoint;
this.resumableStreams = resumableStreams;
@@ -366,8 +366,7 @@ private Flux extractError(ClientResponse response, Str
McpSchema.JSONRPCResponse.JSONRPCError jsonRpcError = null;
Exception toPropagate;
try {
- McpSchema.JSONRPCResponse jsonRpcResponse = objectMapper.readValue(body,
- McpSchema.JSONRPCResponse.class);
+ McpSchema.JSONRPCResponse jsonRpcResponse = jsonMapper.readValue(body, McpSchema.JSONRPCResponse.class);
jsonRpcError = jsonRpcResponse.error();
toPropagate = jsonRpcError != null ? new McpError(jsonRpcError)
: new McpTransportException("Can't parse the jsonResponse " + jsonRpcResponse);
@@ -427,7 +426,7 @@ private Flux directResponseFlux(McpSchema.JSONRPCMessa
s.complete();
}
else {
- McpSchema.JSONRPCMessage jsonRpcResponse = McpSchema.deserializeJsonRpcMessage(objectMapper,
+ McpSchema.JSONRPCMessage jsonRpcResponse = McpSchema.deserializeJsonRpcMessage(jsonMapper,
responseMessage);
s.next(List.of(jsonRpcResponse));
}
@@ -447,8 +446,8 @@ private Flux newEventStream(ClientResponse response, S
}
@Override
- public T unmarshalFrom(Object data, TypeReference typeRef) {
- return this.objectMapper.convertValue(data, typeRef);
+ public T unmarshalFrom(Object data, TypeRef typeRef) {
+ return this.jsonMapper.convertValue(data, typeRef);
}
private Tuple2, Iterable> parse(ServerSentEvent event) {
@@ -456,7 +455,7 @@ private Tuple2, Iterable> parse(Serve
try {
// We don't support batching ATM and probably won't since the next version
// considers removing it.
- McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.objectMapper, event.data());
+ McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.jsonMapper, event.data());
return Tuples.of(Optional.ofNullable(event.id()), List.of(message));
}
catch (IOException ioException) {
@@ -474,7 +473,7 @@ private Tuple2, Iterable> parse(Serve
*/
public static class Builder {
- private ObjectMapper objectMapper;
+ private McpJsonMapper jsonMapper;
private WebClient.Builder webClientBuilder;
@@ -490,13 +489,13 @@ private Builder(WebClient.Builder webClientBuilder) {
}
/**
- * Configure the {@link ObjectMapper} to use.
- * @param objectMapper instance to use
+ * Configure the {@link McpJsonMapper} 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;
}
@@ -555,10 +554,8 @@ public Builder openConnectionOnStartup(boolean openConnectionOnStartup) {
* @return a new instance of {@link WebClientStreamableHttpTransport}
*/
public WebClientStreamableHttpTransport build() {
- ObjectMapper objectMapper = this.objectMapper != null ? this.objectMapper : new ObjectMapper();
-
- return new WebClientStreamableHttpTransport(objectMapper, this.webClientBuilder, endpoint, resumableStreams,
- openConnectionOnStartup);
+ return new WebClientStreamableHttpTransport(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
+ webClientBuilder, endpoint, resumableStreams, openConnectionOnStartup);
}
}
diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebFluxSseClientTransport.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebFluxSseClientTransport.java
index 51d21d18b..91b89d6d2 100644
--- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebFluxSseClientTransport.java
+++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebFluxSseClientTransport.java
@@ -9,8 +9,8 @@
import java.util.function.BiConsumer;
import java.util.function.Function;
-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.spec.HttpHeaders;
import io.modelcontextprotocol.spec.McpClientTransport;
@@ -100,10 +100,10 @@ public class WebFluxSseClientTransport implements McpClientTransport {
private final WebClient webClient;
/**
- * ObjectMapper for serializing outbound messages and deserializing inbound messages.
+ * JSON mapper for serializing outbound messages and deserializing inbound messages.
* Handles conversion between JSON-RPC messages and their string representation.
*/
- protected ObjectMapper objectMapper;
+ protected McpJsonMapper jsonMapper;
/**
* Subscription for the SSE connection handling inbound messages. Used for cleanup
@@ -129,27 +129,16 @@ public class WebFluxSseClientTransport implements McpClientTransport {
*/
private String sseEndpoint;
- /**
- * Constructs a new SseClientTransport with the specified WebClient builder. Uses a
- * default ObjectMapper instance for JSON processing.
- * @param webClientBuilder the WebClient.Builder to use for creating the WebClient
- * instance
- * @throws IllegalArgumentException if webClientBuilder is null
- */
- public WebFluxSseClientTransport(WebClient.Builder webClientBuilder) {
- this(webClientBuilder, new ObjectMapper());
- }
-
/**
* Constructs a new SseClientTransport with the specified WebClient builder and
* ObjectMapper. Initializes both inbound and outbound message processing pipelines.
* @param webClientBuilder the WebClient.Builder to use for creating the WebClient
* instance
- * @param objectMapper the ObjectMapper to use for JSON processing
+ * @param jsonMapper the ObjectMapper to use for JSON processing
* @throws IllegalArgumentException if either parameter is null
*/
- public WebFluxSseClientTransport(WebClient.Builder webClientBuilder, ObjectMapper objectMapper) {
- this(webClientBuilder, objectMapper, DEFAULT_SSE_ENDPOINT);
+ public WebFluxSseClientTransport(WebClient.Builder webClientBuilder, McpJsonMapper jsonMapper) {
+ this(webClientBuilder, jsonMapper, DEFAULT_SSE_ENDPOINT);
}
/**
@@ -157,17 +146,16 @@ public WebFluxSseClientTransport(WebClient.Builder webClientBuilder, ObjectMappe
* ObjectMapper. Initializes both inbound and outbound message processing pipelines.
* @param webClientBuilder the WebClient.Builder to use for creating the WebClient
* instance
- * @param objectMapper the ObjectMapper to use for JSON processing
+ * @param jsonMapper the ObjectMapper to use for JSON processing
* @param sseEndpoint the SSE endpoint URI to use for establishing the connection
* @throws IllegalArgumentException if either parameter is null
*/
- public WebFluxSseClientTransport(WebClient.Builder webClientBuilder, ObjectMapper objectMapper,
- String sseEndpoint) {
- Assert.notNull(objectMapper, "ObjectMapper must not be null");
+ public WebFluxSseClientTransport(WebClient.Builder webClientBuilder, McpJsonMapper jsonMapper, String sseEndpoint) {
+ Assert.notNull(jsonMapper, "jsonMapper must not be null");
Assert.notNull(webClientBuilder, "WebClient.Builder must not be null");
Assert.hasText(sseEndpoint, "SSE endpoint must not be null or empty");
- this.objectMapper = objectMapper;
+ this.jsonMapper = jsonMapper;
this.webClient = webClientBuilder.build();
this.sseEndpoint = sseEndpoint;
}
@@ -217,7 +205,7 @@ public Mono connect(Function, Mono> h
}
else if (MESSAGE_EVENT_TYPE.equals(event.event())) {
try {
- JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.objectMapper, event.data());
+ JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.jsonMapper, event.data());
s.next(message);
}
catch (IOException ioException) {
@@ -255,7 +243,7 @@ public Mono sendMessage(JSONRPCMessage message) {
return Mono.empty();
}
try {
- String jsonText = this.objectMapper.writeValueAsString(message);
+ String jsonText = this.jsonMapper.writeValueAsString(message);
return webClient.post()
.uri(messageEndpointUri)
.contentType(MediaType.APPLICATION_JSON)
@@ -349,13 +337,13 @@ public Mono closeGracefully() { // @formatter:off
* type conversion capabilities to handle complex object structures.
* @param the target type to convert the data into
* @param data the source object to convert
- * @param typeRef the TypeReference describing the target type
+ * @param typeRef the TypeRef describing the target type
* @return the unmarshalled object of type T
* @throws IllegalArgumentException if the conversion cannot be performed
*/
@Override
- public T unmarshalFrom(Object data, TypeReference typeRef) {
- return this.objectMapper.convertValue(data, typeRef);
+ public T unmarshalFrom(Object data, TypeRef typeRef) {
+ return this.jsonMapper.convertValue(data, typeRef);
}
/**
@@ -377,7 +365,7 @@ public static class Builder {
private String sseEndpoint = DEFAULT_SSE_ENDPOINT;
- private ObjectMapper objectMapper = new ObjectMapper();
+ private McpJsonMapper jsonMapper;
/**
* Creates a new builder with the specified WebClient.Builder.
@@ -400,13 +388,13 @@ public Builder sseEndpoint(String sseEndpoint) {
}
/**
- * Sets the object mapper for JSON serialization/deserialization.
- * @param objectMapper the object mapper
+ * Sets the JSON mapper for serialization/deserialization.
+ * @param jsonMapper the JsonMapper to use
* @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;
}
@@ -415,7 +403,8 @@ public Builder objectMapper(ObjectMapper objectMapper) {
* @return a new transport instance
*/
public WebFluxSseClientTransport build() {
- return new WebFluxSseClientTransport(webClientBuilder, objectMapper, sseEndpoint);
+ return new WebFluxSseClientTransport(webClientBuilder,
+ jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, sseEndpoint);
}
}
diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java
index f64346265..95355c0f2 100644
--- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java
+++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java
@@ -9,8 +9,8 @@
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
-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;
@@ -97,7 +97,7 @@ public class WebFluxSseServerTransportProvider implements McpServerTransportProv
public static final String DEFAULT_BASE_URL = "";
- private final ObjectMapper objectMapper;
+ private final McpJsonMapper jsonMapper;
/**
* Base URL for the message endpoint. This is used to construct the full URL for
@@ -131,82 +131,10 @@ public class WebFluxSseServerTransportProvider implements McpServerTransportProv
*/
private KeepAliveScheduler keepAliveScheduler;
- /**
- * Constructs a new WebFlux SSE server transport provider instance with the default
- * SSE endpoint.
- * @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
- * of MCP messages. Must not be null.
- * @param messageEndpoint The endpoint URI where clients should send their JSON-RPC
- * messages. This endpoint will be communicated to clients during SSE connection
- * setup. Must not be null.
- * @throws IllegalArgumentException if either parameter is null
- * @deprecated Use the builder {@link #builder()} instead for better configuration
- * options.
- */
- @Deprecated
- public WebFluxSseServerTransportProvider(ObjectMapper objectMapper, String messageEndpoint) {
- this(objectMapper, messageEndpoint, DEFAULT_SSE_ENDPOINT);
- }
-
- /**
- * Constructs a new WebFlux SSE server transport provider instance.
- * @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
- * of MCP messages. Must not be null.
- * @param messageEndpoint The endpoint URI where clients should send their JSON-RPC
- * messages. This endpoint will be communicated to clients during SSE connection
- * setup. Must not be null.
- * @throws IllegalArgumentException if either parameter is null
- * @deprecated Use the builder {@link #builder()} instead for better configuration
- * options.
- */
- @Deprecated
- public WebFluxSseServerTransportProvider(ObjectMapper objectMapper, String messageEndpoint, String sseEndpoint) {
- this(objectMapper, DEFAULT_BASE_URL, messageEndpoint, sseEndpoint);
- }
-
- /**
- * Constructs a new WebFlux SSE server transport provider instance.
- * @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
- * of MCP messages. Must not be null.
- * @param baseUrl webflux message base path
- * @param messageEndpoint The endpoint URI where clients should send their JSON-RPC
- * messages. This endpoint will be communicated to clients during SSE connection
- * setup. Must not be null.
- * @throws IllegalArgumentException if either parameter is null
- * @deprecated Use the builder {@link #builder()} instead for better configuration
- * options.
- */
- @Deprecated
- public WebFluxSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint,
- String sseEndpoint) {
- this(objectMapper, baseUrl, messageEndpoint, sseEndpoint, null);
- }
-
- /**
- * Constructs a new WebFlux SSE server transport provider instance.
- * @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
- * of MCP messages. Must not be null.
- * @param baseUrl webflux message base path
- * @param messageEndpoint The endpoint URI where clients should send their JSON-RPC
- * messages. This endpoint will be communicated to clients during SSE connection
- * setup. Must not be null.
- * @param sseEndpoint The SSE endpoint path. Must not be null.
- * @param keepAliveInterval The interval for sending keep-alive pings to clients.
- * @throws IllegalArgumentException if either parameter is null
- * @deprecated Use the builder {@link #builder()} instead for better configuration
- * options.
- */
- @Deprecated
- public WebFluxSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint,
- String sseEndpoint, Duration keepAliveInterval) {
- this(objectMapper, baseUrl, messageEndpoint, sseEndpoint, keepAliveInterval,
- (serverRequest) -> McpTransportContext.EMPTY);
- }
-
/**
* Constructs a new WebFlux SSE server transport provider instance.
- * @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
- * of MCP messages. Must not be null.
+ * @param jsonMapper The ObjectMapper to use for JSON serialization/deserialization of
+ * MCP messages. Must not be null.
* @param baseUrl webflux message base path
* @param messageEndpoint The endpoint URI where clients should send their JSON-RPC
* messages. This endpoint will be communicated to clients during SSE connection
@@ -217,16 +145,16 @@ public WebFluxSseServerTransportProvider(ObjectMapper objectMapper, String baseU
* context from HTTP requests. Must not be null.
* @throws IllegalArgumentException if either parameter is null
*/
- private WebFluxSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint,
+ private WebFluxSseServerTransportProvider(McpJsonMapper jsonMapper, String baseUrl, String messageEndpoint,
String sseEndpoint, Duration keepAliveInterval,
McpTransportContextExtractor contextExtractor) {
- Assert.notNull(objectMapper, "ObjectMapper must not be null");
+ Assert.notNull(jsonMapper, "ObjectMapper must not be null");
Assert.notNull(baseUrl, "Message base path must not be null");
Assert.notNull(messageEndpoint, "Message endpoint must not be null");
Assert.notNull(sseEndpoint, "SSE endpoint 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;
@@ -404,7 +332,7 @@ private Mono handleMessage(ServerRequest request) {
return request.bodyToMono(String.class).flatMap(body -> {
try {
- McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper, body);
+ McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body);
return session.handle(message).flatMap(response -> ServerResponse.ok().build()).onErrorResume(error -> {
logger.error("Error processing message: {}", error.getMessage());
// TODO: instead of signalling the error, just respond with 200 OK
@@ -433,7 +361,7 @@ public WebFluxMcpSessionTransport(FluxSink> sink) {
public Mono sendMessage(McpSchema.JSONRPCMessage message) {
return Mono.fromSupplier(() -> {
try {
- return objectMapper.writeValueAsString(message);
+ return jsonMapper.writeValueAsString(message);
}
catch (IOException e) {
throw Exceptions.propagate(e);
@@ -452,8 +380,8 @@ public Mono sendMessage(McpSchema.JSONRPCMessage message) {
}
@Override
- public T unmarshalFrom(Object data, TypeReference typeRef) {
- return objectMapper.convertValue(data, typeRef);
+ public T unmarshalFrom(Object data, TypeRef typeRef) {
+ return jsonMapper.convertValue(data, typeRef);
}
@Override
@@ -480,7 +408,7 @@ public static Builder builder() {
*/
public static class Builder {
- private ObjectMapper objectMapper;
+ private McpJsonMapper jsonMapper;
private String baseUrl = DEFAULT_BASE_URL;
@@ -494,15 +422,15 @@ public static class Builder {
serverRequest) -> McpTransportContext.EMPTY;
/**
- * Sets the ObjectMapper to use for JSON serialization/deserialization of MCP
+ * Sets the McpJsonMapper to use for JSON serialization/deserialization of MCP
* messages.
- * @param objectMapper The ObjectMapper instance. Must not be null.
+ * @param jsonMapper The McpJsonMapper 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;
}
@@ -577,11 +505,9 @@ public Builder contextExtractor(McpTransportContextExtractor cont
* @throws IllegalStateException if required parameters are not set
*/
public WebFluxSseServerTransportProvider build() {
- Assert.notNull(objectMapper, "ObjectMapper must be set");
Assert.notNull(messageEndpoint, "Message endpoint must be set");
-
- return new WebFluxSseServerTransportProvider(objectMapper, baseUrl, messageEndpoint, sseEndpoint,
- keepAliveInterval, contextExtractor);
+ return new WebFluxSseServerTransportProvider(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
+ baseUrl, messageEndpoint, sseEndpoint, keepAliveInterval, contextExtractor);
}
}
diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java
index 1f3d4c3bf..400be341e 100644
--- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java
+++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java
@@ -4,7 +4,7 @@
package io.modelcontextprotocol.server.transport;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.server.McpStatelessServerHandler;
import io.modelcontextprotocol.server.McpTransportContextExtractor;
@@ -34,7 +34,7 @@ public class WebFluxStatelessServerTransport implements McpStatelessServerTransp
private static final Logger logger = LoggerFactory.getLogger(WebFluxStatelessServerTransport.class);
- private final ObjectMapper objectMapper;
+ private final McpJsonMapper jsonMapper;
private final String mcpEndpoint;
@@ -46,13 +46,13 @@ public class WebFluxStatelessServerTransport implements McpStatelessServerTransp
private volatile boolean isClosing = false;
- private WebFluxStatelessServerTransport(ObjectMapper objectMapper, String mcpEndpoint,
+ private WebFluxStatelessServerTransport(McpJsonMapper jsonMapper, String mcpEndpoint,
McpTransportContextExtractor 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;
this.routerFunction = RouterFunctions.route()
@@ -106,13 +106,20 @@ private Mono handlePost(ServerRequest request) {
return request.bodyToMono(String.class).flatMap(body -> {
try {
- McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper, body);
+ McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body);
if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
- return this.mcpHandler.handleRequest(transportContext, jsonrpcRequest)
- .flatMap(jsonrpcResponse -> ServerResponse.ok()
- .contentType(MediaType.APPLICATION_JSON)
- .bodyValue(jsonrpcResponse));
+ return this.mcpHandler.handleRequest(transportContext, jsonrpcRequest).flatMap(jsonrpcResponse -> {
+ try {
+ String json = jsonMapper.writeValueAsString(jsonrpcResponse);
+ return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(json);
+ }
+ catch (IOException e) {
+ logger.error("Failed to serialize response: {}", e.getMessage());
+ return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .bodyValue(new McpError("Failed to serialize response"));
+ }
+ });
}
else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) {
return this.mcpHandler.handleNotification(transportContext, jsonrpcNotification)
@@ -146,7 +153,7 @@ public static Builder builder() {
*/
public static class Builder {
- private ObjectMapper objectMapper;
+ private McpJsonMapper jsonMapper;
private String mcpEndpoint = "/mcp";
@@ -158,15 +165,15 @@ private Builder() {
}
/**
- * 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;
}
@@ -205,10 +212,9 @@ public Builder contextExtractor(McpTransportContextExtractor cont
* @throws IllegalStateException if required parameters are not set
*/
public WebFluxStatelessServerTransport build() {
- Assert.notNull(objectMapper, "ObjectMapper must be set");
Assert.notNull(mcpEndpoint, "Message endpoint must be set");
-
- return new WebFluxStatelessServerTransport(objectMapper, mcpEndpoint, contextExtractor);
+ return new WebFluxStatelessServerTransport(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
+ mcpEndpoint, contextExtractor);
}
}
diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java
index 44d89eaeb..b6cc20864 100644
--- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java
+++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java
@@ -4,8 +4,8 @@
package io.modelcontextprotocol.server.transport;
-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.HttpHeaders;
@@ -49,7 +49,7 @@ public class WebFluxStreamableServerTransportProvider implements McpStreamableSe
public static final String MESSAGE_EVENT_TYPE = "message";
- private final ObjectMapper objectMapper;
+ private final McpJsonMapper jsonMapper;
private final String mcpEndpoint;
@@ -67,14 +67,14 @@ public class WebFluxStreamableServerTransportProvider implements McpStreamableSe
private KeepAliveScheduler keepAliveScheduler;
- private WebFluxStreamableServerTransportProvider(ObjectMapper objectMapper, String mcpEndpoint,
+ private WebFluxStreamableServerTransportProvider(McpJsonMapper jsonMapper, String mcpEndpoint,
McpTransportContextExtractor contextExtractor, boolean disallowDelete,
Duration keepAliveInterval) {
- Assert.notNull(objectMapper, "ObjectMapper must not be null");
+ Assert.notNull(jsonMapper, "JsonMapper must not be null");
Assert.notNull(mcpEndpoint, "Message endpoint must not be null");
Assert.notNull(contextExtractor, "Context extractor must not be null");
- this.objectMapper = objectMapper;
+ this.jsonMapper = jsonMapper;
this.mcpEndpoint = mcpEndpoint;
this.contextExtractor = contextExtractor;
this.disallowDelete = disallowDelete;
@@ -230,12 +230,13 @@ private Mono handlePost(ServerRequest request) {
return request.bodyToMono(String.class).flatMap(body -> {
try {
- McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper, body);
+ McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body);
if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest
&& jsonrpcRequest.method().equals(McpSchema.METHOD_INITIALIZE)) {
- McpSchema.InitializeRequest initializeRequest = objectMapper.convertValue(jsonrpcRequest.params(),
- new TypeReference() {
- });
+ var typeReference = new TypeRef() {
+ };
+ McpSchema.InitializeRequest initializeRequest = jsonMapper.convertValue(jsonrpcRequest.params(),
+ typeReference);
McpStreamableServerSession.McpStreamableServerSessionInit init = this.sessionFactory
.startSession(initializeRequest);
sessions.put(init.session().getId(), init.session());
@@ -243,7 +244,7 @@ private Mono handlePost(ServerRequest request) {
McpSchema.JSONRPCResponse jsonrpcResponse = new McpSchema.JSONRPCResponse(
McpSchema.JSONRPC_VERSION, jsonrpcRequest.id(), initializeResult, null);
try {
- return this.objectMapper.writeValueAsString(jsonrpcResponse);
+ return this.jsonMapper.writeValueAsString(jsonrpcResponse);
}
catch (IOException e) {
logger.warn("Failed to serialize initResponse", e);
@@ -349,7 +350,7 @@ public Mono sendMessage(McpSchema.JSONRPCMessage message) {
public Mono sendMessage(McpSchema.JSONRPCMessage message, String messageId) {
return Mono.fromSupplier(() -> {
try {
- return objectMapper.writeValueAsString(message);
+ return jsonMapper.writeValueAsString(message);
}
catch (IOException e) {
throw Exceptions.propagate(e);
@@ -369,8 +370,8 @@ public Mono sendMessage(McpSchema.JSONRPCMessage message, String messageId
}
@Override
- public T unmarshalFrom(Object data, TypeReference typeRef) {
- return objectMapper.convertValue(data, typeRef);
+ public T unmarshalFrom(Object data, TypeRef typeRef) {
+ return jsonMapper.convertValue(data, typeRef);
}
@Override
@@ -397,7 +398,7 @@ public static Builder builder() {
*/
public static class Builder {
- private ObjectMapper objectMapper;
+ private McpJsonMapper jsonMapper;
private String mcpEndpoint = "/mcp";
@@ -413,15 +414,15 @@ private Builder() {
}
/**
- * Sets the ObjectMapper to use for JSON serialization/deserialization of MCP
- * messages.
- * @param objectMapper The ObjectMapper instance. Must not be null.
+ * Sets the {@link McpJsonMapper} to use for JSON serialization/deserialization of
+ * MCP messages.
+ * @param jsonMapper The {@link McpJsonMapper} 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, "McpJsonMapper must not be null");
+ this.jsonMapper = jsonMapper;
return this;
}
@@ -482,13 +483,12 @@ public Builder keepAliveInterval(Duration keepAliveInterval) {
* @throws IllegalStateException if required parameters are not set
*/
public WebFluxStreamableServerTransportProvider build() {
- Assert.notNull(objectMapper, "ObjectMapper must be set");
Assert.notNull(mcpEndpoint, "Message endpoint must be set");
-
- return new WebFluxStreamableServerTransportProvider(objectMapper, mcpEndpoint, contextExtractor,
+ return new WebFluxStreamableServerTransportProvider(
+ jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, mcpEndpoint, contextExtractor,
disallowDelete, keepAliveInterval);
}
}
-}
\ No newline at end of file
+}
diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxSseIntegrationTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxSseIntegrationTests.java
index f8f0f7a3a..f580b59e8 100644
--- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxSseIntegrationTests.java
+++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxSseIntegrationTests.java
@@ -16,8 +16,6 @@
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport;
@@ -79,7 +77,6 @@ protected SingleSessionSyncSpecification prepareSyncServerBuilder() {
public void before() {
this.mcpServerTransportProvider = new WebFluxSseServerTransportProvider.Builder()
- .objectMapper(new ObjectMapper())
.messageEndpoint(CUSTOM_MESSAGE_ENDPOINT)
.sseEndpoint(CUSTOM_SSE_ENDPOINT)
.contextExtractor(TEST_CONTEXT_EXTRACTOR)
diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStatelessIntegrationTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStatelessIntegrationTests.java
index 5516e55b7..a00e24b55 100644
--- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStatelessIntegrationTests.java
+++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStatelessIntegrationTests.java
@@ -13,9 +13,6 @@
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.server.RouterFunctions;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
@@ -67,7 +64,6 @@ protected StatelessSyncSpecification prepareSyncServerBuilder() {
@BeforeEach
public void before() {
this.mcpStreamableServerTransport = WebFluxStatelessServerTransport.builder()
- .objectMapper(new ObjectMapper())
.messageEndpoint(CUSTOM_MESSAGE_ENDPOINT)
.build();
diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStreamableIntegrationTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStreamableIntegrationTests.java
index 933ddf39d..e4bcef829 100644
--- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStreamableIntegrationTests.java
+++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStreamableIntegrationTests.java
@@ -16,8 +16,6 @@
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
@@ -76,7 +74,6 @@ protected SyncSpecification> prepareSyncServerBuilder() {
public void before() {
this.mcpStreamableServerTransportProvider = WebFluxStreamableServerTransportProvider.builder()
- .objectMapper(new ObjectMapper())
.messageEndpoint(CUSTOM_MESSAGE_ENDPOINT)
.contextExtractor(TEST_CONTEXT_EXTRACTOR)
.build();
diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/WebFluxSseMcpSyncClientTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/WebFluxSseMcpSyncClientTests.java
index 804feb135..0f35f9f0d 100644
--- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/WebFluxSseMcpSyncClientTests.java
+++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/WebFluxSseMcpSyncClientTests.java
@@ -13,7 +13,6 @@
import org.junit.jupiter.api.Timeout;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
-
import org.springframework.web.reactive.function.client.WebClient;
/**
diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/transport/WebFluxSseClientTransportTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/transport/WebFluxSseClientTransportTests.java
index 06c95d145..3dacb62d8 100644
--- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/transport/WebFluxSseClientTransportTests.java
+++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/transport/WebFluxSseClientTransportTests.java
@@ -11,6 +11,8 @@
import java.util.function.Function;
import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.json.McpJsonMapper;
+import io.modelcontextprotocol.json.jackson.JacksonMcpJsonMapper;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest;
import org.junit.jupiter.api.AfterAll;
@@ -29,6 +31,7 @@
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.reactive.function.client.WebClient;
+import static io.modelcontextprotocol.utils.McpJsonMapperUtils.JSON_MAPPER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -54,8 +57,6 @@ class WebFluxSseClientTransportTests {
private WebClient.Builder webClientBuilder;
- private ObjectMapper objectMapper;
-
// Test class to access protected methods
static class TestSseClientTransport extends WebFluxSseClientTransport {
@@ -63,8 +64,8 @@ static class TestSseClientTransport extends WebFluxSseClientTransport {
private Sinks.Many> events = Sinks.many().unicast().onBackpressureBuffer();
- public TestSseClientTransport(WebClient.Builder webClientBuilder, ObjectMapper objectMapper) {
- super(webClientBuilder, objectMapper);
+ public TestSseClientTransport(WebClient.Builder webClientBuilder, McpJsonMapper jsonMapper) {
+ super(webClientBuilder, jsonMapper);
}
@Override
@@ -112,8 +113,7 @@ static void cleanup() {
@BeforeEach
void setUp() {
webClientBuilder = WebClient.builder().baseUrl(host);
- objectMapper = new ObjectMapper();
- transport = new TestSseClientTransport(webClientBuilder, objectMapper);
+ transport = new TestSseClientTransport(webClientBuilder, JSON_MAPPER);
transport.connect(Function.identity()).block();
}
@@ -131,12 +131,13 @@ void testEndpointEventHandling() {
@Test
void constructorValidation() {
- assertThatThrownBy(() -> new WebFluxSseClientTransport(null)).isInstanceOf(IllegalArgumentException.class)
+ assertThatThrownBy(() -> new WebFluxSseClientTransport(null, JSON_MAPPER))
+ .isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("WebClient.Builder must not be null");
assertThatThrownBy(() -> new WebFluxSseClientTransport(webClientBuilder, null))
.isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("ObjectMapper must not be null");
+ .hasMessageContaining("jsonMapper must not be null");
}
@Test
@@ -148,7 +149,7 @@ void testBuilderPattern() {
// Test builder with custom ObjectMapper
ObjectMapper customMapper = new ObjectMapper();
WebFluxSseClientTransport transport2 = WebFluxSseClientTransport.builder(webClientBuilder)
- .objectMapper(customMapper)
+ .jsonMapper(new JacksonMcpJsonMapper(customMapper))
.build();
assertThatCode(() -> transport2.closeGracefully().block()).doesNotThrowAnyException();
@@ -160,7 +161,6 @@ void testBuilderPattern() {
// Test builder with all custom parameters
WebFluxSseClientTransport transport4 = WebFluxSseClientTransport.builder(webClientBuilder)
- .objectMapper(customMapper)
.sseEndpoint("/custom-sse")
.build();
assertThatCode(() -> transport4.closeGracefully().block()).doesNotThrowAnyException();
diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java
index f3e2d3626..3db0bbd3a 100644
--- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java
+++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java
@@ -7,7 +7,6 @@
import java.util.Map;
import java.util.function.BiFunction;
-import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.client.McpAsyncClient;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
@@ -110,18 +109,15 @@ public class AsyncServerMcpTransportContextIntegrationTests {
// Server transports
private final WebFluxStatelessServerTransport statelessServerTransport = WebFluxStatelessServerTransport.builder()
- .objectMapper(new ObjectMapper())
.contextExtractor(serverContextExtractor)
.build();
private final WebFluxStreamableServerTransportProvider streamableServerTransport = WebFluxStreamableServerTransportProvider
.builder()
- .objectMapper(new ObjectMapper())
.contextExtractor(serverContextExtractor)
.build();
private final WebFluxSseServerTransportProvider sseServerTransport = WebFluxSseServerTransportProvider.builder()
- .objectMapper(new ObjectMapper())
.contextExtractor(serverContextExtractor)
.messageEndpoint("/mcp/message")
.build();
diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java
index 865192489..94e16e73e 100644
--- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java
+++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java
@@ -8,7 +8,6 @@
import java.util.function.BiFunction;
import java.util.function.Supplier;
-import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
@@ -105,18 +104,15 @@ public class SyncServerMcpTransportContextIntegrationTests {
};
private final WebFluxStatelessServerTransport statelessServerTransport = WebFluxStatelessServerTransport.builder()
- .objectMapper(new ObjectMapper())
.contextExtractor(serverContextExtractor)
.build();
private final WebFluxStreamableServerTransportProvider streamableServerTransport = WebFluxStreamableServerTransportProvider
.builder()
- .objectMapper(new ObjectMapper())
.contextExtractor(serverContextExtractor)
.build();
private final WebFluxSseServerTransportProvider sseServerTransport = WebFluxSseServerTransportProvider.builder()
- .objectMapper(new ObjectMapper())
.contextExtractor(serverContextExtractor)
.messageEndpoint("/mcp/message")
.build();
diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpAsyncServerTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpAsyncServerTests.java
index a3bdf10b0..fe0314687 100644
--- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpAsyncServerTests.java
+++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpAsyncServerTests.java
@@ -4,7 +4,6 @@
package io.modelcontextprotocol.server;
-import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.WebFluxSseServerTransportProvider;
import io.modelcontextprotocol.spec.McpServerTransportProvider;
import org.junit.jupiter.api.Timeout;
@@ -30,8 +29,7 @@ class WebFluxSseMcpAsyncServerTests extends AbstractMcpAsyncServerTests {
private DisposableServer httpServer;
private McpServerTransportProvider createMcpTransportProvider() {
- var transportProvider = new WebFluxSseServerTransportProvider.Builder().objectMapper(new ObjectMapper())
- .messageEndpoint(MESSAGE_ENDPOINT)
+ var transportProvider = new WebFluxSseServerTransportProvider.Builder().messageEndpoint(MESSAGE_ENDPOINT)
.build();
HttpHandler httpHandler = RouterFunctions.toHttpHandler(transportProvider.getRouterFunction());
diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpSyncServerTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpSyncServerTests.java
index 3e28e96b8..67ef90bdf 100644
--- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpSyncServerTests.java
+++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpSyncServerTests.java
@@ -4,7 +4,6 @@
package io.modelcontextprotocol.server;
-import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.WebFluxSseServerTransportProvider;
import io.modelcontextprotocol.spec.McpServerTransportProvider;
import org.junit.jupiter.api.Timeout;
@@ -37,9 +36,7 @@ protected McpServer.SyncSpecification> prepareSyncServerBuilder() {
}
private McpServerTransportProvider createMcpTransportProvider() {
- transportProvider = new WebFluxSseServerTransportProvider.Builder().objectMapper(new ObjectMapper())
- .messageEndpoint(MESSAGE_ENDPOINT)
- .build();
+ transportProvider = new WebFluxSseServerTransportProvider.Builder().messageEndpoint(MESSAGE_ENDPOINT).build();
return transportProvider;
}
diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxStreamableMcpAsyncServerTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxStreamableMcpAsyncServerTests.java
index 959f2f472..9b5a80f16 100644
--- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxStreamableMcpAsyncServerTests.java
+++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxStreamableMcpAsyncServerTests.java
@@ -4,7 +4,6 @@
package io.modelcontextprotocol.server;
-import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.WebFluxStreamableServerTransportProvider;
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
import org.junit.jupiter.api.Timeout;
@@ -32,7 +31,6 @@ class WebFluxStreamableMcpAsyncServerTests extends AbstractMcpAsyncServerTests {
private McpStreamableServerTransportProvider createMcpTransportProvider() {
var transportProvider = WebFluxStreamableServerTransportProvider.builder()
- .objectMapper(new ObjectMapper())
.messageEndpoint(MESSAGE_ENDPOINT)
.build();
diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxStreamableMcpSyncServerTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxStreamableMcpSyncServerTests.java
index 3396d489c..6a47ba3ae 100644
--- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxStreamableMcpSyncServerTests.java
+++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxStreamableMcpSyncServerTests.java
@@ -4,7 +4,6 @@
package io.modelcontextprotocol.server;
-import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.WebFluxStreamableServerTransportProvider;
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
import org.junit.jupiter.api.Timeout;
@@ -32,7 +31,6 @@ class WebFluxStreamableMcpSyncServerTests extends AbstractMcpSyncServerTests {
private McpStreamableServerTransportProvider createMcpTransportProvider() {
var transportProvider = WebFluxStreamableServerTransportProvider.builder()
- .objectMapper(new ObjectMapper())
.messageEndpoint(MESSAGE_ENDPOINT)
.build();
diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/utils/McpJsonMapperUtils.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/utils/McpJsonMapperUtils.java
new file mode 100644
index 000000000..67347573c
--- /dev/null
+++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/utils/McpJsonMapperUtils.java
@@ -0,0 +1,12 @@
+package io.modelcontextprotocol.utils;
+
+import io.modelcontextprotocol.json.McpJsonMapper;
+
+public final class McpJsonMapperUtils {
+
+ private McpJsonMapperUtils() {
+ }
+
+ public static final McpJsonMapper JSON_MAPPER = McpJsonMapper.createDefault();
+
+}
\ No newline at end of file
diff --git a/mcp-spring/mcp-spring-webmvc/pom.xml b/mcp-spring/mcp-spring-webmvc/pom.xml
index 4bd9f87aa..8c698487d 100644
--- a/mcp-spring/mcp-spring-webmvc/pom.xml
+++ b/mcp-spring/mcp-spring-webmvc/pom.xml
@@ -22,7 +22,13 @@
-
+
+ io.modelcontextprotocol.sdk
+ mcp-json-jackson2
+ 0.13.0-SNAPSHOT
+
+
+ io.modelcontextprotocol.sdkmcp0.13.0-SNAPSHOT
diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java
index 85373b6fe..0b71ddc1f 100644
--- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java
+++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java
@@ -11,8 +11,8 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
-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;
@@ -92,7 +92,7 @@ public class WebMvcSseServerTransportProvider implements McpServerTransportProvi
*/
public static final String DEFAULT_SSE_ENDPOINT = "/sse";
- private final ObjectMapper objectMapper;
+ private final McpJsonMapper jsonMapper;
private final String messageEndpoint;
@@ -118,85 +118,9 @@ public class WebMvcSseServerTransportProvider implements McpServerTransportProvi
private KeepAliveScheduler keepAliveScheduler;
- /**
- * Constructs a new WebMvcSseServerTransportProvider instance with the default SSE
- * endpoint.
- * @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
- * of messages.
- * @param messageEndpoint The endpoint URI where clients should send their JSON-RPC
- * messages via HTTP POST. This endpoint will be communicated to clients through the
- * SSE connection's initial endpoint event.
- * @throws IllegalArgumentException if either objectMapper or messageEndpoint is null
- * @deprecated Use the builder {@link #builder()} instead for better configuration
- * options.
- */
- @Deprecated
- public WebMvcSseServerTransportProvider(ObjectMapper objectMapper, String messageEndpoint) {
- this(objectMapper, messageEndpoint, DEFAULT_SSE_ENDPOINT);
- }
-
- /**
- * Constructs a new WebMvcSseServerTransportProvider instance.
- * @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
- * of messages.
- * @param messageEndpoint The endpoint URI where clients should send their JSON-RPC
- * messages via HTTP POST. This endpoint will be communicated to clients through the
- * SSE connection's initial endpoint event.
- * @param sseEndpoint The endpoint URI where clients establish their SSE connections.
- * @throws IllegalArgumentException if any parameter is null
- * @deprecated Use the builder {@link #builder()} instead for better configuration
- * options.
- */
- @Deprecated
- public WebMvcSseServerTransportProvider(ObjectMapper objectMapper, String messageEndpoint, String sseEndpoint) {
- this(objectMapper, "", messageEndpoint, sseEndpoint);
- }
-
- /**
- * Constructs a new WebMvcSseServerTransportProvider instance.
- * @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
- * of messages.
- * @param baseUrl The base URL for the message endpoint, used to construct the full
- * endpoint URL for clients.
- * @param messageEndpoint The endpoint URI where clients should send their JSON-RPC
- * messages via HTTP POST. This endpoint will be communicated to clients through the
- * SSE connection's initial endpoint event.
- * @param sseEndpoint The endpoint URI where clients establish their SSE connections.
- * @throws IllegalArgumentException if any parameter is null
- * @deprecated Use the builder {@link #builder()} instead for better configuration
- * options.
- */
- @Deprecated
- public WebMvcSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint,
- String sseEndpoint) {
- this(objectMapper, baseUrl, messageEndpoint, sseEndpoint, null);
- }
-
- /**
- * Constructs a new WebMvcSseServerTransportProvider instance.
- * @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
- * of messages.
- * @param baseUrl The base URL for the message endpoint, used to construct the full
- * endpoint URL for clients.
- * @param messageEndpoint The endpoint URI where clients should send their JSON-RPC
- * messages via HTTP POST. This endpoint will be communicated to clients through the
- * SSE connection's initial endpoint event.
- * @param sseEndpoint The endpoint URI where clients establish their SSE connections.
- * @param keepAliveInterval The interval for sending keep-alive messages to clients.
- * @throws IllegalArgumentException if any parameter is null
- * @deprecated Use the builder {@link #builder()} instead for better configuration
- * options.
- */
- @Deprecated
- public WebMvcSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint,
- String sseEndpoint, Duration keepAliveInterval) {
- this(objectMapper, baseUrl, messageEndpoint, sseEndpoint, keepAliveInterval,
- (serverRequest) -> McpTransportContext.EMPTY);
- }
-
/**
* Constructs a new WebMvcSseServerTransportProvider instance.
- * @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
+ * @param jsonMapper The McpJsonMapper to use for JSON serialization/deserialization
* of messages.
* @param baseUrl The base URL for the message endpoint, used to construct the full
* endpoint URL for clients.
@@ -209,16 +133,16 @@ public WebMvcSseServerTransportProvider(ObjectMapper objectMapper, String baseUr
* {@link McpTransportContext}.
* @throws IllegalArgumentException if any parameter is null
*/
- private WebMvcSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint,
+ private WebMvcSseServerTransportProvider(McpJsonMapper jsonMapper, String baseUrl, String messageEndpoint,
String sseEndpoint, Duration keepAliveInterval,
McpTransportContextExtractor contextExtractor) {
- Assert.notNull(objectMapper, "ObjectMapper must not be null");
+ Assert.notNull(jsonMapper, "McpJsonMapper must not be null");
Assert.notNull(baseUrl, "Message base URL must not be null");
Assert.notNull(messageEndpoint, "Message endpoint must not be null");
Assert.notNull(sseEndpoint, "SSE endpoint 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;
@@ -399,7 +323,7 @@ private ServerResponse handleMessage(ServerRequest request) {
final McpTransportContext transportContext = this.contextExtractor.extract(request);
String body = request.body(String.class);
- McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper, body);
+ McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body);
// Process the message through the session's handle method
session.handle(message).contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)).block(); // Block
@@ -456,7 +380,7 @@ public Mono sendMessage(McpSchema.JSONRPCMessage message) {
return Mono.fromRunnable(() -> {
sseBuilderLock.lock();
try {
- String jsonText = objectMapper.writeValueAsString(message);
+ String jsonText = jsonMapper.writeValueAsString(message);
sseBuilder.id(sessionId).event(MESSAGE_EVENT_TYPE).data(jsonText);
logger.debug("Message sent to session {}", sessionId);
}
@@ -471,15 +395,15 @@ public Mono sendMessage(McpSchema.JSONRPCMessage message) {
}
/**
- * Converts data from one type to another using the configured ObjectMapper.
+ * Converts data from one type to another using the configured McpJsonMapper.
* @param data The source data object to convert
* @param typeRef The target type reference
* @return The converted object of type T
* @param The target type
*/
@Override
- public T unmarshalFrom(Object data, TypeReference typeRef) {
- return objectMapper.convertValue(data, typeRef);
+ public T unmarshalFrom(Object data, TypeRef typeRef) {
+ return jsonMapper.convertValue(data, typeRef);
}
/**
@@ -541,7 +465,7 @@ public static Builder builder() {
*/
public static class Builder {
- private ObjectMapper objectMapper = new ObjectMapper();
+ private McpJsonMapper jsonMapper;
private String baseUrl = "";
@@ -556,12 +480,12 @@ public static class Builder {
/**
* Sets the JSON object mapper to use for message serialization/deserialization.
- * @param objectMapper The object mapper to use
+ * @param jsonMapper The object mapper 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, "McpJsonMapper must not be null");
+ this.jsonMapper = jsonMapper;
return this;
}
@@ -633,14 +557,14 @@ public Builder contextExtractor(McpTransportContextExtractor cont
* Builds a new instance of WebMvcSseServerTransportProvider with the configured
* settings.
* @return A new WebMvcSseServerTransportProvider instance
- * @throws IllegalStateException if objectMapper or messageEndpoint is not set
+ * @throws IllegalStateException if jsonMapper or messageEndpoint is not set
*/
public WebMvcSseServerTransportProvider build() {
if (messageEndpoint == null) {
throw new IllegalStateException("MessageEndpoint must be set");
}
- return new WebMvcSseServerTransportProvider(objectMapper, baseUrl, messageEndpoint, sseEndpoint,
- keepAliveInterval, contextExtractor);
+ return new WebMvcSseServerTransportProvider(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
+ baseUrl, messageEndpoint, sseEndpoint, keepAliveInterval, contextExtractor);
}
}
diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java
index fc2da0439..4223084ff 100644
--- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java
+++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java
@@ -4,8 +4,8 @@
package io.modelcontextprotocol.server.transport;
-import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.common.McpTransportContext;
+import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.server.McpStatelessServerHandler;
import io.modelcontextprotocol.server.McpTransportContextExtractor;
import io.modelcontextprotocol.spec.McpError;
@@ -38,7 +38,7 @@ public class WebMvcStatelessServerTransport implements McpStatelessServerTranspo
private static final Logger logger = LoggerFactory.getLogger(WebMvcStatelessServerTransport.class);
- private final ObjectMapper objectMapper;
+ private final McpJsonMapper jsonMapper;
private final String mcpEndpoint;
@@ -50,13 +50,13 @@ public class WebMvcStatelessServerTransport implements McpStatelessServerTranspo
private volatile boolean isClosing = false;
- private WebMvcStatelessServerTransport(ObjectMapper objectMapper, String mcpEndpoint,
+ private WebMvcStatelessServerTransport(McpJsonMapper jsonMapper, String mcpEndpoint,
McpTransportContextExtractor 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;
this.routerFunction = RouterFunctions.route()
@@ -110,7 +110,7 @@ private ServerResponse handlePost(ServerRequest request) {
try {
String body = request.body(String.class);
- McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper, body);
+ McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body);
if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
try {
@@ -171,7 +171,7 @@ public static Builder builder() {
*/
public static class Builder {
- private ObjectMapper objectMapper;
+ private McpJsonMapper jsonMapper;
private String mcpEndpoint = "/mcp";
@@ -185,13 +185,13 @@ private Builder() {
/**
* Sets the ObjectMapper to use for JSON serialization/deserialization of MCP
* messages.
- * @param objectMapper The ObjectMapper instance. Must not be null.
+ * @param jsonMapper The ObjectMapper 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, "ObjectMapper must not be null");
+ this.jsonMapper = jsonMapper;
return this;
}
@@ -230,10 +230,9 @@ public Builder contextExtractor(McpTransportContextExtractor cont
* @throws IllegalStateException if required parameters are not set
*/
public WebMvcStatelessServerTransport build() {
- Assert.notNull(objectMapper, "ObjectMapper must be set");
Assert.notNull(mcpEndpoint, "Message endpoint must be set");
-
- return new WebMvcStatelessServerTransport(objectMapper, mcpEndpoint, contextExtractor);
+ return new WebMvcStatelessServerTransport(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
+ mcpEndpoint, contextExtractor);
}
}
diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java
index 3cc104dd4..9bb9bfa86 100644
--- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java
+++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java
@@ -10,6 +10,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
+import io.modelcontextprotocol.json.McpJsonMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
@@ -20,8 +21,7 @@
import org.springframework.web.servlet.function.ServerResponse;
import org.springframework.web.servlet.function.ServerResponse.SseBuilder;
-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;
@@ -82,7 +82,7 @@ public class WebMvcStreamableServerTransportProvider implements McpStreamableSer
*/
private final boolean disallowDelete;
- private final ObjectMapper objectMapper;
+ private final McpJsonMapper jsonMapper;
private final RouterFunction routerFunction;
@@ -104,7 +104,7 @@ public class WebMvcStreamableServerTransportProvider implements McpStreamableSer
/**
* Constructs a new WebMvcStreamableServerTransportProvider instance.
- * @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
+ * @param jsonMapper The McpJsonMapper to use for JSON serialization/deserialization
* of messages.
* @param baseUrl The base URL for the message endpoint, used to construct the full
* endpoint URL for clients.
@@ -113,14 +113,14 @@ public class WebMvcStreamableServerTransportProvider implements McpStreamableSer
* @param disallowDelete Whether to disallow DELETE requests on the endpoint.
* @throws IllegalArgumentException if any parameter is null
*/
- private WebMvcStreamableServerTransportProvider(ObjectMapper objectMapper, String mcpEndpoint,
+ private WebMvcStreamableServerTransportProvider(McpJsonMapper jsonMapper, String mcpEndpoint,
boolean disallowDelete, McpTransportContextExtractor contextExtractor,
Duration keepAliveInterval) {
- Assert.notNull(objectMapper, "ObjectMapper must not be null");
+ Assert.notNull(jsonMapper, "McpJsonMapper must not be null");
Assert.notNull(mcpEndpoint, "MCP endpoint must not be null");
Assert.notNull(contextExtractor, "McpTransportContextExtractor must not be null");
- this.objectMapper = objectMapper;
+ this.jsonMapper = jsonMapper;
this.mcpEndpoint = mcpEndpoint;
this.disallowDelete = disallowDelete;
this.contextExtractor = contextExtractor;
@@ -325,13 +325,13 @@ private ServerResponse handlePost(ServerRequest request) {
try {
String body = request.body(String.class);
- McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper, body);
+ McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body);
// Handle initialization request
if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest
&& jsonrpcRequest.method().equals(McpSchema.METHOD_INITIALIZE)) {
- McpSchema.InitializeRequest initializeRequest = objectMapper.convertValue(jsonrpcRequest.params(),
- new TypeReference() {
+ McpSchema.InitializeRequest initializeRequest = jsonMapper.convertValue(jsonrpcRequest.params(),
+ new TypeRef() {
});
McpStreamableServerSession.McpStreamableServerSessionInit init = this.sessionFactory
.startSession(initializeRequest);
@@ -516,7 +516,7 @@ public Mono sendMessage(McpSchema.JSONRPCMessage message, String messageId
return;
}
- String jsonText = objectMapper.writeValueAsString(message);
+ String jsonText = jsonMapper.writeValueAsString(message);
this.sseBuilder.id(messageId != null ? messageId : this.sessionId)
.event(MESSAGE_EVENT_TYPE)
.data(jsonText);
@@ -539,15 +539,15 @@ public Mono sendMessage(McpSchema.JSONRPCMessage message, String messageId
}
/**
- * Converts data from one type to another using the configured ObjectMapper.
+ * Converts data from one type to another using the configured McpJsonMapper.
* @param data The source data object to convert
* @param typeRef The target type reference
* @return The converted object of type T
* @param The target type
*/
@Override
- public T unmarshalFrom(Object data, TypeReference typeRef) {
- return objectMapper.convertValue(data, typeRef);
+ public T unmarshalFrom(Object data, TypeRef typeRef) {
+ return jsonMapper.convertValue(data, typeRef);
}
/**
@@ -597,7 +597,7 @@ public static Builder builder() {
*/
public static class Builder {
- private ObjectMapper objectMapper;
+ private McpJsonMapper jsonMapper;
private String mcpEndpoint = "/mcp";
@@ -609,15 +609,15 @@ public static class Builder {
private Duration keepAliveInterval;
/**
- * Sets the ObjectMapper to use for JSON serialization/deserialization of MCP
+ * Sets the McpJsonMapper to use for JSON serialization/deserialization of MCP
* messages.
- * @param objectMapper The ObjectMapper instance. Must not be null.
+ * @param jsonMapper The McpJsonMapper 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, "McpJsonMapper must not be null");
+ this.jsonMapper = jsonMapper;
return this;
}
@@ -678,11 +678,10 @@ public Builder keepAliveInterval(Duration keepAliveInterval) {
* @throws IllegalStateException if required parameters are not set
*/
public WebMvcStreamableServerTransportProvider build() {
- Assert.notNull(this.objectMapper, "ObjectMapper must be set");
Assert.notNull(this.mcpEndpoint, "MCP endpoint must be set");
-
- return new WebMvcStreamableServerTransportProvider(this.objectMapper, this.mcpEndpoint, this.disallowDelete,
- this.contextExtractor, this.keepAliveInterval);
+ return new WebMvcStreamableServerTransportProvider(
+ jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, mcpEndpoint, disallowDelete,
+ contextExtractor, keepAliveInterval);
}
}
diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/common/McpTransportContextIntegrationTests.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/common/McpTransportContextIntegrationTests.java
index 1f5f1cc0c..cc9945436 100644
--- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/common/McpTransportContextIntegrationTests.java
+++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/common/McpTransportContextIntegrationTests.java
@@ -8,7 +8,6 @@
import java.util.function.BiFunction;
import java.util.function.Supplier;
-import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
@@ -40,7 +39,6 @@
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
-
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -223,10 +221,7 @@ static class TestStatelessConfig {
@Bean
public WebMvcStatelessServerTransport webMvcStatelessServerTransport() {
- return WebMvcStatelessServerTransport.builder()
- .objectMapper(new ObjectMapper())
- .contextExtractor(serverContextExtractor)
- .build();
+ return WebMvcStatelessServerTransport.builder().contextExtractor(serverContextExtractor).build();
}
@Bean
@@ -251,10 +246,7 @@ static class TestStreamableHttpConfig {
@Bean
public WebMvcStreamableServerTransportProvider webMvcStreamableServerTransport() {
- return WebMvcStreamableServerTransportProvider.builder()
- .objectMapper(new ObjectMapper())
- .contextExtractor(serverContextExtractor)
- .build();
+ return WebMvcStreamableServerTransportProvider.builder().contextExtractor(serverContextExtractor).build();
}
@Bean
@@ -281,7 +273,6 @@ static class TestSseConfig {
public WebMvcSseServerTransportProvider webMvcSseServerTransport() {
return WebMvcSseServerTransportProvider.builder()
- .objectMapper(new ObjectMapper())
.contextExtractor(serverContextExtractor)
.messageEndpoint("/mcp/message")
.build();
diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMcpStreamableAsyncServerTransportTests.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMcpStreamableAsyncServerTransportTests.java
index 66349216d..ae1f4f4d1 100644
--- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMcpStreamableAsyncServerTransportTests.java
+++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMcpStreamableAsyncServerTransportTests.java
@@ -16,8 +16,6 @@
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
import io.modelcontextprotocol.server.transport.WebMvcStreamableServerTransportProvider;
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
import reactor.netty.DisposableServer;
@@ -48,10 +46,7 @@ static class TestConfig {
@Bean
public WebMvcStreamableServerTransportProvider webMvcSseServerTransportProvider() {
- return WebMvcStreamableServerTransportProvider.builder()
- .objectMapper(new ObjectMapper())
- .mcpEndpoint(MCP_ENDPOINT)
- .build();
+ return WebMvcStreamableServerTransportProvider.builder().mcpEndpoint(MCP_ENDPOINT).build();
}
@Bean
diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMcpStreamableSyncServerTransportTests.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMcpStreamableSyncServerTransportTests.java
index cab487f12..c8c24b8a7 100644
--- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMcpStreamableSyncServerTransportTests.java
+++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMcpStreamableSyncServerTransportTests.java
@@ -16,8 +16,6 @@
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
import io.modelcontextprotocol.server.transport.WebMvcStreamableServerTransportProvider;
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
import reactor.netty.DisposableServer;
@@ -48,10 +46,7 @@ static class TestConfig {
@Bean
public WebMvcStreamableServerTransportProvider webMvcSseServerTransportProvider() {
- return WebMvcStreamableServerTransportProvider.builder()
- .objectMapper(new ObjectMapper())
- .mcpEndpoint(MCP_ENDPOINT)
- .build();
+ return WebMvcStreamableServerTransportProvider.builder().mcpEndpoint(MCP_ENDPOINT).build();
}
@Bean
diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseAsyncServerTransportTests.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseAsyncServerTransportTests.java
index bb4c2bf37..ccf3170c9 100644
--- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseAsyncServerTransportTests.java
+++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseAsyncServerTransportTests.java
@@ -4,7 +4,6 @@
package io.modelcontextprotocol.server;
-import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider;
import io.modelcontextprotocol.spec.McpServerTransportProvider;
import org.apache.catalina.Context;
@@ -37,7 +36,10 @@ static class TestConfig {
@Bean
public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider() {
- return new WebMvcSseServerTransportProvider(new ObjectMapper(), MESSAGE_ENDPOINT);
+ return WebMvcSseServerTransportProvider.builder()
+ .messageEndpoint(MESSAGE_ENDPOINT)
+ .sseEndpoint(WebMvcSseServerTransportProvider.DEFAULT_SSE_ENDPOINT)
+ .build();
}
@Bean
diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseCustomContextPathTests.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseCustomContextPathTests.java
index cce36d191..d8d26af48 100644
--- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseCustomContextPathTests.java
+++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseCustomContextPathTests.java
@@ -3,7 +3,6 @@
*/
package io.modelcontextprotocol.server;
-import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider;
@@ -92,7 +91,6 @@ static class TestConfig {
public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider() {
return WebMvcSseServerTransportProvider.builder()
- .objectMapper(new ObjectMapper())
.baseUrl(CUSTOM_CONTEXT_PATH)
.messageEndpoint(MESSAGE_ENDPOINT)
.sseEndpoint(WebMvcSseServerTransportProvider.DEFAULT_SSE_ENDPOINT)
diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseIntegrationTests.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseIntegrationTests.java
index 18a9d0063..e780b8e51 100644
--- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseIntegrationTests.java
+++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseIntegrationTests.java
@@ -21,8 +21,6 @@
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
import io.modelcontextprotocol.AbstractMcpClientServerIntegrationTests;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
@@ -64,7 +62,6 @@ static class TestConfig {
@Bean
public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider() {
return WebMvcSseServerTransportProvider.builder()
- .objectMapper(new ObjectMapper())
.messageEndpoint(MESSAGE_ENDPOINT)
.contextExtractor(TEST_CONTEXT_EXTRACTOR)
.build();
diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseSyncServerTransportTests.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseSyncServerTransportTests.java
index 101a067ad..66d6d3ae9 100644
--- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseSyncServerTransportTests.java
+++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseSyncServerTransportTests.java
@@ -4,7 +4,6 @@
package io.modelcontextprotocol.server;
-import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
@@ -36,10 +35,7 @@ static class TestConfig {
@Bean
public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider() {
- return WebMvcSseServerTransportProvider.builder()
- .objectMapper(new ObjectMapper())
- .messageEndpoint(MESSAGE_ENDPOINT)
- .build();
+ return WebMvcSseServerTransportProvider.builder().messageEndpoint(MESSAGE_ENDPOINT).build();
}
@Bean
diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcStatelessIntegrationTests.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcStatelessIntegrationTests.java
index c7c1e710d..9633dfbd1 100644
--- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcStatelessIntegrationTests.java
+++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcStatelessIntegrationTests.java
@@ -19,8 +19,6 @@
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
import io.modelcontextprotocol.AbstractStatelessIntegrationTests;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
@@ -46,10 +44,7 @@ static class TestConfig {
@Bean
public WebMvcStatelessServerTransport webMvcStatelessServerTransport() {
- return WebMvcStatelessServerTransport.builder()
- .objectMapper(new ObjectMapper())
- .messageEndpoint(MESSAGE_ENDPOINT)
- .build();
+ return WebMvcStatelessServerTransport.builder().messageEndpoint(MESSAGE_ENDPOINT).build();
}
diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcStreamableIntegrationTests.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcStreamableIntegrationTests.java
index 3f1716f89..abdd82967 100644
--- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcStreamableIntegrationTests.java
+++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcStreamableIntegrationTests.java
@@ -21,8 +21,6 @@
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
import io.modelcontextprotocol.AbstractMcpClientServerIntegrationTests;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
@@ -52,7 +50,6 @@ static class TestConfig {
@Bean
public WebMvcStreamableServerTransportProvider webMvcStreamableServerTransportProvider() {
return WebMvcStreamableServerTransportProvider.builder()
- .objectMapper(new ObjectMapper())
.contextExtractor(TEST_CONTEXT_EXTRACTOR)
.mcpEndpoint(MESSAGE_ENDPOINT)
.build();
diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java
index dd3bc59da..923a7ee36 100644
--- a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java
+++ b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java
@@ -54,6 +54,7 @@
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
+import static io.modelcontextprotocol.utils.ToolsUtils.EMPTY_JSON_SCHEMA;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
import static org.assertj.core.api.Assertions.assertThat;
@@ -81,7 +82,6 @@ void simple(String clientType) {
var server = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
.requestTimeout(Duration.ofSeconds(1000))
.build();
-
try (
// Create client without sampling capabilities
var client = clientBuilder.clientInfo(new McpSchema.Implementation("Sample " + "client", "0.0.0"))
@@ -106,7 +106,7 @@ void testCreateMessageWithoutSamplingCapabilities(String clientType) {
var clientBuilder = clientBuilders.get(clientType);
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
- .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build())
+ .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
return exchange.createMessage(mock(McpSchema.CreateMessageRequest.class))
.then(Mono.just(mock(CallToolResult.class)));
@@ -155,7 +155,7 @@ void testCreateMessageSuccess(String clientType) {
AtomicReference samplingResult = new AtomicReference<>();
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
- .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build())
+ .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
var createMessageRequest = McpSchema.CreateMessageRequest.builder()
@@ -233,7 +233,7 @@ void testCreateMessageWithRequestTimeoutSuccess(String clientType) throws Interr
AtomicReference samplingResult = new AtomicReference<>();
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
- .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build())
+ .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
var createMessageRequest = McpSchema.CreateMessageRequest.builder()
@@ -307,7 +307,7 @@ void testCreateMessageWithRequestTimeoutFail(String clientType) throws Interrupt
null);
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
- .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build())
+ .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
var createMessageRequest = McpSchema.CreateMessageRequest.builder()
@@ -357,7 +357,7 @@ void testCreateElicitationWithoutElicitationCapabilities(String clientType) {
var clientBuilder = clientBuilders.get(clientType);
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
- .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build())
+ .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> exchange.createElicitation(mock(ElicitRequest.class))
.then(Mono.just(mock(CallToolResult.class))))
.build();
@@ -400,7 +400,7 @@ void testCreateElicitationSuccess(String clientType) {
null);
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
- .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build())
+ .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
var elicitationRequest = McpSchema.ElicitRequest.builder()
@@ -457,7 +457,7 @@ void testCreateElicitationWithRequestTimeoutSuccess(String clientType) {
AtomicReference resultRef = new AtomicReference<>();
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
- .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build())
+ .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
var elicitationRequest = McpSchema.ElicitRequest.builder()
@@ -528,7 +528,7 @@ void testCreateElicitationWithRequestTimeoutFail(String clientType) {
AtomicReference resultRef = new AtomicReference<>();
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
- .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build())
+ .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
var elicitationRequest = ElicitRequest.builder()
@@ -626,7 +626,7 @@ void testRootsWithoutCapability(String clientType) {
var clientBuilder = clientBuilders.get(clientType);
McpServerFeatures.SyncToolSpecification tool = McpServerFeatures.SyncToolSpecification.builder()
- .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build())
+ .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
exchange.listRoots(); // try to list roots
@@ -757,14 +757,6 @@ void testRootsServerCloseWithActiveSubscription(String clientType) {
// ---------------------------------------
// Tools Tests
// ---------------------------------------
- String emptyJsonSchema = """
- {
- "$schema": "http://json-schema.org/draft-07/schema#",
- "type": "object",
- "properties": {}
- }
- """;
-
@ParameterizedTest(name = "{0} : {displayName} ")
@ValueSource(strings = { "httpclient", "webflux" })
void testToolCallSuccess(String clientType) {
@@ -774,7 +766,7 @@ void testToolCallSuccess(String clientType) {
var responseBodyIsNullOrBlank = new AtomicBoolean(false);
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
- .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build())
+ .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
try {
@@ -828,7 +820,7 @@ void testThrowingToolCallIsCaughtBeforeTimeout(String clientType) {
.tool(Tool.builder()
.name("tool1")
.description("tool1 description")
- .inputSchema(emptyJsonSchema)
+ .inputSchema(EMPTY_JSON_SCHEMA)
.build())
.callHandler((exchange, request) -> {
// We trigger a timeout on blocking read, raising an exception
@@ -867,7 +859,7 @@ void testToolCallSuccessWithTranportContextExtraction(String clientType) {
var expectedCallResponse = new McpSchema.CallToolResult(
List.of(new McpSchema.TextContent("CALL RESPONSE; ctx=value")), null);
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
- .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build())
+ .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
McpTransportContext transportContext = exchange.transportContext();
@@ -919,7 +911,7 @@ void testToolListChangeHandlingSuccess(String clientType) {
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
- .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build())
+ .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
// perform a blocking call to a remote service
try {
@@ -988,7 +980,7 @@ void testToolListChangeHandlingSuccess(String clientType) {
.tool(Tool.builder()
.name("tool2")
.description("tool2 description")
- .inputSchema(emptyJsonSchema)
+ .inputSchema(EMPTY_JSON_SCHEMA)
.build())
.callHandler((exchange, request) -> callResponse)
.build();
@@ -1040,7 +1032,7 @@ void testLoggingNotification(String clientType) throws InterruptedException {
.tool(Tool.builder()
.name("logging-test")
.description("Test logging notifications")
- .inputSchema(emptyJsonSchema)
+ .inputSchema(EMPTY_JSON_SCHEMA)
.build())
.callHandler((exchange, request) -> {
@@ -1154,7 +1146,7 @@ void testProgressNotification(String clientType) throws InterruptedException {
.tool(McpSchema.Tool.builder()
.name("progress-test")
.description("Test progress notifications")
- .inputSchema(emptyJsonSchema)
+ .inputSchema(EMPTY_JSON_SCHEMA)
.build())
.callHandler((exchange, request) -> {
@@ -1308,7 +1300,7 @@ void testPingSuccess(String clientType) {
.tool(Tool.builder()
.name("ping-async-test")
.description("Test ping async behavior")
- .inputSchema(emptyJsonSchema)
+ .inputSchema(EMPTY_JSON_SCHEMA)
.build())
.callHandler((exchange, request) -> {
diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java
index c96f10eda..e17c01f90 100644
--- a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java
+++ b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java
@@ -31,6 +31,7 @@
import org.junit.jupiter.params.provider.ValueSource;
import reactor.core.publisher.Mono;
+import static io.modelcontextprotocol.utils.ToolsUtils.EMPTY_JSON_SCHEMA;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
import static org.assertj.core.api.Assertions.assertThat;
@@ -74,15 +75,6 @@ void simple(String clientType) {
// ---------------------------------------
// Tools Tests
// ---------------------------------------
-
- String emptyJsonSchema = """
- {
- "$schema": "http://json-schema.org/draft-07/schema#",
- "type": "object",
- "properties": {}
- }
- """;
-
@ParameterizedTest(name = "{0} : {displayName} ")
@ValueSource(strings = { "httpclient", "webflux" })
void testToolCallSuccess(String clientType) {
@@ -92,7 +84,7 @@ void testToolCallSuccess(String clientType) {
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
McpStatelessServerFeatures.SyncToolSpecification tool1 = McpStatelessServerFeatures.SyncToolSpecification
.builder()
- .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build())
+ .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((ctx, request) -> {
try {
@@ -145,7 +137,7 @@ void testThrowingToolCallIsCaughtBeforeTimeout(String clientType) {
.tool(Tool.builder()
.name("tool1")
.description("tool1 description")
- .inputSchema(emptyJsonSchema)
+ .inputSchema(EMPTY_JSON_SCHEMA)
.build())
.callHandler((context, request) -> {
// We trigger a timeout on blocking read, raising an exception
@@ -180,7 +172,7 @@ void testToolListChangeHandlingSuccess(String clientType) {
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
McpStatelessServerFeatures.SyncToolSpecification tool1 = McpStatelessServerFeatures.SyncToolSpecification
.builder()
- .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build())
+ .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((ctx, request) -> {
// perform a blocking call to a remote service
try {
@@ -241,7 +233,7 @@ void testToolListChangeHandlingSuccess(String clientType) {
.tool(Tool.builder()
.name("tool2")
.description("tool2 description")
- .inputSchema(emptyJsonSchema)
+ .inputSchema(EMPTY_JSON_SCHEMA)
.build())
.callHandler((exchange, request) -> callResponse)
.build();
diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/MockMcpTransport.java b/mcp-test/src/main/java/io/modelcontextprotocol/MockMcpTransport.java
index 5484a63c2..cd8458311 100644
--- a/mcp-test/src/main/java/io/modelcontextprotocol/MockMcpTransport.java
+++ b/mcp-test/src/main/java/io/modelcontextprotocol/MockMcpTransport.java
@@ -9,8 +9,8 @@
import java.util.function.BiConsumer;
import java.util.function.Function;
-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.spec.McpClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.JSONRPCNotification;
@@ -93,8 +93,8 @@ public Mono closeGracefully() {
}
@Override
- public T unmarshalFrom(Object data, TypeReference typeRef) {
- return new ObjectMapper().convertValue(data, typeRef);
+ public T unmarshalFrom(Object data, TypeRef typeRef) {
+ return McpJsonMapper.getDefault().convertValue(data, typeRef);
}
}
diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java
index 8902a53b3..8a0b3e0d9 100644
--- a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java
+++ b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java
@@ -4,6 +4,7 @@
package io.modelcontextprotocol.client;
+import static io.modelcontextprotocol.utils.McpJsonMapperUtils.JSON_MAPPER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -22,8 +23,7 @@
import java.util.function.Consumer;
import java.util.function.Function;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
+import io.modelcontextprotocol.json.McpJsonMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@@ -176,7 +176,12 @@ void testListAllToolsReturnsImmutableList() {
.consumeNextWith(result -> {
assertThat(result.tools()).isNotNull();
// Verify that the returned list is immutable
- assertThatThrownBy(() -> result.tools().add(new Tool("test", "test", "{\"type\":\"object\"}")))
+ assertThatThrownBy(() -> result.tools()
+ .add(Tool.builder()
+ .name("test")
+ .title("test")
+ .inputSchema(JSON_MAPPER, "{\"type\":\"object\"}")
+ .build()))
.isInstanceOf(UnsupportedOperationException.class);
})
.verifyComplete();
diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java
index 8eb6ec248..e1ffd2c75 100644
--- a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java
+++ b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java
@@ -22,8 +22,6 @@
import java.util.function.Consumer;
import java.util.function.Function;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java
index 1e87d4420..b0701911a 100644
--- a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java
+++ b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java
@@ -26,6 +26,7 @@
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
+import static io.modelcontextprotocol.utils.ToolsUtils.EMPTY_JSON_SCHEMA;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -95,18 +96,10 @@ void testImmediateClose() {
// ---------------------------------------
// Tools Tests
// ---------------------------------------
- String emptyJsonSchema = """
- {
- "$schema": "http://json-schema.org/draft-07/schema#",
- "type": "object",
- "properties": {}
- }
- """;
-
@Test
@Deprecated
void testAddTool() {
- Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
+ Tool newTool = Tool.builder().name("new-tool").title("New test tool").inputSchema(EMPTY_JSON_SCHEMA).build();
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
.build();
@@ -120,7 +113,7 @@ void testAddTool() {
@Test
void testAddToolCall() {
- Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
+ Tool newTool = Tool.builder().name("new-tool").title("New test tool").inputSchema(EMPTY_JSON_SCHEMA).build();
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
.build();
@@ -136,7 +129,11 @@ void testAddToolCall() {
@Test
@Deprecated
void testAddDuplicateTool() {
- Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
+ Tool duplicateTool = McpSchema.Tool.builder()
+ .name(TEST_TOOL_NAME)
+ .title("Duplicate tool")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build();
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
@@ -156,7 +153,11 @@ void testAddDuplicateTool() {
@Test
void testAddDuplicateToolCall() {
- Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
+ Tool duplicateTool = McpSchema.Tool.builder()
+ .name(TEST_TOOL_NAME)
+ .title("Duplicate tool")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build();
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
@@ -176,8 +177,11 @@ void testAddDuplicateToolCall() {
@Test
void testDuplicateToolCallDuringBuilding() {
- Tool duplicateTool = new Tool("duplicate-build-toolcall", "Duplicate toolcall during building",
- emptyJsonSchema);
+ Tool duplicateTool = McpSchema.Tool.builder()
+ .name("duplicate-build-toolcall")
+ .title("Duplicate toolcall during building")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build();
assertThatThrownBy(() -> prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
@@ -189,7 +193,11 @@ void testDuplicateToolCallDuringBuilding() {
@Test
void testDuplicateToolsInBatchListRegistration() {
- Tool duplicateTool = new Tool("batch-list-tool", "Duplicate tool in batch list", emptyJsonSchema);
+ Tool duplicateTool = McpSchema.Tool.builder()
+ .name("batch-list-tool")
+ .title("Duplicate tool in batch list")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build();
List specs = List.of(
McpServerFeatures.AsyncToolSpecification.builder()
.tool(duplicateTool)
@@ -210,7 +218,11 @@ void testDuplicateToolsInBatchListRegistration() {
@Test
void testDuplicateToolsInBatchVarargsRegistration() {
- Tool duplicateTool = new Tool("batch-varargs-tool", "Duplicate tool in batch varargs", emptyJsonSchema);
+ Tool duplicateTool = McpSchema.Tool.builder()
+ .name("batch-varargs-tool")
+ .title("Duplicate tool in batch varargs")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build();
assertThatThrownBy(() -> prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
@@ -229,8 +241,11 @@ void testDuplicateToolsInBatchVarargsRegistration() {
@Test
void testRemoveTool() {
- Tool too = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
-
+ Tool too = McpSchema.Tool.builder()
+ .name(TEST_TOOL_NAME)
+ .title("Duplicate tool")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build();
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
.toolCall(too, (exchange, request) -> Mono.just(new CallToolResult(List.of(), false)))
@@ -256,7 +271,11 @@ void testRemoveNonexistentTool() {
@Test
void testNotifyToolsListChanged() {
- Tool too = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
+ Tool too = McpSchema.Tool.builder()
+ .name(TEST_TOOL_NAME)
+ .title("Duplicate tool")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build();
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java
index 5d70ae4c0..d804de43b 100644
--- a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java
+++ b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java
@@ -4,6 +4,7 @@
package io.modelcontextprotocol.server;
+import static io.modelcontextprotocol.utils.ToolsUtils.EMPTY_JSON_SCHEMA;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -99,15 +100,6 @@ void testGetAsyncServer() {
// ---------------------------------------
// Tools Tests
// ---------------------------------------
-
- String emptyJsonSchema = """
- {
- "$schema": "http://json-schema.org/draft-07/schema#",
- "type": "object",
- "properties": {}
- }
- """;
-
@Test
@Deprecated
void testAddTool() {
@@ -115,7 +107,7 @@ void testAddTool() {
.capabilities(ServerCapabilities.builder().tools(true).build())
.build();
- Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
+ Tool newTool = Tool.builder().name("new-tool").title("New test tool").inputSchema(EMPTY_JSON_SCHEMA).build();
assertThatCode(() -> mcpSyncServer.addTool(new McpServerFeatures.SyncToolSpecification(newTool,
(exchange, args) -> new CallToolResult(List.of(), false))))
.doesNotThrowAnyException();
@@ -129,7 +121,7 @@ void testAddToolCall() {
.capabilities(ServerCapabilities.builder().tools(true).build())
.build();
- Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
+ Tool newTool = Tool.builder().name("new-tool").title("New test tool").inputSchema(EMPTY_JSON_SCHEMA).build();
assertThatCode(() -> mcpSyncServer.addTool(McpServerFeatures.SyncToolSpecification.builder()
.tool(newTool)
.callHandler((exchange, request) -> new CallToolResult(List.of(), false))
@@ -141,7 +133,11 @@ void testAddToolCall() {
@Test
@Deprecated
void testAddDuplicateTool() {
- Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
+ Tool duplicateTool = Tool.builder()
+ .name(TEST_TOOL_NAME)
+ .title("Duplicate tool")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build();
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
@@ -158,7 +154,11 @@ void testAddDuplicateTool() {
@Test
void testAddDuplicateToolCall() {
- Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
+ Tool duplicateTool = Tool.builder()
+ .name(TEST_TOOL_NAME)
+ .title("Duplicate tool")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build();
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
@@ -176,8 +176,11 @@ void testAddDuplicateToolCall() {
@Test
void testDuplicateToolCallDuringBuilding() {
- Tool duplicateTool = new Tool("duplicate-build-toolcall", "Duplicate toolcall during building",
- emptyJsonSchema);
+ Tool duplicateTool = Tool.builder()
+ .name("duplicate-build-toolcall")
+ .title("Duplicate toolcall during building")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build();
assertThatThrownBy(() -> prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
@@ -189,7 +192,11 @@ void testDuplicateToolCallDuringBuilding() {
@Test
void testDuplicateToolsInBatchListRegistration() {
- Tool duplicateTool = new Tool("batch-list-tool", "Duplicate tool in batch list", emptyJsonSchema);
+ Tool duplicateTool = Tool.builder()
+ .name("batch-list-tool")
+ .title("Duplicate tool in batch list")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build();
List specs = List.of(
McpServerFeatures.SyncToolSpecification.builder()
.tool(duplicateTool)
@@ -210,7 +217,11 @@ void testDuplicateToolsInBatchListRegistration() {
@Test
void testDuplicateToolsInBatchVarargsRegistration() {
- Tool duplicateTool = new Tool("batch-varargs-tool", "Duplicate tool in batch varargs", emptyJsonSchema);
+ Tool duplicateTool = Tool.builder()
+ .name("batch-varargs-tool")
+ .title("Duplicate tool in batch varargs")
+ .inputSchema(EMPTY_JSON_SCHEMA)
+ .build();
assertThatThrownBy(() -> prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
@@ -229,7 +240,7 @@ void testDuplicateToolsInBatchVarargsRegistration() {
@Test
void testRemoveTool() {
- Tool tool = new McpSchema.Tool(TEST_TOOL_NAME, "Test tool", emptyJsonSchema);
+ Tool tool = Tool.builder().name(TEST_TOOL_NAME).title("Test tool").inputSchema(EMPTY_JSON_SCHEMA).build();
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/utils/McpJsonMapperUtils.java b/mcp-test/src/main/java/io/modelcontextprotocol/utils/McpJsonMapperUtils.java
new file mode 100644
index 000000000..e9ec8900c
--- /dev/null
+++ b/mcp-test/src/main/java/io/modelcontextprotocol/utils/McpJsonMapperUtils.java
@@ -0,0 +1,12 @@
+package io.modelcontextprotocol.utils;
+
+import io.modelcontextprotocol.json.McpJsonMapper;
+
+public final class McpJsonMapperUtils {
+
+ private McpJsonMapperUtils() {
+ }
+
+ public static final McpJsonMapper JSON_MAPPER = McpJsonMapper.getDefault();
+
+}
\ No newline at end of file
diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/utils/ToolsUtils.java b/mcp-test/src/main/java/io/modelcontextprotocol/utils/ToolsUtils.java
new file mode 100644
index 000000000..ec603aac1
--- /dev/null
+++ b/mcp-test/src/main/java/io/modelcontextprotocol/utils/ToolsUtils.java
@@ -0,0 +1,15 @@
+package io.modelcontextprotocol.utils;
+
+import io.modelcontextprotocol.spec.McpSchema;
+
+import java.util.Collections;
+
+public final class ToolsUtils {
+
+ private ToolsUtils() {
+ }
+
+ public static final McpSchema.JsonSchema EMPTY_JSON_SCHEMA = new McpSchema.JsonSchema("object",
+ Collections.emptyMap(), null, null, null, null);
+
+}
diff --git a/mcp/pom.xml b/mcp/pom.xml
index dc85a419e..6ba402a4d 100644
--- a/mcp/pom.xml
+++ b/mcp/pom.xml
@@ -65,6 +65,11 @@
+
+ io.modelcontextprotocol.sdk
+ mcp-json-jackson2
+ 0.13.0-SNAPSHOT
+ org.slf4j
@@ -74,7 +79,7 @@
com.fasterxml.jackson.core
- jackson-databind
+ jackson-annotations${jackson.version}
@@ -83,11 +88,6 @@
reactor-core
-
- com.networknt
- json-schema-validator
- ${json-schema-validator.version}
-
@@ -216,8 +216,14 @@
test
-
+
+
+ com.google.code.gson
+ gson
+ 2.10.1
+ test
+
-
\ No newline at end of file
+
diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java b/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java
index eb6d42f68..8d5bc34a6 100644
--- a/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java
+++ b/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java
@@ -18,11 +18,10 @@
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_TYPE_REFERENCE = new TypeReference<>() {
+ private static final TypeRef VOID_TYPE_REFERENCE = new TypeRef<>() {
};
- public static final TypeReference