Tools as the Model's Hands: Designing Tool Schemas That Don't Get Misused

2026-06-05


Tools as the Model's Hands: Designing Tool Schemas That Don't Get Misused

When you give Claude a tool, you are giving it a way to act on the world — not just talk. A poorly designed tool is like handing someone a master key and calling it a door handle. The schema you write is your first line of defence.

The jargon

Tool schema — a JSON description you pass to the API that tells Claude what a tool is called, what it does, and what arguments it accepts. Claude reads this at inference time; it doesn't see your actual code.

JSON Schema — a standard format for describing the shape of data. You use it to say "this field must be a string" or "this argument is required."

Tool call — Claude's decision to invoke a tool, expressed as structured output your code then executes. Claude proposes the call; you run it.

The lesson

Claude decides which tool to call — and how to fill in its arguments — based almost entirely on what you write in the schema. If the description is vague, Claude guesses. If the argument types are too loose, Claude passes whatever seems reasonable. Both cause real problems when the tool touches a database, an API, or a file system.

The schema is a contract. Write it like one.

How it works

Here is a weak schema for a tool that deletes records:

{
  "name": "delete_record",
  "description": "Deletes a record.",
  "input_schema": {
    "type": "object",
    "properties": {
      "id": { "type": "string" }
    }
  }
}

Three problems: the description says nothing about consequences, id is unconstrained, and nothing is marked required. Claude might pass an empty string, a name instead of an ID, or call it when a softer action would do.

Here is a tighter version:

{
  "name": "delete_record",
  "description": "Permanently deletes a single client record by its UUID. Use only when the user has explicitly confirmed deletion. Prefer archive_record for reversible removal.",
  "input_schema": {
    "type": "object",
    "properties": {
      "record_id": {
        "type": "string",
        "format": "uuid",
        "description": "The UUID of the record to delete. Must match the pattern xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx."
      }
    },
    "required": ["record_id"]
  }
}

The description now tells Claude when to reach for this tool versus an alternative. The argument is named unambiguously, constrained to UUID format, and marked required so Claude cannot omit it.

When to reach for it / when not to

Do this whenever a tool has side effects — writes, deletes, sends messages, charges money. The tighter the schema, the less your execution layer needs to second-guess the model's intent.

For pure read tools with no consequences (looking up a price, fetching a document), a lighter schema is fine. Over-engineering read-only tools just adds noise.

One pattern worth knowing: if you want Claude to choose between a destructive and a safe action, name them differently and say so in both descriptions. Claude pays attention to contrast.

Try it

Pick one tool you have defined or are planning to define. Read its description as if you know nothing about your system. Does it say what the tool shouldn't be used for? Does it name a safer alternative? Add both this week and watch whether Claude's tool-selection rate shifts.


Don't miss what's next. Subscribe to My Claude Daily Learning: