How to Use Tool Annotations (readOnlyHint & destructiveHint) in Codex Skills

Tool annotations like readOnlyHint and destructiveHint are optional metadata hints that describe a tool's behavior to help client applications present and manage tools more intelligently, but they must never be used for security-critical decisions.

The ComposioHQ/awesome-codex-skills repository implements the Modular Codex Platform (MCP) specification, which defines standard annotation keys to declaratively signal whether a tool modifies external state or performs read-only operations. These hints enable smarter UI rendering and user confirmation flows while remaining strictly advisory.

What Are Tool Annotations?

Tool annotations are metadata fields defined within the annotations object of a tool's schema. According to the MCP specification as implemented in mcp-builder/reference/mcp_best_practices.md (lines 750-770), four standard hint keys are available:

  • readOnlyHint – Indicates the tool does not modify any external state. Safe to call multiple times without side effects.
  • destructiveHint – Signals the tool does change external state (e.g., writes to databases, sends emails, deletes files). Clients should warn users and require confirmation.
  • idempotentHint – Re-invoking the tool with identical inputs yields the same result.
  • openWorldHint – The tool may call external services or APIs not under the caller's direct control.

These annotations appear alongside inputSchema and outputSchema but remain hints, not guarantees. The runtime implementation might misdeclare behavior, so callers must still validate results independently.

Declaring Tool Annotations in Your Skills

You attach annotations through the standard decorator or registration pattern depending on your language. The ComposioHQ/awesome-codex-skills repository provides reference implementations in both Python and Node.js.

Python Implementation

In mcp-builder/reference/python_mcp_server.md (lines 94-100), the @mcp.tool decorator accepts an annotations dictionary parameter. Use this to mark read-only utilities that fetch data without side effects:

@mcp.tool(
    name="get_user_profile",
    description="Retrieves a user's profile without altering any data.",
    inputSchema={
        "type": "object",
        "properties": {"user_id": {"type": "string"}}
    },
    outputSchema={"type": "object"},
    annotations={
        "readOnlyHint": True,      # Safe to call repeatedly

        "idempotentHint": True,    # Same inputs yield same results

    },
)
def get_user_profile(user_id: str) -> dict:
    # Implementation that only reads from a database

    return {"user_id": user_id, "name": "Alice"}

Set readOnlyHint to True when your function performs pure reads. Omit destructiveHint or set it to False for these non-mutating operations.

Node.js Implementation

For Node.js servers, reference mcp-builder/reference/node_mcp_server.md (lines 173-180). The mcp.tool() registration function accepts an options object including annotations:

mcp.tool({
  name: "delete_file",
  description: "Deletes the specified file from the user's storage.",
  inputSchema: {
    type: "object",
    properties: {
      filePath: { type: "string" }
    },
    required: ["filePath"]
  },
  outputSchema: {
    type: "object",
    properties: { success: { type: "boolean" } }
  },
  annotations: {
    destructiveHint: true,   // Changes external state
    openWorldHint: true      // Interacts with file system API
  }
}, async ({ filePath }) => {
  await fs.promises.unlink(filePath);
  return { success: true };
});

Mark destructive operations with destructiveHint: true so client applications can trigger confirmation dialogs.

How Clients Consume readOnlyHint and destructiveHint

Client applications parse the annotations object to determine UX behavior:

  • readOnlyHint: true – Enables "preview" buttons, allows silent background execution, and skips confirmation prompts.
  • destructiveHint: true – Triggers red button styling, modal confirmations, and explicit user consent requirements before execution.

Server-side logic may also reference these hints for logging categorization or rate-limiting strategies. However, as emphasized in mcp-builder/reference/mcp_best_practices.md, never rely on these booleans for authorization or security enforcement.

Best Practices for Tool Annotation Hints

When implementing readOnlyHint and destructiveHint annotations in the Codex skill framework, follow these guidelines derived from the MCP specification:

  1. Use exact key names – The runtime expects readOnlyHint, destructiveHint, idempotentHint, and openWorldHint. Variations will not be recognized by compliant clients.

  2. Combine with schema validation – Annotations complement, not replace, inputSchema and outputSchema validation. Always define strict JSON schemas for data contracts.

  3. Document side effects honestly – If your tool calls external APIs that might charge credits or send notifications, include openWorldHint: true alongside destructiveHint.

  4. Never trust client-side hints – A malicious or buggy tool could declare readOnlyHint: true while performing destructive operations. Implement server-side validation and audit logs independently of annotation values.

Summary

  • Tool annotations like readOnlyHint and destructiveHint provide metadata in mcp-builder/reference/mcp_best_practices.md to describe tool behavior.
  • Declarative syntax in Python (@mcp.tool with annotations parameter) and Node.js (mcp.tool() with options object) attaches these hints at lines 94-100 and 173-180 respectively.
  • Client UX uses these hints to render confirmation dialogs for destructive operations or silent execution for read-only tools.
  • Security limitation – These are advisory hints only; always validate tool behavior server-side regardless of annotation claims.

Frequently Asked Questions

Can I rely on readOnlyHint for security decisions?

No. The readOnlyHint annotation is a UX convenience, not a security guarantee. A tool could falsely declare readOnlyHint: true while performing writes. Always implement server-side validation and authorization checks independent of these metadata flags.

What happens if I mark a destructive tool with readOnlyHint by mistake?

Client applications may execute the tool silently without confirmation prompts, leading to unintended data loss. Double-check your annotations in mcp-builder/reference/python_mcp_server.md or the Node equivalent to ensure destructiveHint: true is set for any operation that modifies state.

Should I use both readOnlyHint and destructiveHint on the same tool?

No. These hints describe mutually exclusive behaviors. readOnlyHint indicates zero side effects, while destructiveHint indicates state mutation. Choose the one that accurately describes your tool's behavior, or omit both if the tool has indeterminate effects.

Where can I find the complete list of supported annotation keys?

The definitive reference lives in mcp-builder/reference/mcp_best_practices.md (approximately lines 750-770) within the ComposioHQ/awesome-codex-skills repository. This file documents readOnlyHint, destructiveHint, idempotentHint, and openWorldHint alongside usage examples for both Python and Node.js implementations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →