Back to guides
GUIDE8 min read

MCP tools done right: permissions, not prompts

How to design agent tools that inherit the caller's access rights instead of a shared service account.

The shortcut everyone takes first

The fastest way to give an agent access to your systems is one broad tool, backed by one service account with wide read access, and a system prompt that says something like "only look up records for the customer the user is asking about." It works in a demo. It's also the shortcut most teams end up unwinding six months later, usually right after a security review asks the obvious question: what stops the agent from looking up someone else's data?

The honest answer, in that design, is: the prompt. And a prompt is not an access control.

Why prompt-level restrictions fail

Instructions in a system prompt are guidance, not enforcement. A sufficiently unusual user request, a prompt injected through a retrieved document, or just a model having an off day can all produce a tool call the instructions were supposed to prevent. None of that requires malice. It just requires the gap between "the model was told not to" and "the system cannot" to exist at all.

Permissions belong in the tool, not the instructions

The fix is to push access control down into the tool definition itself, so the question "is this user allowed to see this record" is answered by the system, every time, regardless of what the model decides to ask for. In practice that means each MCP tool is scoped and typed narrowly, and the permission check happens using the calling user's own identity, not a shared service account:

// tool: get_customer_history { "name": "get_customer_history", "description": "Return ticket and contract history for one customer", "auth": "caller_identity", "scope": "customer_id must match caller's entitlements", "parameters": { "customer_id": "string" } }

With this shape, there's no version of the conversation where the agent can retrieve a record the calling user couldn't have queried directly. The model can still misjudge when to call the tool. It can no longer call it with permissions it doesn't have.

What this looks like end to end

Every capability a KESDONE manifest declares becomes a tool built this way: typed parameters, an explicit auth model, and a scope tied to the caller rather than a static credential. The audit trail records who called what, with which identity, and what was returned, which is the difference between "we have logs" and "we can answer a real question about what an agent did and why."

A simple test for any agent tool you're designing: if you handed the tool's exact permissions to a junior engineer instead of an LLM, would you be comfortable with what they could see? If the answer is no, the tool is scoped too broadly, no matter what the prompt says.

Takeaways

  • A system prompt is guidance. It is not an access control, and shouldn't be relied on as one.
  • Permission checks belong in the tool definition, evaluated against the calling user's own identity.
  • A well-scoped tool makes the wrong data inaccessible, not just unlikely to be requested.

Want the full reference, or ready to see it on your own data?

See the Knowledge and Agent Context Product
Keep reading

Related guides