# How to name tools so an agent picks the right one from fifty

> Give every tool a noun and a verb, a description long enough to choose on, and a warning on anything destructive, then measure how often two tools still look alike.

Source: https://voxgig.com/howto/name-tools-so-agents-pick-the-right-one

- Audience: agent-builder
- Level: intermediate
- Languages: typescript, javascript
- Verified: 2026-09-14
- Published: 2026-09-14
- Updated: 2026-09-24

## Short answer

Name every tool `noun_verb`, so the thing it acts on comes first and the catalog sorts into groups. Write a description a reader could choose on without seeing the others, and say in it when an operation cannot be undone. Then measure: count tools whose descriptions overlap, and count queries that tie between two tools.

---
## You will need

Node 22 or later, and a tool catalog with more than a handful of entries. The description is the
field a model reads, and [the tool
definition](https://modelcontextprotocol.io/specification/draft/server/tools#listing-tools) carries
it alongside the name and the input schema. The [guidance on writing tools for
agents](https://www.anthropic.com/engineering/writing-tools-for-agents) makes the same argument
about descriptions from the model's side.[^2]

Voxgig maintains sdkgen. This page compares the naming its go-mcp target produces with
operationId-derived names and with a catalog somebody curated by hand.

## Approaches compared

| Approach | When it fits | What it costs you | When to pick something else |
| --- | --- | --- | --- |
| [Hand-curated verb-first names](https://modelcontextprotocol.io/docs/getting-started/intro) | A small catalog somebody owns and revisits | Verbs collide first, so `get` and `search` appear across resources | The catalog is large enough that grouping matters |
| [Noun-first names with a measurement](https://modelcontextprotocol.io/specification/draft/server/tools) | Any catalog past a dozen tools, because the prefix does the grouping | A convention to hold to, and a measurement to run | The catalog is five tools and nobody is confused |
| [The sdkgen go-mcp naming](https://github.com/voxgig/sdkgen) | You generate the server and want entity-operation names everywhere | Names decided by the model rather than by you, for better or worse | Your tools do not map onto entities |
| [openapi-mcp-generator operationId names](https://github.com/harsha-iiiv/openapi-mcp-generator) | The description already has good operation ids | Names as good as the operation ids, which are often `getMetersById` | The description's operation ids were never designed |

Every one of these produces names, and only one of them tells you whether the names work. The
measurement is the part worth adopting whichever naming you use, because a catalog is judged by how
it behaves under real questions rather than by whether it follows a rule.

Noun first is worth the convention for a reason that has nothing to do with taste. A catalog sorted
alphabetically groups by resource. A model reading the list sees every meter operation together, and
a person scanning for one has a prefix to scan for.

## Say what cannot be undone, in the description

The safety note goes where the model reads, not in your documentation.

```js title="names.mjs"
if (/delete|retire|void|remove|purge/.test(tool.name) && !/cannot be undone/i.test(tool.description)) {
  findings.push(`${tool.name}: destructive and the description does not say so`)
}
```

A name that implies destruction is not enough. The description is the field that reaches the model
with the most context. A sentence there is what an agent works with when it decides whether to ask
its user first.

Keep descriptions to a shape. A verb, the object, the limits, and any warning, in that order, is
enough structure that two tools rarely read alike by accident. It also makes a missing limit obvious
to whoever reviews the catalog.

## Measure the ties, not only the rules

The second half of the check is behavioral.

```js title="names.mjs"
const scored = tools
  .map((t) => ({ name: t.name, score: words(`${t.name} ${t.description}`).filter((w) => terms.has(w)).length }))
  .sort((a, b) => b.score - a.score)
const top = scored[0].score
const tied = scored.filter((s) => s.score === top && top > 0)
```

Term overlap is cruder than what a model does, and that is fine here. A query that ties on a crude
scorer is a query where two tools genuinely share their vocabulary, which is exactly what you want
to find before an agent does.

## Check it worked

Audit a careless catalog and a rewritten one with the same ten operations.

```bash
node demo.mjs
```

```text output
before: 10 tools, 22 findings
   1  confusable pair
  10  thin description
   1  undeclared destruction
  10  unqualified name
      get and getById: descriptions overlap 0.60
  2 tied  "find the meter with serial SN-40199" -> meterSearch, updateMeter
  1 tied  "retire the meter that was replaced" -> updateMeter
  0 tied  "show me the invoice line items" -> nothing matched
after: 10 tools, 1 findings
   1  confusable pair
      meter_retire and invoice_void: descriptions overlap 0.62
  1 tied  "find the meter with serial SN-40199" -> meter_update_serial
  2 tied  "retire the meter that was replaced" -> meter_read, meter_retire
  1 tied  "show me the invoice line items" -> invoice_read
```

Twenty-two findings became one, and the third query goes from matching nothing to matching
`invoice_read`. Two results are worth being clear about rather than glossing over.

The remaining confusable pair is caused by the safety sentence itself: `meter_retire` and
`invoice_void` both say the operation cannot be undone, so their descriptions overlap. That is a
cost of the rule, and it is the right trade, because the two tools act on different nouns and no
question ties between them.

The first query still lands on `meter_update_serial` rather than `meter_search`, because the word
`serial` appears in both. Good names reduce ambiguity; they do not remove it, and the measurement is
what tells you where it is left.

```bash
node --test names.test.mjs
```

```text output
1..6
# tests 6
# suites 0
# pass 6
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 111.86389
```

## When it goes wrong

An agent picks a destructive tool for a read. The verb is buried and the description is short. Put
the verb in the name, and make the description's first clause the action.

Two tools are chosen at random between. Their descriptions differ only in the noun. Add what each
one returns and what its limits are, which is usually the distinguishing detail. Two tools that
still read alike after that are probably one tool with a parameter.

Renaming breaks every existing agent configuration. Tool names are an interface. Keep the old names
as aliases for a window, and measure which are still called. A tool catalog is an interface with no
versioning story, so the window is all you have.

The catalog is fine, but the agent still flounders. There are ninety tools and the useful ones are
five. Ship fewer tools to that agent rather than better names for all of them. A catalog is a menu,
and a menu with ninety items is read by nobody.

## When not to do this

Do not rename tools without a deprecation window. An agent configuration pointing at a name you
removed fails at the moment somebody needs it, and the failure looks like the agent broke.

Do not put the warning about a destructive operation only in the input schema. Some clients show a
schema and most show the description, and a warning nobody renders may as well not exist.

Do not accept generated names without running the measurement. The sdkgen go-mcp target and every
other generator name tools from the model they were given.[^3] A model with two words for one resource
produces a catalog with two words for one resource.

## Related how-tos

- [Run an MCP server against a mock API while you build tools](/howto/run-an-mcp-server-against-a-mock-api)

- [Issue and rotate per-agent API keys for an MCP server](/howto/per-agent-api-keys-for-mcp)

## Last verified

Verified 2026-09-14 against Node 22.22.2. Both output blocks are what the preceding command printed.

[^1]: The protocol has met this catalog. The [MCP
specification](https://modelcontextprotocol.io/specification/draft/server/tools) asks that a tool
name be 1 to 128 characters, case-sensitive and unique within its server. Its example of a collision
across servers is two of them each exposing a search tool. Its three example valid names are
`getUser`, `DATA_EXPORT_v2` and `admin.tools.list`, one in each of three casings, which is a
specification declining to have an opinion.

[^2]: The [post](https://www.anthropic.com/engineering/writing-tools-for-agents) also takes a
position on prefixes, up to a point. Its examples group tools by service and then by resource,
`asana_projects_search` and `asana_users_search`, which puts the verb last. It reports that choosing
between a prefix and a suffix had non-trivial effects on its evaluations, and that the effect varies
by model. Which way it cuts is left to the reader's own evaluations.

[^3]: `getMetersById` is what an operation id looks like when nobody designed it. The [OpenAPI
specification](https://spec.openapis.org/oas/v3.1.0.html) does not require the field, and where one
is present asks only that it be unique across the document, case-sensitive, and follow common
programming naming conventions. Which conventions is not said. Tools and libraries may use the id to
identify the operation, the specification adds, and a generator that names tools from it has taken
that permission at its word.