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