6 min read

MCP tool descriptions that stop production mistakes

MCP tool descriptions prevent accidental production actions when they name the target, side effect, and confirmation requirement in plain language.

MCP tool descriptions that stop production mistakes

An MCP tool description can either stop an unsafe production call before it starts or hide the danger behind a friendly verb. Most accidental production actions do not begin with an agent deciding to cause damage. They begin when a vague description makes a destructive tool look interchangeable with an inspection tool.

Write every state changing tool description as a small operational contract: name the target system, name the side effect, and name the confirmation requirement. If one of those facts is absent, the description asks the model to infer a safety boundary that your code should have made explicit.

I have reviewed enough action interfaces to distrust labels such as "manage," "sync," "deploy," and "cleanup." They are convenient for the author and expensive for the person who has to explain why a test request landed in a live account. A good description makes the inconvenient details impossible to miss.

A tool description is an execution warning, not product copy

MCP tool descriptions should tell an agent and its human operator what will happen if the call succeeds. They should not sell the capability, summarize an internal subsystem, or repeat the tool name in a longer sentence.

The Model Context Protocol tool schema includes a human readable description alongside the tool name and inputSchema. The MCP specification also allows tool annotations such as readOnlyHint and destructiveHint. Those annotations help a client present tools, but the specification says clients must treat them as hints. They are not permission checks. A description therefore remains the place where the operator can read the actual consequence before a call reaches your service.

Consider these two definitions:

{
  "name": "delete_backup",
  "description": "Deletes a backup.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "backup_id": { "type": "string" }
    },
    "required": ["backup_id"]
  }
}
{
  "name": "delete_production_backup",
  "description": "Permanently deletes one backup from the Production PostgreSQL backup store. This removes a recovery point and cannot be undone. Ask the user to confirm the backup ID and its timestamp before calling this tool.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "backup_id": {
        "type": "string",
        "description": "Immutable backup ID returned by list_production_backups."
      }
    },
    "required": ["backup_id"],
    "additionalProperties": false
  },
  "annotations": {
    "destructiveHint": true,
    "readOnlyHint": false
  }
}

The second definition does more than sound cautious. It gives the agent a target, an irreversible result, a way to identify the object safely, and a required conversational pause. It also gives a reviewer enough information to reject a call before they investigate implementation details.

Do not assume a forceful verb solves this. "Destroy" warns better than "delete," but it still says nothing about which account, which data class, or how the tool handles confirmation. The description owns that context.

Put the target system in the first sentence

The first sentence should identify the exact system affected, including the environment or account boundary. An action against "the database" can mean a disposable local container, a shared test service, a staging tenant, or the production customer ledger. Those are different actions even when the API endpoint happens to match.

Use nouns that an operator recognizes from their own work. Say "Production payments account," "staging Kubernetes cluster," "customer tenant northwind," or "repository mobile-api release branch." Avoid internal nicknames unless every intended operator knows them and the name appears in the arguments.

This order works because it puts risk before mechanics:

[Target system]. [Action and result]. [Confirmation rule].

For example:

Production identity directory. Disables the selected user account and ends active sessions. Ask the user to confirm the username before calling.

The target must match the handler, not the author’s intent. If the tool accepts an environment argument, a description claiming "Updates staging" becomes false the moment a caller passes production. Either split the operation into environment specific tools or say plainly what the argument permits.

A split is usually easier to operate:

list_staging_feature_flags
set_staging_feature_flag
list_production_feature_flags
request_production_feature_flag_change

That design may look repetitive. Repetition is cheaper than a tool selector deciding that set_feature_flag looks appropriate and discovering only later that an optional argument pointed at production.

Names deserve the same discipline, but names cannot carry the whole warning. Tool lists truncate names. Agents sometimes focus on a description while choosing between similar names. Humans scan both under pressure. Put the target in both places where it fits, then make the description complete if the name disappears from view.

There is one exception: a tool that receives an immutable resource URI whose host already fixes the environment. Even then, say the host or account class in the description. A UUID does not tell a human whether it identifies a development record or a live customer.

State the side effect as a completed result

A safe description tells the reader what the world looks like after success. This forces the author to distinguish an observation from a change, a reversible change from a permanent one, and a request from its execution.

Compare the vague verb "manage":

Manages service deployments.

It conceals several very different outcomes. A deployment tool might create a release, promote an existing release, restart instances, change traffic allocation, roll back code, or only fetch status. Each operation deserves its own tool when it has a different failure mode or approval rule.

Use an explicit result instead:

Creates a deployment request for the Production catalog service. It does not change running instances. A release manager must approve the request in the deployment system.

Or:

Changes Production catalog traffic so the specified release receives 100 percent of requests. Existing requests may finish on the prior release. Ask the user to confirm the release version before calling.

The difference between creating a request and executing it matters more than the difference between HTTP POST and PATCH. A request object can still create work, consume a quota, or notify people, so describe that side effect too. But do not label it as a live deployment when it only opens an approval item.

Avoid euphemisms. "Retires" can mean archive, disable, delete, or terminate billing. "Cleans up" can mean removing temporary files or deleting the only retained copy of a customer export. Write the actual verb and object: deletes, disables, rotates, promotes, transfers, sends, charges, or publishes.

For operations with delayed effects, name the delay. A DNS change may propagate after the API returns. A user removal may stop future access while preserving audit records. A credential rotation may invalidate clients that still use the old secret. The agent needs this context to decide whether it should inspect dependent systems first.

Descriptions should also mention material scope when a single call affects many objects. "Deletes the selected record" is different from "Deletes all records matched by the supplied query." A batch endpoint hidden behind a singular verb causes predictable trouble.

Confirmation language must describe a real control

A confirmation sentence is useful only if the implementation and the operating process honor it. Writing "requires confirmation" on a tool whose handler runs immediately is theater, and agents will eventually expose it.

There are three distinct patterns, and descriptions should name the one you actually use.

  1. The agent asks the user in its own conversation, then calls the action. This depends on the agent following the description and does not stop a modified or careless client.
  2. The tool creates a request for a separate person or system to approve. The call itself has a side effect, but the described production change waits.
  3. An execution gateway pauses the action and requires human approval before it sends credentials or contacts the target system.

Do not collapse those patterns into "confirmation required." They provide different protection and different audit evidence.

Use verbs that identify the actor and the timing:

Before calling, ask the user to confirm the repository name and release tag.
Calling this tool submits a change request. The deployment system requires a release manager to approve it before any production release begins.
This action gateway asks a human to approve every call before it sends the request to the Production payments API.

The final wording describes an enforced boundary. The first wording describes an instruction to the agent. Both can be appropriate, but they are not equivalent.

Never tell the agent to ask for a vague confirmation. Tell it what facts the person needs to approve. For a deletion, that may be resource name, account, and retention status. For a transfer, it may be source, destination, amount, and currency. For a release, it may be service, version, and traffic scope. The description should not demand ritual; it should ask for the facts that catch a wrong target.

A confirmation rule also needs a scope. "Get approval before production changes" is weak when one session can run ten calls after a single approval. If the actual control approves a process for its lifetime, say so in the product documentation and avoid claiming every call gets an individual pause.

Read-only claims fail when handlers do hidden work

Match approval to the risk
Per-session authorization approves a new agent process once, while selected keys can require approval every use.

Call a tool read only only when its handler does not intentionally change the target system. The word describes behavior, not the HTTP method, the database permission name, or the author’s hopes.

A GET request can refresh a session, update a last accessed field, generate an export, start a report job, or trigger a cache fill with meaningful cost. A POST request can be harmless if it evaluates a dry run and does not persist anything. Inspect the handler and its downstream calls before choosing the label.

The MCP annotation readOnlyHint is useful for a client that wants to reduce friction around inspection tools. It is still a hint, so the server must enforce its own boundary. More importantly, the description should state any exception that surprises an operator.

This description is misleading:

Read-only tool for checking invoice status.

It fails if the endpoint creates a document view event, refreshes a third party token, or starts a remote calculation. A more honest form is:

Retrieves the current status of one Production invoice. It does not edit the invoice or charge the customer. The billing provider records this request in its access log.

An access log is normally acceptable for an inspection action. It becomes relevant if the target has compliance rules, cost per retrieval, or a workflow that reacts to reads. State such effects without turning every description into a legal notice.

Keep dry runs separate from execution where possible. A deploy tool with a dry_run boolean creates two safety profiles inside one definition. Agents can omit a default, misunderstand whether the server honors it, or reuse a payload without changing it. plan_production_deployment and execute_production_deployment make the distinction visible in tool selection, logs, and reviews.

The same rule applies to validation tools. "Validate configuration" sounds safe, but some providers allocate a resource or contact a live dependency during validation. If it does, describe it as an action and apply the relevant confirmation rule.

One broad tool creates approval mistakes

Tools should group operations by a shared consequence and approval boundary, not by the convenience of a single API client. A general purpose administration tool turns descriptions into a catalog of exceptions that neither a model nor a human will reliably read.

This is the pattern to avoid:

{
  "name": "admin",
  "description": "Administer users, deployments, secrets, and configuration across environments.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "operation": { "type": "string" },
      "environment": { "type": "string" },
      "payload": { "type": "object" }
    },
    "required": ["operation", "environment", "payload"]
  }
}

This definition destroys the useful unit of review. A reviewer cannot tell from the tool card whether the call will fetch status, rotate credentials, or delete a user. The operation string moves the important semantics into a late argument where it is easy to overlook.

Split by intent and risk instead:

get_production_deployment_status
plan_production_deployment
submit_production_deployment_request
rotate_production_service_credential
create_production_user_access_request

You do not need a tool for every endpoint. You need separate tools where target, side effect, or confirmation changes. A batch tool can remain a batch tool if it always touches the same bounded resource type and always needs the same approval. Its description must say that it can affect multiple objects and show how the caller limits selection.

Arguments need descriptions too. A tool description says what the operation does; argument descriptions constrain the dangerous choices. Use enums for environments and action types where you can. Reject unrecognized values server side. Do not put "production" in a freeform string and trust the description to save you.

A narrow tool also produces better audit records. When the journal says rotate_production_service_credential, an investigator understands the class of action before opening the arguments. When it says admin, they must reconstruct intent from a payload.

Write the description before the handler

Route MCP calls safely
Connect MCP-capable agents through the bundled sp mcp shim instead of handing them live credentials.

Drafting the operational contract before implementation exposes vague requirements while changing the interface is still cheap. If you cannot write one plain sentence about the completed result, you do not yet have a stable tool boundary.

Use this review sequence for every action tool:

  1. Write the target as an operator would identify it, including environment, account, or tenant.
  2. Write the completed result with a literal verb, and state whether the change can be reversed.
  3. Name the confirmation actor, the point where approval occurs, and whether it applies per call or per session.
  4. Compare the sentence against handler behavior, defaults, retries, and downstream APIs.
  5. Add argument descriptions for identifiers, scope controls, and any value that changes the target.

That fourth item catches the failures that polished docs miss. Retries can make a charge or message occur twice unless the downstream request uses an idempotency mechanism. Defaults can change an omitted environment into production. A handler can resolve a friendly name to several resources. The description cannot repair those implementation errors, but writing it forces them into the open.

A useful internal test is to remove the tool name and show only the description and input schema to another engineer. Ask them to predict what will happen after a successful call and what approval they expect. If their answer differs from the handler, fix the contract or the code.

Also test with ordinary language prompts. "Clear the old data," "make the new version live," and "fix Jordan's account" are exactly the sort of requests that make a broad tool look tempting. A safe agent should use an inspection tool first, ask for a missing identifier, or present the concrete action for approval. If it can jump from that prompt to production deletion, the failure begins in interface design long before model behavior enters the picture.

Error messages and results must preserve the safety boundary

Verify the action trail
Verify Sallyport's hash-chained audit history offline with sp audit verify, without an audit key.

A careful description loses much of its value when the tool result hides the executed target or when an error invites the agent to try a broader action. Return enough evidence for the agent and operator to verify what happened.

For a successful state change, return the canonical target identifier, the action performed, and the resulting state. Do not reply with only ok.

{
  "status": "completed",
  "target": {
    "environment": "production",
    "service": "catalog",
    "release": "2025.06.14-3"
  },
  "action": "traffic_promoted",
  "traffic_percent": 100,
  "request_id": "relreq_8a2f"
}

For an approval pause, say that nothing reached the target. This distinction prevents an agent from compensating for a call that merely waits for a person.

{
  "status": "approval_required",
  "action": "rotate_production_service_credential",
  "target": "production/catalog-api",
  "executed": false,
  "approval_scope": "this call"
}

Errors need the same care. "Forbidden" is technically accurate and operationally useless. Tell the caller whether the target was rejected, the environment was invalid, approval was absent, or the request failed after it reached the remote system. Never expose a secret in that explanation, and never advise the agent to repeat a state changing request blindly.

Idempotency deserves a visible result for externally meaningful actions. If a network timeout occurs after a remote service accepted a transfer or created a release, an agent must query the request status using a stable request ID. The retry path should not guess. Descriptions cannot express every retry rule, but a tool that makes irreversible calls should have a companion status tool and a result shape that supports recovery.

Descriptions need enforcement behind them

Plain language reduces bad selections, but it cannot stop a process that already holds an unrestricted production token. Put the credentials and the final network action behind a boundary that can deny, approve, and record the call.

Sallyport uses that arrangement for MCP connected agents: the agent uses the bundled sp mcp shim while the app keeps API and SSH credentials in its encrypted vault and executes approved actions itself. Its per session authorization and optional per call key approvals make the confirmation wording an enforceable behavior rather than a request for good manners.

That does not excuse weak tool design. The gateway sees the call that arrives. Your tool schema still decides whether the call says "delete this production backup" or hides deletion behind a generic admin operation. Enforce argument validation in the handler, constrain credentials to the intended target where the remote system supports it, and keep an audit record that identifies the process and action.

The Model Context Protocol authorization guidance makes the same larger point in another layer: authorization belongs in a protocol flow with explicit checks, not in a model instruction. Treat descriptions as a human readable contract. Treat server authorization, credential custody, and approval as the controls that make that contract true.

Take the most dangerous tool you expose today and rewrite its description without looking at its name. If you cannot name the production target, completed side effect, and approval scope in two or three direct sentences, do not offer that tool to an autonomous agent yet.

FAQ

What should an MCP tool description include for a production action?

A production tool description should name the exact target, state what changes, and say whether a person must approve the call. "Deploy service" fails because it hides the environment, the action, and the approval boundary. Write the consequence a tired engineer can understand in one read.

Are MCP tool names enough to prevent accidental production changes?

Tool names help routing, but names are often abbreviated and become stale as a tool grows. Put the safety meaning in the description because that is where the agent and operator can see the target, side effect, and approval requirement together. Keep the name narrow too, but do not rely on it alone.

How do I describe the target system clearly in an MCP tool?

Put the target system first: "Production billing API" is better than "API". Then name the state change, such as disabling a customer or creating a deployment. Finally, state the confirmation rule in plain language, including whether the tool only prepares a request.

How should I state an irreversible side effect?

Say what the call changes and, when relevant, what it cannot undo. "Permanently deletes the selected production database backup" is clear. "Manages backups" leaves an agent to guess whether it is listing, restoring, copying, or destroying data.

Is saying 'requires confirmation' sufficient in a tool description?

No. A sentence that says "requires confirmation" without naming who confirms and when creates false confidence. State whether the user approves each call, whether an external gateway asks for approval, or whether the tool only opens a request for a separate operator.

Should confirmation requirements be structured fields or plain text?

An agent may parse a structured confirmation field more reliably, but the description still needs a plain language safety statement for people reviewing the tool. Use both when possible. The structured field should not replace a readable consequence.

Can I label a tool read-only if it logs access or refreshes a token?

Read-only means the tool does not intentionally alter the target system. Listing resources, fetching status, and validating a request qualify only if their implementation does not refresh credentials, create records, or trigger background work. Audit the handler, not the verb in its name.

Should planning and execution use separate MCP tools?

Use separate tools when their consequences differ. A single "deploy" tool that can plan, release, roll back, and promote will eventually receive the wrong parameters. Separate inspection, request creation, and execution so each description can make one unambiguous promise.

How do I document a tool that can target staging or production?

If the target comes from an argument, describe the allowed values and call out production explicitly. Do not claim that a tool always needs confirmation if development calls bypass it. Split the tool or make the environment rule visible in the tool contract and enforced in code.

How can I test whether MCP descriptions prevent unsafe tool selection?

Test with prompts that use vague words such as "clean up," "ship," "fix access," and "remove the old one." Inspect whether the agent selects the tool, asks a useful clarifying question, and preserves the confirmation boundary. A tool that works only with perfect prompts is not safe enough for routine use.

Sallyport

Sallyport runs API calls and SSH commands for your AI agent. The keys stay in a local vault on your Mac; you approve each run and every action lands in a sealed journal.

© 2026 Sallyport · Open source under Apache-2.0 · Oleg Sotnikov