A logo showing the text blog.marcnuri.com
Español
Home»Artificial Intelligence»MCP Tool Annotations: Every Hint and Its Default Value

Recent Posts

  • Fabric8 Kubernetes Client 8.0 is now available!
  • Fabric8 Kubernetes Client 7.9 is now available!
  • Eclipse JKube 1.20 is now available!
  • Project, Agent, Person: The Missing Axis of AI Coding Tools
  • Fabric8 Kubernetes Client 7.8 is now available!

Categories

  • Artificial Intelligence
  • Backend Development
  • Cloud Native
  • Engineering Insights
  • Frontend Development
  • JavaScript
  • Legacy
  • Operations
  • Personal
  • Pet projects
  • Quality Engineering
  • Tools

Archives

  • September 2026
  • July 2026
  • June 2026
  • May 2026
  • April 2026
  • March 2026
  • February 2026
  • January 2026
  • December 2025
  • October 2025
  • September 2025
  • July 2025
  • May 2025
  • April 2025
  • March 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • August 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023
  • March 2023
  • February 2023
  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • July 2022
  • June 2022
  • May 2022
  • March 2022
  • February 2022
  • January 2022
  • December 2021
  • November 2021
  • October 2021
  • September 2021
  • August 2021
  • July 2021
  • January 2021
  • December 2020
  • November 2020
  • October 2020
  • September 2020
  • August 2020
  • July 2020
  • June 2020
  • May 2020
  • March 2020
  • February 2020
  • January 2020
  • December 2019
  • November 2019
  • October 2019
  • September 2019
  • July 2019
  • March 2019
  • November 2018
  • July 2018
  • June 2018
  • May 2018
  • April 2018
  • March 2018
  • February 2018
  • December 2017
  • October 2017
  • August 2017
  • July 2017
  • January 2017
  • May 2016
  • December 2015
  • November 2015
  • December 2014
  • November 2014
  • October 2014
  • March 2014
  • August 2012
  • February 2011
  • November 2008
  • June 2008
  • May 2008
  • April 2008
  • January 2008
  • November 2007
  • September 2007
  • August 2007
  • July 2007
  • June 2007
  • May 2007
  • April 2007
  • March 2007

MCP Tool Annotations: Every Hint and Its Default Value

2025-05-27 in Artificial Intelligence tagged LLM / AI Agent / Model Context Protocol (MCP) / Kubernetes by Marc Nuri | Last updated: 2026-10-14
Versión en Español

Introduction

⁠

MCP tool annotations are optional metadata that a Model Context Protocol (MCP) server attaches to each of its tools to tell clients how the tool behaves. Besides a display title, they're four boolean hints: readOnlyHint (the tool only reads data), destructiveHint (it may destroy data), idempotentHint (calling it twice has no extra effect) and openWorldHint (it reaches systems outside the server). Clients use them to decide when to ask the user for confirmation and how to present each tool.

Anthropic introduced MCP in November 2024, and tool annotations arrived a few months later, in the 2025-03-26 revision of the specification. I first wrote this post in May 2025, while adding them to the Kubernetes MCP Server. Since then, the specification has gone through three more revisions (2025-06-18, 2025-11-25 and 2026-07-28), and as of October 2026 the latest one still defines the same five annotations with the same defaults. What changed is everything around them: most of the MCP clients I checked now act on them, and both Anthropic's connector directory and OpenAI's plugin review require them.

In this post, I'll walk through what tool annotations are, what each hint means and its default value, how clients use them, why you can't blindly trust them, and how to set them in your own server. The examples come from the Kubernetes MCP Server; I tested them with v0.0.67 in October 2026.

What are MCP Tool Annotations?

⁠

MCP tool annotations are metadata that you attach to your MCP tools to communicate their behavior to the MCP client. Think of them as labels that describe what your tool does and how it behaves, similar to how you might label physical tools in a workshop.

They travel with each tool in the server's tools/list response, next to its name, description and input schema. This is the JSON a client receives for the Kubernetes MCP Server's helm_list tool (description, input schema and _meta left out):

tools/list (excerpt)
{
  "name": "helm_list",
  "title": "Helm: List",
  "annotations": {
    "title": "Helm: List",
    "readOnlyHint": true,
    "destructiveHint": false,
    "idempotentHint": false,
    "openWorldHint": true
  }
}

The specification calls all of these properties hints, title included: they help client applications make informed decisions about tool usage, display appropriate user interfaces, and implement safety controls.

Note

Annotations are hints provided by the MCP server and are informational only. They do not enforce any behavior or restrictions on the tool itself. The specification says clients MUST consider them untrusted unless they come from a trusted server (more on this below).

Why are MCP Tool Annotations Important?

⁠

MCP tool annotations allow servers to express the behavior of their tools in a standardized way. By using annotations, tools become self-descriptive, which brings several benefits:

  • Enhanced User Experience: A human-readable title lets clients show "Helm: List" instead of helm_list.
  • Fewer, Better Confirmation Prompts: Clients can run read-only tools without asking and always ask before a destructive one, instead of prompting for every call.
  • Server-Side Safety Modes: A server can use its own annotations to decide which tools to expose; the Kubernetes MCP Server's read-only mode is built on them.
  • Directory Requirements: As of October 2026, Anthropic's Connectors Directory and OpenAI's plugin review both require annotations before they list a server.
  • Consistency Across Tools: Annotations provide a consistent way to describe tool behavior, making it easier for developers to understand and use different tools across various MCP servers.

MCP Tool Annotations and Their Default Values

⁠

The MCP specification defines five annotations, all optional:

AnnotationDefaultMeaning
titlenoneA human-readable name for the tool
readOnlyHintfalseThe tool only reads, never modifies its environment
destructiveHinttrueThe tool may delete or overwrite data (if not read-only)
idempotentHintfalseRepeating the call has no extra effect (if not read-only)
openWorldHinttrueThe tool reaches external systems, not a closed domain

The defaults assume the worst: per the specification, a tool without annotations may modify its environment, may destroy data, is not safe to retry, and talks to the outside world. For a client that follows the defaults, leaving out a hint never makes a tool look safer than it is; it only makes the client ask for confirmation more often than needed. Not every client does: VS Code, for example, only asks you to review an open-world tool's result when openWorldHint is explicitly true.

readOnlyHint (Read-Only Hint)

⁠

Default false. Set it to true when the tool doesn't modify its environment, like helm_list. It's the hint clients act on most: a read-only tool can run without a confirmation prompt.

destructiveHint (Destructive Hint)

⁠

Default true, and only meaningful when readOnlyHint is false. true means the tool may perform destructive updates, like deleting a pod or uninstalling a Helm release; false means it only performs additive ones, like running a new pod or installing a release. Clients that read it ask before running a destructive tool.

idempotentHint (Idempotent Hint)

⁠

Default false, and only meaningful when readOnlyHint is false. true means calling the tool again with the same arguments has no additional effect, so a retry is safe: uninstalling the same Helm release twice leaves the cluster as the first call did.

openWorldHint (Open-World Hint)

⁠

Default true. true means the tool may interact with an open world of external entities, like a web search or a Kubernetes cluster; false means it works on a closed domain, like a local memory store.

The title Annotation and the Top-Level title

⁠

The title annotation has a sibling. Since the 2025-06-18 revision, a tool can also carry a top-level title field, and the specification sets the display-name order: title, then annotations.title, then name. That's why the Kubernetes MCP Server sends the same title in both places: clients that predate the top-level field only know annotations.title, and whichever one a newer client checks first, it shows the same name.

How MCP Clients Use Tool Annotations

⁠

When I first wrote this post, annotations were mostly a promise. As of October 2026, readOnlyHint is the annotation that matters: most clients use it to skip the confirmation prompt, and some, like Codex CLI and the Claude Agent SDK, to run read-only tool calls in parallel.

ClientAnnotationsWhat it does
VS Code (Copilot)title, readOnlyHint, openWorldHintRuns read-only tools without asking, asks for the rest, and can ask you to review open-world results.
Claudetitle, readOnlyHint, destructiveHintRead-only tools can skip the prompt; destructive ones always ask.
ChatGPTreadOnlyHint, destructiveHint, openWorldHintTreats a tool without readOnlyHint as a write action that needs confirmation.
Codex CLIreadOnlyHint, destructiveHint, openWorldHintBy default, never asks for read-only tools and always for destructive ones; runs read-only calls in parallel.
Gemini CLIreadOnlyHint; any, through policiesIn Plan Mode, lets you approve read-only tools and blocks the rest; its policies can allow, ask or deny by annotation.
GoosereadOnlyHintIn Smart Approve mode, runs read-only tools without asking, always asks for readOnlyHint: false, and lets the model judge tools without the hint.

No client I checked acts on idempotentHint by default (a Gemini CLI policy you write yourself could match it), and Cursor, Windsurf and Zed don't document any use of annotations. Set it anyway: it documents whether a retry is safe, and it costs one line.

Can You Trust MCP Tool Annotations?

⁠

Not blindly. The specification is explicit: "clients MUST consider tool annotations to be untrusted unless they come from trusted servers". A malicious or buggy server can mark a tool that deletes your data as readOnlyHint: true, and nothing in the protocol stops it.

In practice, the trust decision is the moment you install or enable a server. Clients use annotations to relax their prompts for servers you chose to trust, and your own permission rules take precedence over the hints. Real guarantees come from deterministic controls: the permissions of the credentials the server runs with, sandboxing, or network policies. For a Kubernetes MCP server, that's the RBAC of the service account or user in its kubeconfig. Read-only doesn't mean harmless either: a read-only tool can still put Secrets, or text an attacker controls, like a pod's logs, in front of the model. The Kubernetes MCP Server's resources_get and resources_list read any kind, Secrets included, unless you deny it in its denied_resources setting.

The official MCP blog covered this in depth in Tool Annotations as Risk Vocabulary (March 2026). As of October 2026, new hints for sensitive data and untrusted output are proposed in SEP-1913, still a draft, and a Tool Annotations Interest Group is reviewing it and other proposals; none has been accepted.

Real-World Example: Kubernetes MCP Server

⁠

Let's look at how these annotations are applied in a real-world scenario using the Kubernetes MCP Server as an example.

The Kubernetes MCP Server provides a set of tools to interact with Kubernetes and OpenShift clusters, and every one of them declares its annotations. This is the definition of the helm_list tool in v0.0.67 (input schema and RBAC requirements trimmed). The server declares its tools with its own api.Tool type, which it converts to the official MCP Go SDK's mcp.Tool, and ptr.To comes from k8s.io/utils/ptr:

pkg/toolsets/helm/helm.go
{Tool: api.Tool{
	Name:        "helm_list",
	Description: "List all the Helm releases in the current or provided namespace (or in all namespaces if specified)",
	InputSchema: &jsonschema.Schema{ /* namespace, all_namespaces */ },
	Annotations: api.ToolAnnotations{
		Title:           "Helm: List",
		ReadOnlyHint:    ptr.To(true),
		DestructiveHint: ptr.To(false),
		OpenWorldHint:   ptr.To(true),
	},
}, Handler: helmList},

In this example, the tool helm_list is defined with several annotations:

  • Title: "Helm: List" provides a clear and concise name for the tool to be presented to end users.
  • Read-Only Hint: true indicates that this tool only reads data from the Kubernetes cluster without making any changes.
  • Destructive Hint: Marked as false since this function lists Helm releases without modifying anything. Strictly, the specification only considers it for tools that are not read-only, but setting it makes the intent explicit.
  • Open World Hint: true indicates that this tool interacts with an external system (the Kubernetes cluster), which is important for understanding its scope and potential dependencies. That's a judgement call: OpenAI's plugin reference, for example, says a private workspace isn't open-world just because it's hosted externally.

The idempotentHint: false in the JSON above comes from the Go SDK, which always sends readOnlyHint and idempotentHint; for a read-only tool, clients ignore it.

Compare it with helm_uninstall, which declares DestructiveHint: ptr.To(true) and IdempotentHint: ptr.To(true): it deletes a release, and uninstalling the same release twice has no additional effect. For more examples, check the toolsets of the Kubernetes MCP Server, where every tool declares its annotations.

Read-Only Mode and Destructive Tools

⁠

The Kubernetes MCP Server also uses its own annotations to decide which tools to expose. Two flags filter the tool list before any client sees it:

  • --read-only exposes only the tools annotated with readOnlyHint: true.
  • --disable-destructive hides the tools annotated with destructiveHint: true.
npx -y kubernetes-mcp-server@latest --read-only --toolsets core

npx comes with Node.js, and the server uses your kubeconfig. If it has more than one context, every tool gets a context argument, so the agent can reach all of them: give the server a kubeconfig with a single context (--kubeconfig), or add --disable-multi-cluster to keep it on the current one. Like most local MCP servers, it talks over stdio, so you don't run it in a terminal: you add this command to your MCP client's configuration (on native Windows, some clients need cmd /c npx instead of npx).

I checked what each flag does with v0.0.67 and the core, config and helm toolsets enabled (--toolsets core,config,helm; Helm isn't enabled by default). The exact number of tools depends on your cluster and configuration, but what each flag leaves out doesn't:

FlagsTools exposedWhat's left out
none23nothing
--disable-destructive17the six destructive tools, such as pods_delete, resources_delete and helm_uninstall
--read-only15every tool that changes the cluster, including the additive ones: pods_run and helm_install

Unlike a client trusting a remote server's hints, this is the server enforcing its own annotations, so a read-only session can't call a tool that writes.

It isn't a sandbox, though. The config toolset, enabled by default, includes configuration_view, a read-only tool that returns your kubeconfig, credentials included. An agent that can also run shell commands can pass them to kubectl and write anyway (#1415). For a read-only session, leave that toolset out, as the command above does, and give the server a kubeconfig whose user only has the permissions you want the agent to have. For finer control, the server's confirmation rules can ask the user before any tool with destructiveHint: true runs. They ask through MCP elicitation, so with a client that doesn't support it, the call proceeds unless you set confirmation_fallback = "deny".

Setting Tool Annotations in Go, TypeScript, Python and Java

⁠

Every official MCP SDK can set annotations. These snippets declare the same annotations as helm_list above, title included, each with the SDK's latest release as of October 2026. The Go SDK also sends the default idempotentHint: false, which the others leave out; to a client, both mean the same.

With the official Go SDK (v1.8.0), DestructiveHint and OpenWorldHint are pointers, because their default is true and the SDK needs to tell "unset" from false. Here, server is an *mcp.Server and helmList a typed tool handler:

destructive, openWorld := false, true
mcp.AddTool(server, &mcp.Tool{
	Name:        "helm_list",
	Title:       "Helm: List",
	Description: "List all the Helm releases",
	Annotations: &mcp.ToolAnnotations{
		Title:           "Helm: List",
		ReadOnlyHint:    true,
		DestructiveHint: &destructive,
		OpenWorldHint:   &openWorld,
	},
}, helmList)

With the TypeScript SDK (v2.3.1), annotations go in the tool's configuration:

import { McpServer } from '@modelcontextprotocol/server';

const server = new McpServer({ name: 'kubernetes', version: '1.0.0' });
server.registerTool('helm_list', {
  title: 'Helm: List',
  description: 'List all the Helm releases',
  annotations: { title: 'Helm: List', readOnlyHint: true, destructiveHint: false, openWorldHint: true }
}, helmList);

With the Python SDK (v2.3.0), they're an argument of the tool decorator, with snake_case field names:

from mcp.server import MCPServer
from mcp.types import ToolAnnotations

mcp = MCPServer("kubernetes")

@mcp.tool(title="Helm: List",
          annotations=ToolAnnotations(title="Helm: List", read_only_hint=True,
                                      destructive_hint=False, open_world_hint=True))
def helm_list(namespace: str | None = None) -> str: ...

On v1, the fields are camelCase (readOnlyHint=True): v1 accepts the snake_case names without an error but sends them as unknown keys, so clients see no hints.

With the Java SDK (v2.0.1), the tool builder from io.modelcontextprotocol.spec.McpSchema takes a ToolAnnotations instance:

var tool = McpSchema.Tool.builder("helm_list", Map.of("type", "object"))
    .title("Helm: List")
    .annotations(McpSchema.ToolAnnotations.builder().title("Helm: List")
        .readOnlyHint(true).destructiveHint(false).openWorldHint(true).build())
    .build();

Best Practices for MCP Tool Annotations

⁠

When implementing tool annotations, consider the following guidelines:

  • Be Accurate: Ensure that the annotations truthfully reflect the tool's behavior. Misleading annotations can lead to poor user experiences or safety issues, and connector and plugin directories reject servers whose annotations don't match their tools.
  • Set the Hints Explicitly: Don't rely on the defaults. A spec-following client assumes the worst for a missing hint, but not every client or server handles a missing value the same way.
  • Split Tools Whose Arguments Change Their Behavior: A tool that reads or writes depending on an argument can't be read-only. Two tools, one read-only and one not, let clients skip the prompt for the safe one.
  • Think about Users: Use clear and descriptive titles that help users (as opposed to developers) understand the tool's purpose. Set the top-level title, and keep annotations.title for older clients.
  • Consider Safety Implications: Be conservative with destructive and read-only hints. When in doubt, err on the side of caution.
  • Keep Annotations Up to Date: As tools evolve, ensure that their annotations are updated to reflect any changes in behavior or functionality. The Kubernetes MCP Server keeps a snapshot of its tools/list output in its tests, so any annotation change shows up in code review.
  • Use Annotations Consistently: Apply annotations uniformly across all tools in your MCP server to maintain a consistent user experience.

Conclusion

⁠

MCP tool annotations are a small part of the specification that most of the major MCP clients now rely on to decide when to ask before running a tool. Accurate annotations mean fewer confirmation prompts for safe tools, and a prompt before destructive ones in the clients that act on them.

In my Kubernetes MCP Server, annotations help users understand the difference between safe query operations and potentially destructive cluster modifications. They also let operators run the server in read-only mode, or without destructive tools, with a single flag.

As you build your own MCP servers, remember that annotations are about communication: they help bridge the gap between what your tool does technically and what users need to know to use it effectively. Just don't mistake them for a security boundary.

You Might Also Like

⁠
  • Introduction to the Model Context Protocol (MCP): The Future of AI Integration
  • Giving Superpowers to Small Language Models with Model Context Protocol (MCP)
  • Goose AI Agent Tutorial: CLI, MCP, Ollama and Recipes
  • Kubernetes MCP Server Joins the Containers Organization!
  • Agent Client Protocol (ACP): The LSP for AI Coding Agents Explained
Twitter iconFacebook iconLinkedIn iconPinterest iconEmail icon

Post navigation
Fabric8 Kubernetes Client 7.2 is now available!Kubernetes MCP Server Joins the Containers Organization!
© 2007 - 2026 Marc Nuri