Skip to content

Commit 52b5642

Browse files
committed
polish gh-749
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
1 parent 5229550 commit 52b5642

11 files changed

Lines changed: 171 additions & 102 deletions

File tree

mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,6 +1298,29 @@ public ListToolsResult(List<Tool> tools, String nextCursor) {
12981298
}
12991299
}
13001300

1301+
/**
1302+
* A JSON Schema object that describes the expected structure of arguments or output.
1303+
*
1304+
* @param type The type of the schema (e.g., "object")
1305+
* @param properties The properties of the schema object
1306+
* @param required List of required property names
1307+
* @param additionalProperties Whether additional properties are allowed
1308+
* @param defs Schema definitions using the newer $defs keyword
1309+
* @param definitions Schema definitions using the legacy definitions keyword
1310+
* @deprecated use {@link Map} instead.
1311+
*/
1312+
@Deprecated
1313+
@JsonInclude(JsonInclude.Include.NON_ABSENT)
1314+
@JsonIgnoreProperties(ignoreUnknown = true)
1315+
public record JsonSchema( // @formatter:off
1316+
@JsonProperty("type") String type,
1317+
@JsonProperty("properties") Map<String, Object> properties,
1318+
@JsonProperty("required") List<String> required,
1319+
@JsonProperty("additionalProperties") Boolean additionalProperties,
1320+
@JsonProperty("$defs") Map<String, Object> defs,
1321+
@JsonProperty("definitions") Map<String, Object> definitions) { // @formatter:on
1322+
}
1323+
13011324
/**
13021325
* Additional properties describing a Tool to clients.
13031326
*
@@ -1382,6 +1405,27 @@ public Builder description(String description) {
13821405
return this;
13831406
}
13841407

1408+
/**
1409+
* @deprecated use {@link #inputSchema(Map)} instead.
1410+
*/
1411+
@Deprecated
1412+
public Builder inputSchema(JsonSchema inputSchema) {
1413+
Map<String, Object> schema = new HashMap<>();
1414+
if (inputSchema.type() != null)
1415+
schema.put("type", inputSchema.type());
1416+
if (inputSchema.properties() != null)
1417+
schema.put("properties", inputSchema.properties());
1418+
if (inputSchema.required() != null)
1419+
schema.put("required", inputSchema.required());
1420+
if (inputSchema.additionalProperties() != null)
1421+
schema.put("additionalProperties", inputSchema.additionalProperties());
1422+
if (inputSchema.defs() != null)
1423+
schema.put("$defs", inputSchema.defs());
1424+
if (inputSchema.definitions() != null)
1425+
schema.put("definitions", inputSchema.definitions());
1426+
return inputSchema(schema);
1427+
}
1428+
13851429
public Builder inputSchema(Map<String, Object> inputSchema) {
13861430
this.inputSchema = inputSchema;
13871431
return this;

mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package io.modelcontextprotocol.server;
66

7+
import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA;
8+
79
import java.util.List;
810
import java.util.Map;
911

@@ -40,7 +42,11 @@ class AsyncToolSpecificationBuilderTest {
4042
@Test
4143
void builderShouldCreateValidAsyncToolSpecification() {
4244

43-
Tool tool = McpSchema.Tool.builder().name("test-tool").title("A test tool").inputSchema(Map.of()).build();
45+
Tool tool = McpSchema.Tool.builder()
46+
.name("test-tool")
47+
.title("A test tool")
48+
.inputSchema(EMPTY_JSON_SCHEMA)
49+
.build();
4450

4551
McpServerFeatures.AsyncToolSpecification specification = McpServerFeatures.AsyncToolSpecification.builder()
4652
.tool(tool)
@@ -63,7 +69,11 @@ void builderShouldThrowExceptionWhenToolIsNull() {
6369

6470
@Test
6571
void builderShouldThrowExceptionWhenCallToolIsNull() {
66-
Tool tool = McpSchema.Tool.builder().name("test-tool").title("A test tool").inputSchema(Map.of()).build();
72+
Tool tool = McpSchema.Tool.builder()
73+
.name("test-tool")
74+
.title("A test tool")
75+
.inputSchema(EMPTY_JSON_SCHEMA)
76+
.build();
6777

6878
assertThatThrownBy(() -> McpServerFeatures.AsyncToolSpecification.builder().tool(tool).build())
6979
.isInstanceOf(IllegalArgumentException.class)
@@ -72,7 +82,11 @@ void builderShouldThrowExceptionWhenCallToolIsNull() {
7282

7383
@Test
7484
void builderShouldAllowMethodChaining() {
75-
Tool tool = McpSchema.Tool.builder().name("test-tool").title("A test tool").inputSchema(Map.of()).build();
85+
Tool tool = McpSchema.Tool.builder()
86+
.name("test-tool")
87+
.title("A test tool")
88+
.inputSchema(EMPTY_JSON_SCHEMA)
89+
.build();
7690
McpServerFeatures.AsyncToolSpecification.Builder builder = McpServerFeatures.AsyncToolSpecification.builder();
7791

7892
// Then - verify method chaining returns the same builder instance
@@ -87,7 +101,7 @@ void builtSpecificationShouldExecuteCallToolCorrectly() {
87101
Tool tool = McpSchema.Tool.builder()
88102
.name("calculator")
89103
.title("Simple calculator")
90-
.inputSchema(Map.of())
104+
.inputSchema(EMPTY_JSON_SCHEMA)
91105
.build();
92106
String expectedResult = "42";
93107

@@ -111,7 +125,11 @@ void builtSpecificationShouldExecuteCallToolCorrectly() {
111125

112126
@Test
113127
void fromSyncShouldConvertSyncToolSpecificationCorrectly() {
114-
Tool tool = McpSchema.Tool.builder().name("sync-tool").title("A sync tool").inputSchema(Map.of()).build();
128+
Tool tool = McpSchema.Tool.builder()
129+
.name("sync-tool")
130+
.title("A sync tool")
131+
.inputSchema(EMPTY_JSON_SCHEMA)
132+
.build();
115133
String expectedResult = "sync result";
116134

117135
// Create a sync tool specification

mcp-core/src/test/java/io/modelcontextprotocol/server/SyncToolSpecificationBuilderTest.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package io.modelcontextprotocol.server;
66

7+
import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA;
8+
79
import java.util.List;
810
import java.util.Map;
911

@@ -37,7 +39,7 @@ class SyncToolSpecificationBuilderTest {
3739
@Test
3840
void builderShouldCreateValidSyncToolSpecification() {
3941

40-
Tool tool = Tool.builder().name("test-tool").title("A test tool").inputSchema(Map.of()).build();
42+
Tool tool = Tool.builder().name("test-tool").title("A test tool").inputSchema(EMPTY_JSON_SCHEMA).build();
4143

4244
McpServerFeatures.SyncToolSpecification specification = McpServerFeatures.SyncToolSpecification.builder()
4345
.tool(tool)
@@ -61,7 +63,7 @@ void builderShouldThrowExceptionWhenToolIsNull() {
6163

6264
@Test
6365
void builderShouldThrowExceptionWhenCallToolIsNull() {
64-
Tool tool = Tool.builder().name("test-tool").description("A test tool").inputSchema(Map.of()).build();
66+
Tool tool = Tool.builder().name("test-tool").description("A test tool").inputSchema(EMPTY_JSON_SCHEMA).build();
6567

6668
assertThatThrownBy(() -> McpServerFeatures.SyncToolSpecification.builder().tool(tool).build())
6769
.isInstanceOf(IllegalArgumentException.class)
@@ -70,7 +72,7 @@ void builderShouldThrowExceptionWhenCallToolIsNull() {
7072

7173
@Test
7274
void builderShouldAllowMethodChaining() {
73-
Tool tool = Tool.builder().name("test-tool").description("A test tool").inputSchema(Map.of()).build();
75+
Tool tool = Tool.builder().name("test-tool").description("A test tool").inputSchema(EMPTY_JSON_SCHEMA).build();
7476
McpServerFeatures.SyncToolSpecification.Builder builder = McpServerFeatures.SyncToolSpecification.builder();
7577

7678
// Then - verify method chaining returns the same builder instance
@@ -82,7 +84,11 @@ void builderShouldAllowMethodChaining() {
8284

8385
@Test
8486
void builtSpecificationShouldExecuteCallToolCorrectly() {
85-
Tool tool = Tool.builder().name("calculator").description("Simple calculator").inputSchema(Map.of()).build();
87+
Tool tool = Tool.builder()
88+
.name("calculator")
89+
.description("Simple calculator")
90+
.inputSchema(EMPTY_JSON_SCHEMA)
91+
.build();
8692
String expectedResult = "42";
8793

8894
McpServerFeatures.SyncToolSpecification specification = McpServerFeatures.SyncToolSpecification.builder()
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package io.modelcontextprotocol.util;
22

3-
import io.modelcontextprotocol.spec.McpSchema;
4-
53
import java.util.Collections;
4+
import java.util.Map;
65

76
public final class ToolsUtils {
87

98
private ToolsUtils() {
109
}
1110

11+
public static final Map<String, Object> EMPTY_JSON_SCHEMA = Map.of("type", "object", "properties",
12+
Collections.emptyMap());
13+
1214
}

mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package io.modelcontextprotocol;
66

7+
import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA;
8+
79
import java.net.URI;
810
import java.net.http.HttpClient;
911
import java.net.http.HttpRequest;
@@ -105,7 +107,7 @@ void testCreateMessageWithoutSamplingCapabilities(String clientType) {
105107
var clientBuilder = clientBuilders.get(clientType);
106108

107109
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
108-
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build())
110+
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
109111
.callHandler((exchange, request) -> {
110112
return exchange.createMessage(mock(McpSchema.CreateMessageRequest.class))
111113
.then(Mono.just(mock(CallToolResult.class)));
@@ -155,7 +157,7 @@ void testCreateMessageSuccess(String clientType) {
155157
AtomicReference<CreateMessageResult> samplingResult = new AtomicReference<>();
156158

157159
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
158-
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build())
160+
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
159161
.callHandler((exchange, request) -> {
160162

161163
var createMessageRequest = McpSchema.CreateMessageRequest.builder()
@@ -234,7 +236,7 @@ void testCreateMessageWithRequestTimeoutSuccess(String clientType) throws Interr
234236
AtomicReference<CreateMessageResult> samplingResult = new AtomicReference<>();
235237

236238
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
237-
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build())
239+
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
238240
.callHandler((exchange, request) -> {
239241

240242
var createMessageRequest = McpSchema.CreateMessageRequest.builder()
@@ -309,7 +311,7 @@ void testCreateMessageWithRequestTimeoutFail(String clientType) throws Interrupt
309311
.build();
310312

311313
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
312-
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build())
314+
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
313315
.callHandler((exchange, request) -> {
314316

315317
var createMessageRequest = McpSchema.CreateMessageRequest.builder()
@@ -359,7 +361,7 @@ void testCreateElicitationWithoutElicitationCapabilities(String clientType) {
359361
var clientBuilder = clientBuilders.get(clientType);
360362

361363
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
362-
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build())
364+
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
363365
.callHandler((exchange, request) -> exchange.createElicitation(mock(ElicitRequest.class))
364366
.then(Mono.just(mock(CallToolResult.class))))
365367
.build();
@@ -405,7 +407,7 @@ void testCreateElicitationSuccess(String clientType) {
405407
AtomicReference<McpSchema.ElicitResult> elicitResultRef = new AtomicReference<>();
406408

407409
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
408-
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build())
410+
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
409411
.callHandler((exchange, request) -> {
410412

411413
var elicitationRequest = McpSchema.ElicitRequest.builder()
@@ -464,7 +466,7 @@ void testCreateElicitationWithRequestTimeoutSuccess(String clientType) {
464466
AtomicReference<ElicitResult> resultRef = new AtomicReference<>();
465467

466468
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
467-
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build())
469+
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
468470
.callHandler((exchange, request) -> {
469471

470472
var elicitationRequest = McpSchema.ElicitRequest.builder()
@@ -535,7 +537,7 @@ void testCreateElicitationWithRequestTimeoutFail(String clientType) {
535537
AtomicReference<ElicitResult> resultRef = new AtomicReference<>();
536538

537539
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
538-
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build())
540+
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
539541
.callHandler((exchange, request) -> {
540542

541543
var elicitationRequest = ElicitRequest.builder()
@@ -633,7 +635,7 @@ void testRootsWithoutCapability(String clientType) {
633635
var clientBuilder = clientBuilders.get(clientType);
634636

635637
McpServerFeatures.SyncToolSpecification tool = McpServerFeatures.SyncToolSpecification.builder()
636-
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build())
638+
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
637639
.callHandler((exchange, request) -> {
638640

639641
exchange.listRoots(); // try to list roots
@@ -775,7 +777,7 @@ void testToolCallSuccess(String clientType) {
775777
.addContent(new McpSchema.TextContent("CALL RESPONSE; ctx=importantValue"))
776778
.build();
777779
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
778-
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build())
780+
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
779781
.callHandler((exchange, request) -> {
780782

781783
try {
@@ -826,7 +828,11 @@ void testThrowingToolCallIsCaughtBeforeTimeout(String clientType) {
826828
McpSyncServer mcpServer = prepareSyncServerBuilder()
827829
.capabilities(ServerCapabilities.builder().tools(true).build())
828830
.tools(McpServerFeatures.SyncToolSpecification.builder()
829-
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build())
831+
.tool(Tool.builder()
832+
.name("tool1")
833+
.description("tool1 description")
834+
.inputSchema(EMPTY_JSON_SCHEMA)
835+
.build())
830836
.callHandler((exchange, request) -> {
831837
// We trigger a timeout on blocking read, raising an exception
832838
Mono.never().block(Duration.ofSeconds(1));
@@ -864,7 +870,7 @@ void testToolCallSuccessWithTranportContextExtraction(String clientType) {
864870
.addContent(new McpSchema.TextContent("CALL RESPONSE; ctx=value"))
865871
.build();
866872
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
867-
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build())
873+
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
868874
.callHandler((exchange, request) -> {
869875

870876
McpTransportContext transportContext = exchange.transportContext();
@@ -920,7 +926,7 @@ void testToolListChangeHandlingSuccess(String clientType) {
920926
.build();
921927

922928
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
923-
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build())
929+
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
924930
.callHandler((exchange, request) -> {
925931
// perform a blocking call to a remote service
926932
try {
@@ -986,7 +992,11 @@ void testToolListChangeHandlingSuccess(String clientType) {
986992

987993
// Add a new tool
988994
McpServerFeatures.SyncToolSpecification tool2 = McpServerFeatures.SyncToolSpecification.builder()
989-
.tool(Tool.builder().name("tool2").description("tool2 description").inputSchema(Map.of()).build())
995+
.tool(Tool.builder()
996+
.name("tool2")
997+
.description("tool2 description")
998+
.inputSchema(EMPTY_JSON_SCHEMA)
999+
.build())
9901000
.callHandler((exchange, request) -> callResponse)
9911001
.build();
9921002

@@ -1037,7 +1047,7 @@ void testLoggingNotification(String clientType) throws InterruptedException {
10371047
.tool(Tool.builder()
10381048
.name("logging-test")
10391049
.description("Test logging notifications")
1040-
.inputSchema(Map.of())
1050+
.inputSchema(EMPTY_JSON_SCHEMA)
10411051
.build())
10421052
.callHandler((exchange, request) -> {
10431053

@@ -1154,7 +1164,7 @@ void testProgressNotification(String clientType) throws InterruptedException {
11541164
.tool(McpSchema.Tool.builder()
11551165
.name("progress-test")
11561166
.description("Test progress notifications")
1157-
.inputSchema(Map.of())
1167+
.inputSchema(EMPTY_JSON_SCHEMA)
11581168
.build())
11591169
.callHandler((exchange, request) -> {
11601170

@@ -1312,7 +1322,7 @@ void testPingSuccess(String clientType) {
13121322
.tool(Tool.builder()
13131323
.name("ping-async-test")
13141324
.description("Test ping async behavior")
1315-
.inputSchema(Map.of())
1325+
.inputSchema(EMPTY_JSON_SCHEMA)
13161326
.build())
13171327
.callHandler((exchange, request) -> {
13181328

0 commit comments

Comments
 (0)