Answers · MCP security
Are MCP tool annotations like readOnlyHint a security guarantee?
Last updated
TL;DR: No. readOnlyHint, destructiveHint, idempotentHint and openWorldHint are hints a server writes about its own tools, not permissions anything enforces. The MCP specification (2025-11-25) says clients MUST consider tool annotations untrusted unless they come from trusted servers, and its schema says annotations are not guaranteed to describe tool behavior faithfully and that clients should never make tool-use decisions based on annotations from untrusted servers. A malicious or buggy server can mark a tool that deletes files as readOnlyHint: true. Use annotations from servers you trust to decide when to ask for confirmation; enforce safety elsewhere: least-privilege credentials, a client allowlist of tools, human approval for anything that writes, sends or spends, and a check that the tool definitions haven't changed since you reviewed them.
What should I check, step by step?
1. Decide which servers you trust (you run them, or you reviewed and pinned them). Only for those should annotations change how your client behaves, for example auto-approving a readOnlyHint: true tool.
- For every other server, apply the spec's pessimistic defaults whatever it declares: assume a tool may modify its environment (readOnlyHint defaults to false), may be destructive (destructiveHint defaults to true), isn't safe to retry (idempotentHint defaults to false) and reaches the open world (openWorldHint defaults to true).
- Enforce read-only where it can't be argued with: give the server a credential or database role that can only read, so a tool that lies about being read-only still can't write.
- Keep a human in the loop: the spec says there SHOULD always be a human able to deny tool invocations, and that clients SHOULD prompt for confirmation on sensitive operations and show tool inputs before calling.
- Allowlist tools in the client and turn off the ones a task doesn't need, rather than relying on a server's labels to sort them.
- Record the tool definitions (names, descriptions, schemas and annotations) when you review a server, and diff them on every update: an annotation flipping to readOnlyHint: true, or a new tool appearing, should trigger a fresh review.
- Watch combinations, not single tools: a 'read-only' tool that reads private data, one that reads untrusted content, and one that can send data out together make the lethal trifecta for data theft, even if none of them writes anything.
- If you build a server, set annotations accurately on every tool. Missing annotations make well-behaved clients treat your read-only tools as possibly destructive, and inaccurate ones mislead the users who trust you.
What the annotations say
Tool annotations arrived in the 2025-03-26 spec revision. ToolAnnotations has title plus four booleans: readOnlyHint (the tool does not modify its environment; default false), destructiveHint (updates may be destructive rather than additive; default true), idempotentHint (repeat calls with the same arguments have no further effect; default false) and openWorldHint (the tool interacts with an open world of external entities; default true). destructiveHint and idempotentHint are only meaningful when readOnlyHint is false.
- The defaults are deliberately cautious: a tool with no annotations is treated as possibly writing, possibly destructive, not idempotent and open-world.
What annotations can and can't do
The MCP maintainers' March 16, 2026 post on tool annotations describes their main use as driving confirmation prompts: a readOnlyHint: true tool from a trusted server might be auto-approved while a destructiveHint: true tool gets a confirmation step.
- The same post says annotations don't make the model resist prompt injection. They are static metadata on a tool definition; nothing in them stops the model from following instructions it reads in a tool result.
- Read-only is about the tool's own side effects, not about what passes through it. A read-only fetch tool still carries whatever is in the URL it is asked to fetch to the outside world, and a read-only search tool still returns untrusted text into the model's context.
How do I ask Rightshold about this (free, no key)?
Add the MCP server https://www.rightshold.com/mcp to Claude, Cursor, ChatGPT, Grok or VS Code, then ask: “Before I let my client auto-approve this MCP server's 'read-only' tools, is it a listed server and have its tool definitions changed since they were recorded?”
{
"tool": "precall_check",
"arguments": {
"url": "<the MCP server's https URL>",
"tool": "<optional tool name>"
}
}- Free, no key. The answer combines the external registry entry, a live integrity probe against the recorded tool fingerprint, an invisible-text scan of the definitions served now, security flags and tool-name collisions. It is evidence to decide from, never a finding that a tool is read-only or safe.
- check_tool_integrity shows tools added, removed or changed since the definitions were recorded, so an update that quietly relabels or adds a tool is visible before your agent uses it.
Rightshold does not run tools, enforce permissions in your MCP client or verify that a tool's annotations match its behavior. Not legal advice.
What are the sources?
- MCP specification 2025-11-25: Tools (annotations are untrusted; human in the loop)
- MCP specification 2025-11-25: Schema reference (ToolAnnotations and defaults)
- MCP Blog: Tool Annotations as Risk Vocabulary: What Hints Can and Can't Do (March 16, 2026)
Related
- How to restrict an MCP database tool to read-only queries
- How to detect MCP tool description changes (rug pulls)
- How to vet an MCP server before installing it
- How to stop Log-To-Leak style prompt injection through MCP tools
Not legal advice. JSON · Markdown · All answers
Get a free API key in one call
No card and no email needed. 100 free credits; the license and policy checks stay free without a key.
curl -s -X POST https://www.rightshold.com/v1/accounts/signup -H "Content-Type: application/json" -d '{"accept_terms":true}'
export RIGHTSHOLD_API_KEY=rs_... # the api_key from the reply (shown once)
curl -s https://www.rightshold.com/v1/licenses/check -H "Authorization: Bearer $RIGHTSHOLD_API_KEY" \
-H "Content-Type: application/json" -d '{"license":"MIT","intended_use":{"commercial_use":true}}'
