diff --git a/mcp/src/main/java/io/modelcontextprotocol/tools/annotation/Tool.java b/mcp/src/main/java/io/modelcontextprotocol/tools/annotation/Tool.java new file mode 100644 index 00000000..45a6491f --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/tools/annotation/Tool.java @@ -0,0 +1,18 @@ +package io.modelcontextprotocol.tools.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Tool { + + String name() default ""; + + String description() default ""; + + ToolAnnotations annotations(); + +} \ No newline at end of file diff --git a/mcp/src/main/java/io/modelcontextprotocol/tools/annotation/ToolAnnotations.java b/mcp/src/main/java/io/modelcontextprotocol/tools/annotation/ToolAnnotations.java new file mode 100644 index 00000000..b30bdb36 --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/tools/annotation/ToolAnnotations.java @@ -0,0 +1,27 @@ +package io.modelcontextprotocol.tools.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Supports the addition of ToolAnnotations to Tool spec in the MCP schema + * (draft as of 5/18/2025) located here + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ToolAnnotations { + + String title() default ""; + + boolean destructiveHint() default false; + + boolean idempotentHint() default false; + + boolean openWorldHint() default false; + + boolean readOnlyHint() default false; + +} diff --git a/mcp/src/main/java/io/modelcontextprotocol/tools/annotation/ToolParam.java b/mcp/src/main/java/io/modelcontextprotocol/tools/annotation/ToolParam.java new file mode 100644 index 00000000..0801956a --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/tools/annotation/ToolParam.java @@ -0,0 +1,18 @@ +package io.modelcontextprotocol.tools.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface ToolParam { + + String name() default ""; + + String description() default ""; + + boolean required() default true; + +} diff --git a/mcp/src/main/java/io/modelcontextprotocol/tools/annotation/ToolResult.java b/mcp/src/main/java/io/modelcontextprotocol/tools/annotation/ToolResult.java new file mode 100644 index 00000000..8765fa44 --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/tools/annotation/ToolResult.java @@ -0,0 +1,14 @@ +package io.modelcontextprotocol.tools.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ToolResult { + + String description() default ""; + +} diff --git a/mcp/src/main/java/io/modelcontextprotocol/tools/annotation/package-info.java b/mcp/src/main/java/io/modelcontextprotocol/tools/annotation/package-info.java new file mode 100644 index 00000000..ee01eb56 --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/tools/annotation/package-info.java @@ -0,0 +1 @@ +package io.modelcontextprotocol.tools.annotation; \ No newline at end of file diff --git a/mcp/src/main/java/io/modelcontextprotocol/tools/service/ToolGroupService.java b/mcp/src/main/java/io/modelcontextprotocol/tools/service/ToolGroupService.java new file mode 100644 index 00000000..7c7c3e66 --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/tools/service/ToolGroupService.java @@ -0,0 +1,12 @@ +package io.modelcontextprotocol.tools.service; + +import java.util.List; + +import io.modelcontextprotocol.tools.util.ToolDescription; + +public interface ToolGroupService { + + default List getToolDescriptions(String interfaceClassName) { + return ToolDescription.fromService(this, interfaceClassName); + } +} diff --git a/mcp/src/main/java/io/modelcontextprotocol/tools/service/package-info.java b/mcp/src/main/java/io/modelcontextprotocol/tools/service/package-info.java new file mode 100644 index 00000000..4515b402 --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/tools/service/package-info.java @@ -0,0 +1 @@ +package io.modelcontextprotocol.tools.service; \ No newline at end of file diff --git a/mcp/src/main/java/io/modelcontextprotocol/tools/util/ToolAnnotationsDescription.java b/mcp/src/main/java/io/modelcontextprotocol/tools/util/ToolAnnotationsDescription.java new file mode 100644 index 00000000..1a67d24f --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/tools/util/ToolAnnotationsDescription.java @@ -0,0 +1,21 @@ +package io.modelcontextprotocol.tools.util; + +import io.modelcontextprotocol.tools.annotation.ToolAnnotations; + +/** + * Describes the ToolAnnotations type in the MCP schema (draft as of 5/18/2025) + * located here + */ +public record ToolAnnotationsDescription(boolean destructiveHint, boolean idempotentHint, boolean openWorldHint, + boolean readOnlyHint, String title) { + + public static ToolAnnotationsDescription fromAnnotations(ToolAnnotations annotations) { + if (annotations != null) { + return new ToolAnnotationsDescription(annotations.destructiveHint(), annotations.idempotentHint(), + annotations.openWorldHint(), annotations.readOnlyHint(), annotations.title()); + } else { + return null; + } + } +} diff --git a/mcp/src/main/java/io/modelcontextprotocol/tools/util/ToolDescription.java b/mcp/src/main/java/io/modelcontextprotocol/tools/util/ToolDescription.java new file mode 100644 index 00000000..b210b2f4 --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/tools/util/ToolDescription.java @@ -0,0 +1,43 @@ +package io.modelcontextprotocol.tools.util; + +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import io.modelcontextprotocol.tools.annotation.Tool; +import io.modelcontextprotocol.tools.annotation.ToolAnnotations; + +public record ToolDescription(String name, String description, List toolParamDescriptions, + ToolResultDescription resultDescription, ToolAnnotationsDescription toolAnnotationsDescription) { + + public static List fromClass(Class clazz) { + return Arrays.asList(clazz.getMethods()).stream().map(m -> { + // skip static methods + if (!Modifier.isStatic(m.getModifiers())) { + // Look for Tool annotation + Tool ma = m.getAnnotation(Tool.class); + if (ma != null) { + // Look for ToolAnnotations method annotation + ToolAnnotations tas = m.getAnnotation(ToolAnnotations.class); + return new ToolDescription(m.getName(), ma.description(), + ToolParamDescription.fromParameters(m.getParameters()), ToolResultDescription.fromMethod(m), + ToolAnnotationsDescription.fromAnnotations(tas)); + } + } + return null; + }).filter(Objects::nonNull).collect(Collectors.toList()); + + } + + public static List fromService(Object svc, String serviceClass) { + Optional> optClass = Arrays.asList(svc.getClass().getInterfaces()).stream().filter(c -> { + return c.getName().equals(serviceClass); + }).findFirst(); + return optClass.isPresent() ? ToolDescription.fromClass(optClass.get()) : Collections.emptyList(); + } + +} diff --git a/mcp/src/main/java/io/modelcontextprotocol/tools/util/ToolParamDescription.java b/mcp/src/main/java/io/modelcontextprotocol/tools/util/ToolParamDescription.java new file mode 100644 index 00000000..c68f133f --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/tools/util/ToolParamDescription.java @@ -0,0 +1,28 @@ +package io.modelcontextprotocol.tools.util; + +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import io.modelcontextprotocol.tools.annotation.ToolParam; + +public record ToolParamDescription(String name, String description, boolean required, Parameter parameter) { + + public static List fromParameters(Parameter[] parameters) { + return parameters != null ? Arrays.asList(parameters).stream().map(p -> { + ToolParam tp = p.getAnnotation(ToolParam.class); + if (tp != null) { + String name = tp.name(); + if ("".equals(name)) { + name = p.getName(); + } + return new ToolParamDescription(name, tp.description(), tp.required(), p); + } + return null; + }).filter(Objects::nonNull).collect(Collectors.toList()) : Collections.emptyList(); + } + +} diff --git a/mcp/src/main/java/io/modelcontextprotocol/tools/util/ToolResultDescription.java b/mcp/src/main/java/io/modelcontextprotocol/tools/util/ToolResultDescription.java new file mode 100644 index 00000000..c8e3d417 --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/tools/util/ToolResultDescription.java @@ -0,0 +1,14 @@ +package io.modelcontextprotocol.tools.util; + +import java.lang.reflect.Method; + +import io.modelcontextprotocol.tools.annotation.ToolResult; + +public record ToolResultDescription(String description, Class returnType) { + + public static ToolResultDescription fromMethod(Method method) { + ToolResult tr = method.getAnnotation(ToolResult.class); + return tr != null ? new ToolResultDescription(tr.description(), method.getReturnType()) + : new ToolResultDescription("", method.getReturnType()); + } +} diff --git a/mcp/src/main/java/io/modelcontextprotocol/tools/util/package-info.java b/mcp/src/main/java/io/modelcontextprotocol/tools/util/package-info.java new file mode 100644 index 00000000..3fa8f559 --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/tools/util/package-info.java @@ -0,0 +1 @@ +package io.modelcontextprotocol.tools.util; \ No newline at end of file