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

> Learn to use tool annotations like readOnlyHint and destructiveHint in Codex Skills. Enhance client application tool management with these metadata hints from ComposioHQ.

- Repository: [Composio/awesome-codex-skills](https://github.com/composiohq/awesome-codex-skills)
- Tags: how-to-guide
- Published: 2026-04-26

---

**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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/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:

```python
@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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/node_mcp_server.md) (lines 173-180). The `mcp.tool()` registration function accepts an options object including `annotations`:

```javascript
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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/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.