Skip to content

Commit b3abf88

Browse files
committed
feat(tool): Add support for complex JSON schema definitions
1 parent ea0b468 commit b3abf88

File tree

7 files changed

+125
-13
lines changed

7 files changed

+125
-13
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.codeboyzhou.mcp.declarative.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.TYPE)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface McpJsonSchemaDefinition {
11+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.github.codeboyzhou.mcp.declarative.annotation;
2+
3+
import com.github.codeboyzhou.mcp.declarative.util.StringHelper;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
@Target(ElementType.FIELD)
11+
@Retention(RetentionPolicy.RUNTIME)
12+
public @interface McpJsonSchemaDefinitionProperty {
13+
String name() default StringHelper.EMPTY;
14+
15+
String description();
16+
17+
boolean required() default false;
18+
}

src/main/java/com/github/codeboyzhou/mcp/declarative/server/McpSyncServerToolRegister.java

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.github.codeboyzhou.mcp.declarative.server;
22

3+
import com.github.codeboyzhou.mcp.declarative.annotation.McpJsonSchemaDefinition;
4+
import com.github.codeboyzhou.mcp.declarative.annotation.McpJsonSchemaDefinitionProperty;
35
import com.github.codeboyzhou.mcp.declarative.annotation.McpTool;
46
import com.github.codeboyzhou.mcp.declarative.annotation.McpToolParam;
57
import com.github.codeboyzhou.mcp.declarative.util.ReflectionHelper;
@@ -64,26 +66,65 @@ public McpServerFeatures.SyncToolSpecification createComponentFrom(Class<?> claz
6466

6567
private McpSchema.JsonSchema createJsonSchema(Method method) {
6668
Map<String, Object> properties = new HashMap<>();
69+
Map<String, Object> definitions = new HashMap<>();
6770
List<String> required = new ArrayList<>();
6871

6972
Set<Parameter> parameters = ReflectionHelper.getParametersAnnotatedWith(method, McpToolParam.class);
7073
for (Parameter parameter : parameters) {
7174
McpToolParam toolParam = parameter.getAnnotation(McpToolParam.class);
7275
final String parameterName = toolParam.name();
73-
final String parameterType = parameter.getType().getName().toLowerCase();
76+
Class<?> parameterType = parameter.getType();
77+
Map<String, String> property = new HashMap<>();
7478

75-
Map<String, String> parameterProperties = Map.of(
76-
"type", parameterType,
77-
"description", toolParam.description()
78-
);
79-
properties.put(parameterName, parameterProperties);
79+
if (parameterType.getAnnotation(McpJsonSchemaDefinition.class) == null) {
80+
property.put("type", parameterType.getName().toLowerCase());
81+
property.put("description", toolParam.description());
82+
} else {
83+
final String parameterTypeSimpleName = parameterType.getSimpleName();
84+
property.put("$ref", "#/definitions/" + parameterTypeSimpleName);
85+
Map<String, Object> definition = createJsonSchemaDefinition(parameterType);
86+
definitions.put(parameterTypeSimpleName, definition);
87+
}
88+
properties.put(parameterName, property);
8089

8190
if (toolParam.required()) {
8291
required.add(parameterName);
8392
}
8493
}
8594

86-
return new McpSchema.JsonSchema(OBJECT_TYPE_NAME, properties, required, false);
95+
final boolean hasAdditionalProperties = false;
96+
return new McpSchema.JsonSchema(OBJECT_TYPE_NAME, properties, required, hasAdditionalProperties, definitions, definitions);
97+
}
98+
99+
private Map<String, Object> createJsonSchemaDefinition(Class<?> definitionClass) {
100+
Map<String, Object> definitionJsonSchema = new HashMap<>();
101+
definitionJsonSchema.put("type", OBJECT_TYPE_NAME);
102+
103+
Map<String, Object> properties = new HashMap<>();
104+
List<String> required = new ArrayList<>();
105+
106+
ReflectionHelper.doWithFields(definitionClass, field -> {
107+
McpJsonSchemaDefinitionProperty property = field.getAnnotation(McpJsonSchemaDefinitionProperty.class);
108+
if (property == null) {
109+
return;
110+
}
111+
112+
Map<String, Object> fieldProperties = new HashMap<>();
113+
fieldProperties.put("type", field.getType().getName().toLowerCase());
114+
fieldProperties.put("description", property.description());
115+
116+
final String fieldName = property.name().isBlank() ? field.getName() : property.name();
117+
properties.put(fieldName, fieldProperties);
118+
119+
if (property.required()) {
120+
required.add(fieldName);
121+
}
122+
});
123+
124+
definitionJsonSchema.put("properties", properties);
125+
definitionJsonSchema.put("required", required);
126+
127+
return definitionJsonSchema;
87128
}
88129

89130
}

src/main/java/com/github/codeboyzhou/mcp/declarative/util/ReflectionHelper.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
import io.modelcontextprotocol.spec.McpSchema;
44

55
import java.lang.annotation.Annotation;
6+
import java.lang.reflect.Field;
67
import java.lang.reflect.Method;
78
import java.lang.reflect.Parameter;
89
import java.util.LinkedHashMap;
910
import java.util.Map;
1011
import java.util.Set;
12+
import java.util.function.Consumer;
1113

1214
import static java.util.stream.Collectors.toSet;
1315

@@ -23,6 +25,13 @@ public static Set<Parameter> getParametersAnnotatedWith(Method method, Class<? e
2325
return Set.of(parameters).stream().filter(p -> p.isAnnotationPresent(annotation)).collect(toSet());
2426
}
2527

28+
public static void doWithFields(Class<?> clazz, Consumer<Field> consumer) {
29+
Field[] declaredFields = clazz.getDeclaredFields();
30+
for (Field field : declaredFields) {
31+
consumer.accept(field);
32+
}
33+
}
34+
2635
public static Object invokeMethod(Class<?> clazz, Method method) throws Exception {
2736
Object object = clazz.getDeclaredConstructor().newInstance();
2837
return method.invoke(object);
@@ -48,14 +57,16 @@ private static Map<String, Object> asTypedParameters(McpSchema.JsonSchema schema
4857
Object parameterValue = parameters.get(parameterName);
4958
if (parameterValue == null) {
5059
Map<String, Object> map = (Map<String, Object>) parameterProperties;
51-
final String parameterType = map.get("type").toString();
52-
if (isTypeOf(String.class, parameterType)) {
53-
typedParameters.put(parameterName, "");
54-
} else if (isTypeOf(Integer.class, parameterType)) {
60+
final String jsonSchemaType = map.getOrDefault("type", StringHelper.EMPTY).toString();
61+
if (jsonSchemaType.isEmpty()) {
62+
typedParameters.put(parameterName, null);
63+
} else if (isTypeOf(String.class, jsonSchemaType)) {
64+
typedParameters.put(parameterName, StringHelper.EMPTY);
65+
} else if (isTypeOf(Integer.class, jsonSchemaType)) {
5566
typedParameters.put(parameterName, 0);
56-
} else if (isTypeOf(Number.class, parameterType)) {
67+
} else if (isTypeOf(Number.class, jsonSchemaType)) {
5768
typedParameters.put(parameterName, 0.0);
58-
} else if (isTypeOf(Boolean.class, parameterType)) {
69+
} else if (isTypeOf(Boolean.class, jsonSchemaType)) {
5970
typedParameters.put(parameterName, false);
6071
}
6172
} else {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.github.codeboyzhou.mcp.declarative.util;
2+
3+
public final class StringHelper {
4+
5+
public static final String EMPTY = "";
6+
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.github.codeboyzhou.mcp.declarative.server;
2+
3+
import com.github.codeboyzhou.mcp.declarative.annotation.McpJsonSchemaDefinition;
4+
import com.github.codeboyzhou.mcp.declarative.annotation.McpJsonSchemaDefinitionProperty;
5+
6+
@McpJsonSchemaDefinition
7+
public record TestMcpToolComplexJsonSchema(
8+
@McpJsonSchemaDefinitionProperty(name = "username", description = "username", required = true) String name,
9+
@McpJsonSchemaDefinitionProperty(description = "country") String country,
10+
String school // for testing property without annotation
11+
) {
12+
13+
}

src/test/java/com/github/codeboyzhou/mcp/declarative/server/TestMcpTools.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@McpTools
88
public class TestMcpTools {
99

10+
@SuppressWarnings("unused")
1011
@McpTool(name = "tool1", description = "tool1")
1112
public static String tool1(
1213
@McpToolParam(name = "name", description = "name", required = true) String name,
@@ -15,6 +16,7 @@ public static String tool1(
1516
return "Hello " + name + ", I am " + version;
1617
}
1718

19+
@SuppressWarnings("unused")
1820
@McpTool(description = "tool2")
1921
public static String tool2(
2022
@McpToolParam(name = "name", description = "name") String name,
@@ -23,4 +25,13 @@ public static String tool2(
2325
return "Hello " + name + ", I am " + version;
2426
}
2527

28+
@SuppressWarnings("unused")
29+
@McpTool(description = "tool3")
30+
public static String tool3(
31+
@McpToolParam(name = "complexJsonSchema", description = "complexJsonSchema")
32+
TestMcpToolComplexJsonSchema complexJsonSchema
33+
) {
34+
return String.format("Hello, my name is %s, I am from %s", complexJsonSchema.name(), complexJsonSchema.country());
35+
}
36+
2637
}

0 commit comments

Comments
 (0)