How to classify MCP tools by risk (read, edit, delete)

To classify MCP tools by risk, sort each tool into read (it changes nothing), edit (it changes state but only adds to it) or delete (it may destroy or overwrite state). The MCP tools specification offers readOnlyHint and destructiveHint to help. Those are server hints, not guarantees, so set each tool’s final class yourself.
Map the three classes to the spec’s hints
The ToolAnnotations schema defines four optional hints on each tool, plus a display title. Two of the hints decide the class. readOnlyHint says whether the tool changes its environment. destructiveHint matters only when readOnlyHint is false, and says whether updates can destroy data or are additive only.
The wording in the schema gives the mapping:
| Class | readOnlyHint |
destructiveHint |
Spec wording |
|---|---|---|---|
| Read | true |
Not meaningful | “the tool does not modify its environment” |
| Edit | false |
false |
“the tool performs only additive updates” |
| Delete | false |
true |
“the tool may perform destructive updates to its environment” |
The schema documents a default of false for readOnlyHint and true for destructiveHint. A tool that sends neither hint therefore reads as a write that may be destructive. Until you have checked it, class it as delete.
Checked against the 2025-11-25 tools specification and its schema, and the draft schema, fetched 2026-11-13. No Claude Code version was tested for this post. The Claude Code MCP and permissions pages, fetched 2026-11-13, name no Claude Code version.
Read the hints as claims, not facts
The schema is explicit that the annotations are not reliable enough to decide on alone. Its comment reads: “all properties in ToolAnnotations are hints. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like title).” The same comment says clients “should never make tool use decisions based on ToolAnnotations received from untrusted servers.”
The tools page repeats the point as a MUST: “clients MUST consider tool annotations to be untrusted unless they come from trusted servers.”
For a server you do not run, the hints are evidence for your review. The decision belongs to your reviewed class list, enforced by client rules. Do not write allow rules from annotations alone.
Add open-world reach as a second axis
openWorldHint answers a different question. It says whether a tool “may interact with an ‘open world’ of external entities.” The schema’s example is a web search tool, which is open, against a memory tool, which is closed. Its default is true, so a server that omits it is treated as open.
Record it next to the class. A read tool with open reach can still send what it reads to an outside party, so the read class alone does not describe its exposure.
Steps to classify a server’s tools
- List the tools with the
tools/listrequest. Clients send it, per the tools page, to discover the tools a server offers. - For each tool, record the four hints as the server sent them. Mark any hint that is missing.
- Check what the tool does in the server’s own documentation or code. The name is not a signal. The tools page recommends limits on tool names, such as length and characters, and says nothing about meaning.
- Assign the class for the worst case the documentation allows. A tool that can both update and delete records is a delete tool.
- Set
idempotentHintonly after you have checked that repeating a call with the same arguments has no further effect. - Write the list down by full tool name, and keep it in version control with the client rules that use it.
Turn the classes into client rules
Claude Code checks deny rules first, then ask, then allow, and the first match decides. The tool-level authorization post covers the rule order and forms in detail. Here, a server named tickets shows how the three classes map onto rules. The tool names are chosen for the example, not taken from any server:
{
"permissions": {
"allow": [
"mcp__tickets__get_*",
"mcp__tickets__list_*"
],
"ask": [
"mcp__tickets__update_*"
],
"deny": [
"mcp__tickets__delete_*"
]
}
}
Three details in the permissions page affect this file:
- Allow globs must follow the literal prefix
mcp__and the server name. An unanchored allow such asmcp__*is skipped and approves nothing. - Ask is checked before allow, so a broad ask rule for a server also prompts for its read tools.
- In a settings file, Claude Code skips any
mcp__rule that has parentheses. You cannot write a rule that matches one tool’s parameter values there. The page points to the--disallowedToolsflag for that case.
Who sets each signal and who enforces it
The four signals do different jobs, and only one of them is yours.
| Signal | Set by | What Claude Code does with it | Limit |
|---|---|---|---|
readOnlyHint |
Server | Runs read-only calls in parallel with other read-only calls | Described as a parallelism hint, not a permission |
destructiveHint |
Server | Not described on the Claude Code MCP page | Meaningful only when readOnlyHint is false |
idempotentHint |
Server | Repeats a pending call once on a new connection to an HTTP server or claude.ai connector | Set only when repeated calls are safe |
| Your class list | You | Enforced through deny, ask and allow rules | Only as complete as the list |
The MCP page marks idempotentHint with an example that sets only that field:
{
"name": "set_issue_status",
"annotations": {
"idempotentHint": true
}
}
Fine print that changes the class
- Every hint is optional in the schema, so most servers will omit some of them. Plan for missing hints as well as wrong ones.
titleis a display name. The schema comment says the descriptive properties may not describe behaviour faithfully, so do not use it to classify.- A read tool with
destructiveHintset totrueis not a contradiction. The field has no meaning whenreadOnlyHintis true, so ignore it for read tools. - Claude Code repeats a pending call only once, and the MCP page names HTTP servers and claude.ai connectors as the cases where it does so. Do not assume the same repeat for other transports without testing it.
What to do with each class
Read tools can usually be allowed, because they change nothing and the class is the thing you reviewed. Keep a log of what they return, since a read can still expose data.
Edit tools change state, so an ask rule fits: the person sees each call and can refuse it. Record the approvals alongside the call, as the audit post describes for MCP calls.
Delete tools should be denied by default. Allow one only for a named team, through an explicit rule, and keep an approval step in the same place. The tools page’s human-in-the-loop SHOULD covers these calls.
Reviewed classes do not replace server-side checks. The tools page says servers MUST implement access controls on their own tools. For how client and server checks split that work, read the tool-level authorization post. For the wider model, see the AI agent governance hub. To start, list every tool each agent can reach, record its hints and your class for it, and write the deny, ask and allow rules from that list before you rely on any server’s annotations.
Sources
- Model Context Protocol, tools specification (2025-11-25), fetched 2026-11-13
- Model Context Protocol, schema (2025-11-25), ToolAnnotations, fetched 2026-11-13
- Model Context Protocol, draft tools specification and schema, fetched 2026-11-13; the annotation names and the untrusted-annotations rule match the 2025-11-25 text
- Claude Code MCP servers, fetched 2026-11-13
- Claude Code permissions, fetched 2026-11-13
Frequently asked questions
Does the MCP specification define read, edit and delete tool classes?
No. The ToolAnnotations schema defines four optional hints on a tool, readOnlyHint, destructiveHint, idempotentHint and openWorldHint, plus a display title. It does not name classes. The read, edit and delete classes in this post are a labelling scheme you build from readOnlyHint and destructiveHint, so keep the mapping in your own documentation with the rules that use it.
Can I rely on readOnlyHint to stop an agent from writing?
No. The schema says the annotations are hints that are not guaranteed to describe a tool faithfully. A read-only hint does not block a write. A deny rule blocks a call, an ask rule makes Claude Code prompt for each call, and an allow rule lets it run without a prompt. Enforcement comes from rules like these, or from access controls the server implements itself.
What class should a tool get if its server sends no annotations?
Treat it as delete until someone has checked it. The schema documents a default of false for readOnlyHint and true for destructiveHint, so a tool with no hints reads as a write that may be destructive. Check the tool's own documentation or code, then set the class in your reviewed list.
Should the tool name decide its class?
No. The tools page recommends a length limit, allowed characters, case sensitivity and uniqueness within a server for tool names, and says nothing about what a name means. A tool called get_record can still write, so classify each tool by what its documentation or code does, and use the name only to write the rule that matches it.
Do delete-class tools still need a person to approve them?
The tools page asks, for trust and safety, that there always be a human in the loop with the ability to deny tool invocations. That is a SHOULD for clients. Requiring an approval step for delete-class tools is the practical way to meet it, and the approval is only as good as the person reading the prompt.