Skip to content

feat(tools): standard annotation classes #235

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Original file line number Diff line number Diff line change
@@ -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();

}
Original file line number Diff line number Diff line change
@@ -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 <a href=
* "https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.json#L2164">here</a>
*/
@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;

}
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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 "";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package io.modelcontextprotocol.tools.annotation;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.modelcontextprotocol.tools.service;

import java.util.List;

import io.modelcontextprotocol.tools.util.ToolDescription;

public interface ToolGroupService {

default List<ToolDescription> getToolDescriptions(String interfaceClassName) {
return ToolDescription.fromService(this, interfaceClassName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package io.modelcontextprotocol.tools.service;
Original file line number Diff line number Diff line change
@@ -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 <a href=
* "https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.json#L2164">here</a>
*/
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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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<ToolParamDescription> toolParamDescriptions,
ToolResultDescription resultDescription, ToolAnnotationsDescription toolAnnotationsDescription) {

public static List<ToolDescription> 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<ToolDescription> fromService(Object svc, String serviceClass) {
Optional<Class<?>> optClass = Arrays.asList(svc.getClass().getInterfaces()).stream().filter(c -> {
return c.getName().equals(serviceClass);
}).findFirst();
return optClass.isPresent() ? ToolDescription.fromClass(optClass.get()) : Collections.emptyList();
}

}
Original file line number Diff line number Diff line change
@@ -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<ToolParamDescription> 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();
}

}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package io.modelcontextprotocol.tools.util;