Designing MCP Tools That Cannot Exceed the Human

Authorization gets the attention, but a server with perfect OAuth and badly designed tools is still a liability. The question a customer's security lead will actually ask is: "Can the agent do anything the employee could not?" The answer has to be no, and it has to be demonstrable. This is a guide to designing MCP tools so that it is.

Start from permissions

The tempting way to build an MCP server is to generate one tool per API endpoint. It is fast and it is wrong. Your API was designed for integrations, which often run with elevated service permissions. An agent acts for one person, and the tools should reflect what that one person can do in your product.

So the first design artifact is not a tool list. It is a table: for each thing an agent will plausibly be asked to do, which human role can do it in the UI, what data it touches, and whether it is reversible. Tools come from that table. Anything with no human equivalent does not become a tool.

Identity from the token

Every tool call starts with a validated access token. The user and tenant come from its claims. From that point, the server must carry that identity through every internal call until the database, search index, or downstream API enforces it.

The failure mode to design against is the tool that takes tenant_id or user_id as an argument. The model chooses arguments, and the model reads untrusted content. A tool argument is a suggestion, never an authority. Bind identity in the handler from the token context and drop any argument that would let a caller name a different principal.

In practice this means the server's data access layer takes an identity object on every call and refuses to operate without one. If your existing API cannot enforce per-user access, you have found the real project, and it is better to know in the first week.

Narrow tools with honest descriptions

Tool descriptions are read by the model and are part of your attack surface and your safety story. A few rules that hold up:

  • One purpose per tool. search_invoices and get_invoice, not invoices with a mode parameter.
  • Classify reads and writes explicitly, in the tool metadata and in the name. Reviewers want to see the list of tools that can change state, and it should be short.
  • Say what the tool will not do. If update_customer cannot change billing details, say so in the description. Models follow descriptions; vague ones lead to surprising calls.
  • Bound inputs. Enumerations instead of free text where possible, maximum lengths, explicit formats. Validate server-side regardless.

Gate destructive actions

Any tool that deletes, sends, pays, or changes access needs a confirmation step, and the confirmation cannot be a boolean argument the model sets itself. Patterns that work:

  • A two-phase tool: the first call returns a preview and a short-lived confirmation token; the second call requires that token. The host can show the preview to the human.
  • Elicitation, where the client supports it, to put the decision in front of the person.
  • A policy in the server that requires an administrator-granted scope for destructive tools, so most users' agents simply cannot call them.

Combine with rate limits per user and per tool, so a runaway loop does not turn a reversible mistake into an irreversible one.

Treat outputs as untrusted input

Tool results go back into the model's context. If a result contains text a third party could have written (a ticket body, an email, a document), it can contain instructions aimed at the model. Your server cannot fully prevent that, but it can reduce the blast radius:

  • Return structured data, not prose, wherever the schema allows.
  • Bound output size and truncate with a marker.
  • Separate discovery tools from action tools so a single injected instruction has to cross two calls and two sets of permissions.
  • Consider flagging content that looks like instructions, so the host can render it as quoted data.

Usable audit events

Design the audit event before the first tool, because the event shape dictates what the handlers must carry. A workable minimum:

  • Timestamp, correlation ID, and session ID
  • Subject: user, tenant, and the client application that made the call
  • Tool name, a redacted summary of inputs (never raw secrets or full documents), and the outcome class (success, denied, error)
  • Duration and any downstream systems touched

Emit every call, including denials; denials are the interesting ones. Make the events available in a form a customer can ship to their SIEM. Enterprise reviewers increasingly ask for a sample of these events during the review, and having them ready shortens the conversation considerably.

The directory checklist

If you intend to list the server in a connector directory, the review criteria there overlap heavily with what an enterprise security review wants: specification-conformant authorization, a privacy policy, documented data handling, clear tool descriptions, support contact, and a working submission that passes automated checks. Read the listing requirements before the design week, not after the build. The fastest path through a directory review is a server whose design document already answers every question on the form.

Prove it

Everything above is a claim until it is tested. Before handover, run the attacks yourself: cross-tenant reads through every tool, tool arguments naming another user, injected instructions in every output field, replayed tokens, oversized inputs, call floods. Write down what happened. That record, alongside the threat model and the audit event sample, is the evidence pack that turns "trust us" into "here is how we checked".

We build to this design on every server build, and it is the standard our security audit measures existing servers against.