# MCP tool name collisions and safer agent behavior

An agent does not read an MCP tool catalog like a careful engineer reads an SDK. It routes from a compressed instruction to a likely action. If you give it `get_user`, `get_users`, `user_lookup`, and `admin_get_user`, then rely on a paragraph of caveats to separate them, you have designed a guessing game around authority.

MCP tool name collisions are not only duplicate identifiers that make a client reject a catalog. The worse collision is semantic: two callable actions sound interchangeable, but one reaches a broader system, carries a stronger credential, or changes state. I have seen teams call this a prompting issue after an agent picks the wrong action. Most of the time, it is an interface issue they shipped themselves.

The cure is not a giant taxonomy or a ceremonial naming committee. Give every action a name that says what it does, where it does it, and how far its authority reaches. Then write descriptions that define the boundary the name cannot carry. Make ambiguity visible in tests before it becomes an approval card, an unexpected API call, or a messy incident review.

## A collision is semantic before it is syntactic

A syntactic collision occurs when two MCP servers both publish a tool called `search`. Depending on the client, that may overwrite one entry, force a namespace, or produce a confusing catalog. You should fix it because behavior may vary between clients.

A semantic collision survives even when every identifier is technically unique. Consider these tools:

```text
search_customer
search_customer_records
lookup_customer
customer_admin_search
```

All four may appear valid to a compiler and understandable to the team that built them. To an agent asked, "Find the customer record for Maya Chen and update her address," they give weak routing signals. The agent must infer which system owns the truth, whether the action is read-only, whether it can search a whole tenant, and whether an administrative credential is acceptable.

Tool schemas do not rescue a vague catalog. A model may inspect argument names, but similar schemas often make the ambiguity worse. Both a read-only directory search and a production CRM search may accept `query`, `limit`, and `organization_id`. The fact that one returns data and the other can trigger enrichment or write an audit event may live only in a description that the model weighs less heavily than the apparent task match.

Treat these as different defects:

- Identifier collision: the client cannot present two tools consistently.
- Intent collision: two tools appear to satisfy the same user request.
- Authority collision: a broad credential sits behind a tool that sounds like a narrow one.
- Environment collision: similar labels conceal different accounts, regions, or production status.

The last three cause the expensive mistakes. A client can reject duplicate names. It cannot reliably tell you that `sync_contact` means "patch a production CRM record using an organization-wide token" while `update_contact` means "write to a local test fixture."

## Tool names must carry the routing facts

A useful name gives the agent the facts needed to choose before it reads a long description. For actions that touch external systems, I use this order: target system, object, verb, then scope when scope changes authority or consequence.

`crm_contact_update` is better than `update_contact` because it identifies the system. `crm_production_contact_update` may be better still if the same catalog includes a sandbox. `github_org_member_remove` is clearer than `manage_member` because it says which resource changes and that the outcome is removal.

Do not stuff every implementation detail into the name. Agents do not need `crm_v3_contacts_patch_with_bearer_auth`. They need distinctions that change selection. Versioning, transport, and authentication usually belong in the server implementation or description. Account, environment, side effect, and privilege boundary often belong in the name.

A practical pattern looks like this:

```text
<system>_<object>_<verb>[_<scope>]
```

Examples:

```text
billing_invoice_get
billing_invoice_send_customer
billing_production_refund_create
source_control_repo_issue_list
source_control_org_member_remove
warehouse_inventory_adjust
warehouse_inventory_adjust_dry_run
```

The pattern is not sacred. What matters is that neighboring names differ at the point where their effect differs. If `billing_invoice_send_customer` and `billing_invoice_preview_email` sit next to each other, the verbs and objects tell the model which one actually contacts a person. If the only difference appears in a boolean parameter buried in a schema, the catalog asks too much of routing.

Avoid vague verbs such as `process`, `manage`, `handle`, `run`, `execute`, `sync`, and `apply` unless the object itself makes the effect unambiguous. They are popular because product teams use them as umbrellas for several operations. That is exactly why they are poor tool names. A model treats an umbrella as permission to choose the broadest interpretation that completes the request.

## Descriptions define the boundary, not the marketing

The Model Context Protocol tool specification defines a tool with a name, description, and input schema. That is an interface contract, not a place for product copy. The description should answer four operational questions: what action occurs, what external target receives it, what scope applies, and what the tool refuses to do.

Compare these two descriptions:

```json
{
  "name": "crm_contact_update",
  "description": "Updates customer contact information in the CRM.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "contact_id": {"type": "string"},
      "address": {"type": "string"}
    },
    "required": ["contact_id"]
  }
}
```

```json
{
  "name": "crm_production_contact_update",
  "description": "Changes address, phone, or email fields for one existing contact in the production CRM. This writes immediately. Use crm_contact_search first when the caller supplies a name rather than a contact ID. It cannot create contacts, merge records, or update more than one contact per call.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "contact_id": {
        "type": "string",
        "description": "Stable production CRM contact ID, not an email address or display name."
      },
      "changes": {
        "type": "object",
        "properties": {
          "address": {"type": "string"},
          "phone": {"type": "string"},
          "email": {"type": "string"}
        },
        "minProperties": 1,
        "additionalProperties": false
      }
    },
    "required": ["contact_id", "changes"],
    "additionalProperties": false
  }
}
```

The second description gives the agent a sequence, names the consequence, and rules out tempting substitutions. It also places the disambiguating details close to the action rather than burying them in a separate operations manual the agent may never see.

Be blunt about side effects. Write "sends email immediately," "creates a charge," "deletes the remote branch," or "writes to production." Do not write "persists changes" or "performs the requested operation." Those phrases let a reviewer sound precise while hiding the only thing an agent and a human need to notice.

Input field descriptions matter for the same reason. If a field accepts a resource ID, say that a display name is invalid. If a date defaults to UTC, say so. Loose schemas with optional strings shift meaning into prose and let an agent improvise arguments that happen to parse.

## Broad access must never look like a convenient fallback

The most dangerous catalog contains a narrow tool and a broader tool that both appear to solve the same request. The broad one often exists for good reasons: an administrator needs emergency access, a migration needs cross-account search, or a support workflow needs an override. The mistake is exposing it as a peer with a friendly name.

Imagine these entries:

```text
support_ticket_get
support_ticket_update
support_admin_query
```

An agent wants context for a ticket. `support_admin_query` may search tickets, users, billing history, internal notes, and deleted records. If its description begins with "Query the support platform," the agent can select it because its broad coverage looks useful. The tool did what you named it to do. The design failed before the call.

Rename and constrain it:

```text
support_internal_cross_account_search
```

Its description should state that it searches internal support data across accounts, returns material outside the ticket record, and requires an explicit instruction naming the account boundary. If the workflow permits it, require an account ID in the schema rather than accepting a free-text query alone.

I argue against the usual recommendation to expose one "power tool" for flexibility. It is popular because it reduces server code and lets experienced operators do more with fewer calls. For an autonomous agent, it erases the distinction between ordinary work and exceptional authority. Make separate tools for materially different authority. More catalog entries are cheaper than explaining why a broad search disclosed the wrong customer history.

This applies to environments too. Do not offer `deploy` with an `environment` argument that defaults to production. Use separate action names when a wrong value has a different blast radius:

```text
release_staging_deploy
release_production_deploy
```

A schema enum still helps, but distinct names make production visible in the selection phase, the approval phase, and later in the audit record.

## Parameters cannot carry all the safety meaning

A parameter changes an action after the agent has chosen the tool. A name and description affect the choice itself. Teams blur those jobs when they build one universal tool with a large argument object.

This design looks compact:

```json
{
  "name": "repository_action",
  "description": "Performs repository operations.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "operation": {"enum": ["read_file", "create_branch", "delete_branch", "open_pull_request"]},
      "repository": {"type": "string"},
      "branch": {"type": "string"}
    },
    "required": ["operation", "repository"]
  }
}
```

It also puts a read operation, a write operation, and a destructive operation behind the same routing label. An agent that chose `repository_action` has already crossed the meaningful boundary. A reviewer sees an approval for an opaque umbrella action and must inspect arguments under time pressure.

Split it where the action changes class:

```text
repository_file_read
repository_branch_create
repository_branch_delete
repository_pull_request_create
```

Keep parameters for facts that vary within one action: repository ID, branch name, file path, commit message, or a page cursor. Do not make a parameter choose whether the call reads, writes, sends, charges, deletes, or reaches production.

The same rule applies to scope. `report_export` with `scope: all_accounts` turns a harmless-looking export into a cross-account extraction. If scope alters who can be affected or what data can leave, give that scope its own tool or require a stronger authorization path. The agent should not discover the authority difference only after it has filled a JSON field.

## Tool selection needs an ambiguity test suite

You cannot inspect a catalog once and declare it understandable. Test it with the requests users actually make, especially the incomplete requests that force an agent to infer scope.

Build a small selection suite for each server. You can run it manually with the agent client you support, or feed the catalog and prompts into a controlled evaluation harness. Record the selected tool, the proposed arguments, and whether a human would accept the call. Do not score only whether the task eventually succeeded. A broad tool that returns a correct answer is still a wrong selection when a narrower one existed.

Use prompts such as these:

1. "Find the invoice for order 1842." The expected choice should be a read-only billing lookup, not a general ledger search.
2. "Update Priya's phone number." The agent should ask which Priya if a stable contact ID is absent, rather than search and modify a likely match.
3. "Deploy the fix." The agent should ask for environment when the catalog includes staging and production actions.
4. "Remove Alex from the repository." The agent should distinguish repository membership from organization membership.
5. "Send the invoice." The agent should select a send action, not a preview generator or a generic invoice update call.

Add adversarial wording that resembles the bad tool's description. If `internal_cross_account_search` wins when a prompt says "find everything we have on this customer," your description may be technically honest but still too inviting. The correct behavior may be to choose a scoped search or ask the user to identify an account.

Keep the test transcript when you rename tools. It exposes regressions that a schema validator cannot catch. A catalog can remain valid while an innocent rename turns `billing_invoice_get` into `get_invoice`, where it competes with purchase, logistics, and legal systems.

## Approval screens should repeat the action in plain language

A human approval is a last checkpoint, not permission to make tool labels sloppy. If the approval presents only a low-level request such as `POST /v1/contacts/123`, the person approving must reconstruct intent from an endpoint and payload. That is a poor place to notice that the agent selected production CRM instead of a sandbox.

Carry the same business meaning through every layer. The tool name says `crm_production_contact_update`. The description says it writes immediately to one existing production record. The approval should say that the agent wants to change a named field on one production contact, identify the target account when available, and show the proposed changed values. The audit event should retain the tool identity plus the actual executed channel and target.

Do not make the approval wording more reassuring than the action. "Allow CRM update" conceals the difference between correcting a phone number and replacing an email address used for account recovery. Show the meaningful arguments, with secrets removed. If an argument contains sensitive customer data, display enough structure for review while respecting your data handling rules.

Sallyport's per-session authorization can establish that a particular agent process may act during its run, while per-call keys can require a separate approval for credentials that deserve scrutiny on every use. That division works best when the action labels give the person approving an immediate, accurate account of what the agent is requesting.

## Credentials and tool identity solve different problems

Keeping credentials out of an agent prevents a common failure: the agent cannot copy an API key into a log, source file, issue, or chat reply because it never receives the secret. That control does not make every request safe. The agent can still ask a gateway to execute the wrong tool with a legitimate credential.

Separate these questions during design:

- Can the agent obtain or expose the credential?
- Can the agent request an action outside the user's intended scope?
- Can a human see which process requested the action?
- Can an investigator verify what happened after the run?

A tool catalog addresses the second question. Session identity and approvals address the third. A tamper-evident record addresses the fourth. Each layer has a job, and none substitutes for another.

Sallyport keeps HTTP and SSH credentials in its encrypted vault and executes the action without handing those secrets to the agent. That reduces credential exposure, but the agent still needs a catalog whose names prevent it from asking a broader action merely because the name sounded close enough.

This distinction matters when teams say, "The agent cannot see the token, so the tool is safe." The token may be protected while the action is still overpowered. A read-only reporting credential and a production refund credential should not sit behind nearly identical entries just because both are isolated from the model.

## Namespaces help operators but do not excuse vague actions

Many clients display tools with a server-derived prefix, such as `crm.search_contacts` or `billing.search_contacts`. Use a namespace when your client supports it. It gives the agent and the operator one more routing clue, and it reduces literal duplicate names.

Do not depend on it as the only clue. Clients can shorten labels, flatten server catalogs, or show server names that mean little to a person reading an approval. A tool called `search_contacts` remains vague if one server reaches a test database and another reaches live customer data.

A better pair is:

```text
crm_production_contact_search
marketing_audience_contact_search
```

These names remain intelligible after a client adds or removes a prefix. They also make mixed catalogs safer when an agent connects to more servers over time.

Use server boundaries to group related authority, not to hide it. A server called `operations` that exposes billing refunds, production deployment, customer export, and personnel changes may be convenient for the team that owns it. It produces a crowded catalog with unrelated verbs and a wide credential surface. Split servers when separate domains have separate owners, credentials, approval expectations, or review paths.

## A catalog review catches the failures before deployment

Review a tool catalog beside a list of natural-language tasks, not in isolation. A name that feels obvious to its author often relies on context that disappears when thirty tools from six servers appear in one agent session.

Use this short review before you ship a new action:

1. Read the name alone. Can a person tell the external system, object, side effect, and unusual scope?
2. Put it next to every similar tool. Does one name describe broader access with a softer verb?
3. Remove the description mentally. Does the schema hide a read versus write, sandbox versus production, or single-record versus cross-account choice in an argument?
4. Ask an ambiguous user request. Should the agent ask a clarifying question, and have you made that safer than guessing?
5. Check the approval and audit labels. Do they preserve the same distinction as the tool name?

The right answer is often to reject a request for a convenient catch-all action. That refusal frustrates someone once, during implementation. An ambiguous tool frustrates the people who must investigate the unexpected call later, when the context has already vanished.

Keep the name specific, write the boundary plainly, and make broad authority hard to select by accident. An agent does not need more plausible choices. It needs fewer ways to mistake one permission for another.
