Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions mcp-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,6 @@
<version>${project.version}</version>
</dependency>

<!-- MCP JSON -->
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-json</artifactId>
<version>${project.version}</version>
</dependency>

<!-- MCP JSON Jackson -->
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
Expand Down
19 changes: 6 additions & 13 deletions mcp-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,6 @@
</build>

<dependencies>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-json</artifactId>
<version>0.18.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
Expand Down Expand Up @@ -97,21 +92,19 @@
<scope>provided</scope>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-json-jackson3</artifactId>
<version>0.18.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${springframework.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson3.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.projectreactor.netty</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package io.modelcontextprotocol.client;

import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.json.McpJsonDefaults;
import io.modelcontextprotocol.json.schema.JsonSchemaValidator;
import io.modelcontextprotocol.spec.McpClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
Expand Down Expand Up @@ -491,9 +492,12 @@ public McpSyncClient build() {

McpClientFeatures.Async asyncFeatures = McpClientFeatures.Async.fromSync(syncFeatures);

return new McpSyncClient(new McpAsyncClient(transport, this.requestTimeout, this.initializationTimeout,
jsonSchemaValidator != null ? jsonSchemaValidator : JsonSchemaValidator.getDefault(),
asyncFeatures), this.contextProvider);
return new McpSyncClient(
new McpAsyncClient(transport, this.requestTimeout, this.initializationTimeout,
jsonSchemaValidator != null ? jsonSchemaValidator
: McpJsonDefaults.getDefaultJsonSchemaValidator(),
asyncFeatures),
this.contextProvider);
}

}
Expand Down Expand Up @@ -826,7 +830,7 @@ public AsyncSpec enableCallToolSchemaCaching(boolean enableCallToolSchemaCaching
*/
public McpAsyncClient build() {
var jsonSchemaValidator = (this.jsonSchemaValidator != null) ? this.jsonSchemaValidator
: JsonSchemaValidator.getDefault();
: McpJsonDefaults.getDefaultJsonSchemaValidator();
return new McpAsyncClient(this.transport, this.requestTimeout, this.initializationTimeout,
jsonSchemaValidator,
new McpClientFeatures.Async(this.clientInfo, this.capabilities, this.roots,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpClientRequestCustomizer;
import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer;
import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.json.McpJsonDefaults;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.spec.HttpHeaders;
Expand Down Expand Up @@ -327,7 +328,7 @@ public Builder connectTimeout(Duration connectTimeout) {
public HttpClientSseClientTransport build() {
HttpClient httpClient = this.clientBuilder.connectTimeout(this.connectTimeout).build();
return new HttpClientSseClientTransport(httpClient, requestBuilder, baseUri, sseEndpoint,
jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, httpRequestCustomizer);
jsonMapper == null ? McpJsonDefaults.getDefaultMcpJsonMapper() : jsonMapper, httpRequestCustomizer);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpClientRequestCustomizer;
import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer;
import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.json.McpJsonDefaults;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.spec.ClosedMcpTransportSession;
Expand Down Expand Up @@ -822,9 +823,10 @@ public Builder supportedProtocolVersions(List<String> supportedProtocolVersions)
*/
public HttpClientStreamableHttpTransport build() {
HttpClient httpClient = this.clientBuilder.connectTimeout(this.connectTimeout).build();
return new HttpClientStreamableHttpTransport(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
httpClient, requestBuilder, baseUri, endpoint, resumableStreams, openConnectionOnStartup,
httpRequestCustomizer, supportedProtocolVersions);
return new HttpClientStreamableHttpTransport(
jsonMapper == null ? McpJsonDefaults.getDefaultMcpJsonMapper() : jsonMapper, httpClient,
requestBuilder, baseUri, endpoint, resumableStreams, openConnectionOnStartup, httpRequestCustomizer,
supportedProtocolVersions);
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.modelcontextprotocol.json;

import io.modelcontextprotocol.json.schema.JsonSchemaValidator;
import io.modelcontextprotocol.json.schema.JsonSchemaValidatorSupplier;
import io.modelcontextprotocol.util.McpServiceLoader;

public class McpJsonDefaults {

protected static McpServiceLoader<McpJsonMapperSupplier, McpJsonMapper> mcpMapperServiceLoader;

protected static McpServiceLoader<JsonSchemaValidatorSupplier, JsonSchemaValidator> mcpValidatorServiceLoader;

public McpJsonDefaults() {
mcpMapperServiceLoader = new McpServiceLoader<McpJsonMapperSupplier, McpJsonMapper>(
McpJsonMapperSupplier.class);
mcpValidatorServiceLoader = new McpServiceLoader<JsonSchemaValidatorSupplier, JsonSchemaValidator>(
JsonSchemaValidatorSupplier.class);
}

void setMcpJsonMapperSupplier(McpJsonMapperSupplier supplier) {
mcpMapperServiceLoader.setSupplier(supplier);
}

void unsetMcpJsonMapperSupplier(McpJsonMapperSupplier supplier) {
mcpMapperServiceLoader.unsetSupplier(supplier);
}

public synchronized static McpJsonMapper getDefaultMcpJsonMapper() {
if (mcpMapperServiceLoader == null) {
new McpJsonDefaults();
}
return mcpMapperServiceLoader.getDefault();
}

void setJsonSchemaValidatorSupplier(JsonSchemaValidatorSupplier supplier) {
mcpValidatorServiceLoader.setSupplier(supplier);
}

void unsetJsonSchemaValidatorSupplier(JsonSchemaValidatorSupplier supplier) {
mcpValidatorServiceLoader.unsetSupplier(supplier);
}

public synchronized static JsonSchemaValidator getDefaultJsonSchemaValidator() {
if (mcpValidatorServiceLoader == null) {
new McpJsonDefaults();
}
return mcpValidatorServiceLoader.getDefault();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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 <T> generic type
* @throws IOException on parse errors
*/
<T> T readValue(String content, Class<T> type) throws IOException;

/**
* Deserialize JSON bytes into a target type.
* @param content JSON as bytes
* @param type target class
* @return deserialized instance
* @param <T> generic type
* @throws IOException on parse errors
*/
<T> T readValue(byte[] content, Class<T> 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 <T> generic type
* @throws IOException on parse errors
*/
<T> T readValue(String content, TypeRef<T> 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 <T> generic type
* @throws IOException on parse errors
*/
<T> T readValue(byte[] content, TypeRef<T> 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 <T> generic type
*/
<T> T convertValue(Object fromValue, Class<T> type);

/**
* Convert a value to a given parameterized type.
* @param fromValue source value
* @param type target type reference
* @return converted value
* @param <T> generic type
*/
<T> T convertValue(Object fromValue, TypeRef<T> 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;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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<McpJsonMapper> {

}
44 changes: 44 additions & 0 deletions mcp-core/src/main/java/io/modelcontextprotocol/json/TypeRef.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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<List<Foo>> ref = new TypeRef<>(){};
*/
public abstract class TypeRef<T> {

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: <pre>
* TypeRef&lt;List&lt;Foo&gt;&gt; ref = new TypeRef&lt;&gt;(){};
* </pre>
* @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;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2024-2024 the original author or authors.
*/
package io.modelcontextprotocol.json.schema;

import java.util.Map;

/**
* Interface for validating structured content against a JSON schema. This interface
* defines a method to validate structured content based on the provided output schema.
*
* @author Christian Tzolov
*/
public interface JsonSchemaValidator {

/**
* Represents the result of a validation operation.
*
* @param valid Indicates whether the validation was successful.
* @param errorMessage An error message if the validation failed, otherwise null.
* @param jsonStructuredOutput The text structured content in JSON format if the
* validation was successful, otherwise null.
*/
record ValidationResponse(boolean valid, String errorMessage, String jsonStructuredOutput) {

public static ValidationResponse asValid(String jsonStructuredOutput) {
return new ValidationResponse(true, null, jsonStructuredOutput);
}

public static ValidationResponse asInvalid(String message) {
return new ValidationResponse(false, message, null);
}
}

/**
* Validates the structured content against the provided JSON schema.
* @param schema The JSON schema to validate against.
* @param structuredContent The structured content to validate.
* @return A ValidationResponse indicating whether the validation was successful or
* not.
*/
ValidationResponse validate(Map<String, Object> schema, Object structuredContent);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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<JsonSchemaValidator> {

}
Loading