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

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

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

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

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

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

View file

@ -54,6 +54,7 @@ import org.junit.jupiter.params.provider.ValueSource;
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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
AtomicReference<CreateMessageResult> 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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
AtomicReference<CreateMessageResult> 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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
AtomicReference<ElicitResult> 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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
AtomicReference<ElicitResult> 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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
// ---------------------------------------
// 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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
.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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
.tool(Tool.builder()
.name("tool2")
.description("tool2 description")
.inputSchema(emptyJsonSchema)
.inputSchema(EMPTY_JSON_SCHEMA)
.build())
.callHandler((exchange, request) -> callResponse)
.build();
@ -1040,7 +1032,7 @@ public abstract class AbstractMcpClientServerIntegrationTests {
.tool(Tool.builder()
.name("logging-test")
.description("Test logging notifications")
.inputSchema(emptyJsonSchema)
.inputSchema(EMPTY_JSON_SCHEMA)
.build())
.callHandler((exchange, request) -> {
@ -1154,7 +1146,7 @@ public abstract class AbstractMcpClientServerIntegrationTests {
.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 @@ public abstract class AbstractMcpClientServerIntegrationTests {
.tool(Tool.builder()
.name("ping-async-test")
.description("Test ping async behavior")
.inputSchema(emptyJsonSchema)
.inputSchema(EMPTY_JSON_SCHEMA)
.build())
.callHandler((exchange, request) -> {

View file

@ -31,6 +31,7 @@ import org.junit.jupiter.params.ParameterizedTest;
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 @@ public abstract class AbstractStatelessIntegrationTests {
// ---------------------------------------
// 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 @@ public abstract class AbstractStatelessIntegrationTests {
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 @@ public abstract class AbstractStatelessIntegrationTests {
.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 @@ public abstract class AbstractStatelessIntegrationTests {
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 @@ public abstract class AbstractStatelessIntegrationTests {
.tool(Tool.builder()
.name("tool2")
.description("tool2 description")
.inputSchema(emptyJsonSchema)
.inputSchema(EMPTY_JSON_SCHEMA)
.build())
.callHandler((exchange, request) -> callResponse)
.build();

View file

@ -9,8 +9,8 @@ import java.util.List;
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 class MockMcpTransport implements McpClientTransport, McpServerTransport
}
@Override
public <T> T unmarshalFrom(Object data, TypeReference<T> typeRef) {
return new ObjectMapper().convertValue(data, typeRef);
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return McpJsonMapper.getDefault().convertValue(data, typeRef);
}
}

View file

@ -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.concurrent.atomic.AtomicReference;
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 @@ public abstract class AbstractMcpAsyncClientTests {
.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();

View file

@ -22,8 +22,6 @@ import java.util.concurrent.atomic.AtomicReference;
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;

View file

@ -26,6 +26,7 @@ import org.junit.jupiter.params.provider.ValueSource;
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 @@ public abstract class AbstractMcpAsyncServerTests {
// ---------------------------------------
// 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 @@ public abstract class AbstractMcpAsyncServerTests {
@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 @@ public abstract class AbstractMcpAsyncServerTests {
@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 @@ public abstract class AbstractMcpAsyncServerTests {
@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 @@ public abstract class AbstractMcpAsyncServerTests {
@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 @@ public abstract class AbstractMcpAsyncServerTests {
@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<McpServerFeatures.AsyncToolSpecification> specs = List.of(
McpServerFeatures.AsyncToolSpecification.builder()
.tool(duplicateTool)
@ -210,7 +218,11 @@ public abstract class AbstractMcpAsyncServerTests {
@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 @@ public abstract class AbstractMcpAsyncServerTests {
@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 @@ public abstract class AbstractMcpAsyncServerTests {
@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())

View file

@ -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 @@ public abstract class AbstractMcpSyncServerTests {
// ---------------------------------------
// Tools Tests
// ---------------------------------------
String emptyJsonSchema = """
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {}
}
""";
@Test
@Deprecated
void testAddTool() {
@ -115,7 +107,7 @@ public abstract class AbstractMcpSyncServerTests {
.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 @@ public abstract class AbstractMcpSyncServerTests {
.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 @@ public abstract class AbstractMcpSyncServerTests {
@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 @@ public abstract class AbstractMcpSyncServerTests {
@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 @@ public abstract class AbstractMcpSyncServerTests {
@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 @@ public abstract class AbstractMcpSyncServerTests {
@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<McpServerFeatures.SyncToolSpecification> specs = List.of(
McpServerFeatures.SyncToolSpecification.builder()
.tool(duplicateTool)
@ -210,7 +217,11 @@ public abstract class AbstractMcpSyncServerTests {
@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 @@ public abstract class AbstractMcpSyncServerTests {
@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())

View file

@ -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();
}

View file

@ -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);
}