MCP Annotations
MCP annotations provide behavioral hints and metadata to MCP clients about tools and resources. These hints help clients make informed decisions about how to interact with server features.
Tool Annotations
Tool annotations provide behavioral hints to help clients understand what kind of operations a tool performs.
title
A human-readable title for the tool, often displayed in user interfaces.
import io.quarkiverse.mcp.server.Tool;
import io.quarkiverse.mcp.server.Tool.Annotations;
public class MyTools {
@Tool(description = "Performs data analysis",
annotations = @Annotations(title = "Data Analyzer"))
String analyze(String data) {
return "Analysis result";
}
}
readOnlyHint
Set this annotation to true when the tool does not modify its environment (e.g., read operations, queries, searches).
The default value is false, indicating that the tool may perform write operations.
import io.quarkiverse.mcp.server.Tool;
import io.quarkiverse.mcp.server.Tool.Annotations;
public class MyTools {
@Tool(description = "Searches for files matching a pattern",
annotations = @Annotations(readOnlyHint = true))
String searchFiles(String pattern) {
// Only reads files, does not modify anything
return "file1.txt, file2.txt";
}
}
destructiveHint
Set this annotation to true when the tool may perform destructive updates to its environment (e.g., delete operations, overwrites).
Set this annotation to false when the tool performs only additive updates (e.g., creating new files, appending to logs).
The default value is true.
import io.quarkiverse.mcp.server.Tool;
import io.quarkiverse.mcp.server.Tool.Annotations;
public class MyTools {
@Tool(description = "Deletes a file from the filesystem",
annotations = @Annotations(destructiveHint = true))
String deleteFile(String filename) {
// Permanently removes a file - destructive operation
return "File deleted: " + filename;
}
@Tool(description = "Creates a new file",
annotations = @Annotations(destructiveHint = false))
String createFile(String filename, String content) {
// Only adds new files, never overwrites or deletes
return "File created: " + filename;
}
}
idempotentHint
Set this annotation to true when calling the tool repeatedly with the same arguments will have no additional effect on its environment (e.g., set operations, upserts). The default value is false, indicating that repeated calls may have cumulative effects (e.g., append operations, increments).
import io.quarkiverse.mcp.server.Tool;
import io.quarkiverse.mcp.server.Tool.Annotations;
public class MyTools {
@Tool(description = "Sets a configuration value",
annotations = @Annotations(idempotentHint = true))
String setConfig(String key, String value) {
// Setting the same value multiple times has the same effect
return "Config updated: " + key + " = " + value;
}
@Tool(description = "Appends a log entry",
annotations = @Annotations(idempotentHint = false))
String appendLog(String message) {
// Each call adds a new log entry
return "Log appended";
}
}
openWorldHint
Set this annotation to true when the tool may interact with an "open world" of external entities (e.g., web APIs, external databases).
Set this annotation to false when the tool’s domain of interaction is closed and well-defined (e.g., local files, internal state).
The default value is true.
import io.quarkiverse.mcp.server.Tool;
import io.quarkiverse.mcp.server.Tool.Annotations;
public class MyTools {
@Tool(description = "Fetches data from external API",
annotations = @Annotations(openWorldHint = true))
String fetchExternalData(String apiUrl) {
// Interacts with external web services
return "Data from API";
}
@Tool(description = "Calculates a checksum",
annotations = @Annotations(openWorldHint = false))
String calculateChecksum(String data) {
// Pure computation, no external interaction
return "checksum-value";
}
}
Combining Multiple Hints
Tool annotations can be combined to accurately describe a tool’s behavior:
import io.quarkiverse.mcp.server.Tool;
import io.quarkiverse.mcp.server.Tool.Annotations;
public class MyTools {
@Tool(description = "Gets the current weather from an external API",
annotations = @Annotations(
title = "Weather Lookup",
readOnlyHint = true, // Does not modify any state
destructiveHint = false, // No destructive operations
idempotentHint = true, // Same query returns same result
openWorldHint = true)) // Calls external weather API
String getWeather(String location) {
// Read-only external API call
return "Sunny, 72°F";
}
@Tool(description = "Updates user preferences in local database",
annotations = @Annotations(
title = "Update Preferences",
readOnlyHint = false, // Modifies state
destructiveHint = false, // Additive updates only
idempotentHint = true, // Setting same value is idempotent
openWorldHint = false)) // Local database only
String updatePreferences(String key, String value) {
// Local state update
return "Preferences updated";
}
}
Resource Annotations
Resource annotations provide metadata about how clients should prioritize and display resources.
audience
Set this annotation to specify the intended audience for the resource:
-
Role.USER: The resource is intended for end users -
Role.ASSISTANT: The resource is intended for AI assistants
import io.quarkiverse.mcp.server.Resource;
import io.quarkiverse.mcp.server.Resource.Annotations;
import io.quarkiverse.mcp.server.Role;
import io.quarkiverse.mcp.server.TextResourceContents;
import io.quarkiverse.mcp.server.RequestUri;
public class MyResources {
@Resource(uri = "file:///docs/user-guide.txt",
description = "User documentation",
annotations = @Annotations(audience = Role.USER, priority = 1.0))
TextResourceContents userGuide(RequestUri uri) {
return new TextResourceContents(uri.value(), "User guide content...", "text/plain");
}
@Resource(uri = "file:///system/api-schema.json",
description = "API schema for assistant use",
annotations = @Annotations(audience = Role.ASSISTANT, priority = 0.8))
TextResourceContents apiSchema(RequestUri uri) {
return new TextResourceContents(uri.value(), "{\"schema\": \"...\"}", "application/json");
}
}
priority
The priority is a value between 0.0 and 1.0 indicating the resource’s importance. Higher values indicate higher priority. Use this to help clients decide which resources to surface or process first.
The default value is 0.5.
import io.quarkiverse.mcp.server.Resource;
import io.quarkiverse.mcp.server.Resource.Annotations;
import io.quarkiverse.mcp.server.Role;
import io.quarkiverse.mcp.server.TextResourceContents;
import io.quarkiverse.mcp.server.RequestUri;
public class MyResources {
@Resource(uri = "file:///critical-data.json",
description = "Critical system data",
annotations = @Annotations(audience = Role.ASSISTANT, priority = 1.0))
TextResourceContents criticalData(RequestUri uri) {
return new TextResourceContents(uri.value(), "Critical data", "application/json");
}
@Resource(uri = "file:///optional-info.txt",
description = "Optional supplementary information",
annotations = @Annotations(audience = Role.USER, priority = 0.3))
TextResourceContents optionalInfo(RequestUri uri) {
return new TextResourceContents(uri.value(), "Optional info", "text/plain");
}
}
Resource Template Annotations
Resource templates also support annotations with the same semantics:
import io.quarkiverse.mcp.server.ResourceTemplate;
import io.quarkiverse.mcp.server.Resource.Annotations;
import io.quarkiverse.mcp.server.Role;
import io.quarkiverse.mcp.server.TextResourceContents;
import io.quarkiverse.mcp.server.RequestUri;
public class MyResourceTemplates {
@ResourceTemplate(uriTemplate = "file:///logs/{date}",
description = "Daily log files",
annotations = @Annotations(audience = Role.ASSISTANT, priority = 0.7))
TextResourceContents dailyLogs(String date, RequestUri uri) {
return new TextResourceContents(uri.value(), "Logs for " + date, "text/plain");
}
}
Programmatic Annotations
Annotations can also be set programmatically when using the manager APIs:
import io.quarkiverse.mcp.server.ToolManager;
import io.quarkiverse.mcp.server.ToolManager.ToolAnnotations;
import io.quarkiverse.mcp.server.ToolResponse;
import io.quarkus.runtime.Startup;
import jakarta.inject.Inject;
public class MyTools {
@Inject
ToolManager toolManager;
@Startup
void registerTools() {
toolManager.newTool("searchDatabase")
.setDescription("Searches the database")
.setAnnotations(new ToolAnnotations(
"Database Search", // title
true, // readOnlyHint
false, // destructiveHint
true, // idempotentHint
false)) // openWorldHint
.setHandler(args -> ToolResponse.success("Search results"))
.register();
}
}