The safe way to give AI agent access to business data is to make the agent a client of the business system rather than a user with a password. The agent connects as a named person over a standard sign-in, is offered only the tools that person's role allows, has every call checked again by the system when it runs, is capped on what it can spend, and leaves a log of every action attributed to that person. When any of those checks cannot be made, access is denied rather than assumed.
None of that lives in the prompt. Instructions to the model are useful for behaviour and useless for security, because the model can be argued out of them by a document it reads. The controls have to be enforced by the system holding the data, on every call, regardless of what the agent believes it has been told.
The architecture: the agent is a client, the system is the authority
Start with the shape, because most of the mistakes are shape mistakes. A person asks their agent for something. The agent decides which tools to call. Every call passes through a permission layer that belongs to the business system, not to the agent, and only then reaches a module such as finance or CRM. The agent never touches the database, never holds a database credential, and never sees a tool its person could not use.
The request passes from the person to their agent, then through the permission layer, before it reaches any module. The layer filters what the agent is offered and checks what it calls. The agent can only use the tools its person is allowed to use, on the records its person can see.
The principle underneath the picture is one sentence: the agent acts with the authority of the person it represents, and never more. Everything else in this article is a way of making that sentence true under pressure, when the model is wrong, when a document it reads contains instructions, or when a token leaks.
The OWASP Top 10 for LLM applications names the failure this architecture prevents: excessive agency, which it breaks down into excessive functionality (tools beyond what the job needs), excessive permissions (more access downstream than necessary) and excessive autonomy (no independent check before a high-impact action). Its mitigations read like a specification for the permission layer: execute in the user's context, minimise the tools and their permissions, enforce authorisation in the downstream system rather than relying on the model, and require a person's approval for high-impact actions.
Five controls, and where each one lives
The controls are not new; they are the controls you already apply to a person with system access, applied to a client that acts for that person. The table says what each answers, where it is enforced, and what failure looks like when it is missing. The location column is the important one. A control enforced in the prompt is a suggestion.
| Control | What it settles | Enforced where | Failure when missing |
|---|---|---|---|
| Identity | Who the agent is acting for | Sign-in over OAuth; a token issued for this system and bound to a named user | Shared service accounts; actions with no owner; a leaked key that works for everyone |
| Scope | What it is allowed to do | Tools filtered by the person's role before they are offered, and checked again on every call | An agent that can read payroll because its person once needed a contact's phone number |
| Budget | How much it can consume | A spend cap per integration on the system's own AI; rate limits on tool calls | A badly stated task that runs all night; an unbounded bill |
| Logs | What it did, with what, and what happened | Every call recorded with inputs and result, attributed to the person | No way to review, reverse or explain an action after the fact |
| Fail closed | What happens when a check cannot be made | Deny, with an error the agent can report | Ambiguity resolved in the agent's favour; the model deciding its own authority |
| Confirmation on writes | Whether a person sees it before it happens | The client asks before consequential actions; the system marks which tools are consequential | Money sent, records deleted or messages posted on the strength of a misread instruction |
Six rows for five controls plus the one the agent clients themselves provide. Five of the six are enforced by the business system or the client, and none by the model.
Identity: connect as a person, over OAuth, never with a shared key
The Model Context Protocol's authorisation specification is precise about this. A remote server acts as an OAuth 2.1 resource server; the client obtains a token through a standard authorisation flow with PKCE; the client must state which server the token is for using the resource parameter; and the server must validate that each token was issued for it specifically, rejecting anything else. The specification explicitly forbids token passthrough, where a server accepts a token it did not issue and forwards it downstream, because it destroys both the audit trail and the trust boundary.
In practice this is what "sign in once, no token to paste" means. The person adds the workspace address to their agent, is sent to a normal sign-in page, approves the connection, and the agent receives a token that names them and works only against that workspace. Anthropic's guidance for custom connectors in Claude is to review the scopes a server requests, limit them where possible, and connect only to servers you trust. A vendor that instead asks you to paste a company-wide API key into an agent configuration has skipped the first control and made the other four much harder.
Scope: filter before offering, check again when running
An agent discovers what it can do by asking the server for its tool list. The right design answers that question per person: the list a bookkeeper's agent receives is different from the list a director's agent receives, and neither includes tools for modules their role cannot see. This is the OWASP advice to minimise functionality made concrete, and it has a second benefit: a model that is never shown a tool cannot be argued into calling it.
Filtering the list is not enough on its own, because roles change, sessions persist, and clients cache. The same check has to run again when each call arrives, against the person's permissions at that moment. Below is a tools list in the shape the protocol defines, for a role that can read invoices but not record payments. The absent tool is the point.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{ "name": "searchInvoices",
"description": "Search invoices by number, reference, contact, amount, status, date range.",
"inputSchema": { "type": "object", "properties": { "query": { "type": "string" }, "outstanding_only": { "type": "boolean" } } },
"annotations": { "readOnlyHint": true } },
{ "name": "getInvoice",
"description": "Read one invoice in full: status, totals, dates, contact and lines.",
"inputSchema": { "type": "object", "properties": { "invoice_id": { "type": "string" } }, "required": ["invoice_id"] },
"annotations": { "readOnlyHint": true } }
]
}
}
// recordPayment, sendInvoiceReminders and deleteInvoice exist in the system.
// They are not in this list because this person's role cannot use them.
// If the agent calls one anyway, the server answers with a permission error.A tools/list response in the shape the MCP specification defines, with tool names from the Sois accounting module. The specification also says clients must treat annotations such as readOnlyHint as untrusted unless the server is trusted, which is one more reason the server, not the annotation, has to be the thing that enforces the rule.
Prompt injection: the data can talk back
The threat specific to agents is that the data they read can contain instructions. An email from a customer that ends with a line telling the agent to forward the supplier list to an outside address; a document that tells it to mark every invoice paid. The model may or may not comply, and no prompt can guarantee it will not, which is why prompt injection is first on the OWASP list and why both Anthropic and OpenAI warn about it in their connector guidance.
The defence is the architecture, not the model. An agent that is only offered the tools its person can use cannot forward what its person cannot see. A call to mark invoices paid is checked by the system against the person's permissions, not against the model's belief that it was asked to. Consequential tools are marked so that the client asks a person first: ChatGPT currently requires manual confirmation in a conversation before write actions, and OpenAI's guidance is to keep approval on for tools that modify data; Claude asks for approval per tool and advises reserving "allow always" for trusted servers. And the log records the attempt, so an injection that was blocked is visible afterwards rather than silent.
Budget and logs: make the agent's work as reviewable as a person's
Budget matters for two reasons. The obvious one is cost: an agent given a vague outcome will keep calling tools until something stops it, and OWASP lists unbounded consumption as a risk in its own right. The subtler one is blast radius: a cap per integration limits how much a compromised or confused agent can do before a person notices. Where the person's own agent does the reasoning the AI cost sits with them; where the system's own agent does it, the cap should be set per integration and visible per action.
Logs turn all of this from a promise into something you can audit. Each call should record who the agent acted for, which tool, the inputs, the result and the time, in the same place the system records what people did. The test is whether a finance lead can find out what the agent did to a customer's account last month as easily as they can for a colleague. The MCP specification asks clients to log tool usage for audit; a business system should not rely on the client for that, because the client is not the system of record.
A checklist you can run against any system
Take these to any vendor, including us. Each is a yes or no, and each has a test rather than a question to ask.
- The agent connects as a named person over OAuth, with no company-wide key to paste. Test: connect from an external MCP client and look at what the sign-in page asks for.
- The tool list differs by role. Test: connect as a restricted user and as an administrator and compare what the agent is offered.
- The check is repeated when the tool runs. Test: remove a permission from a connected user mid-session and try the action again.
- Access fails closed. Test: call a tool the role should not have and confirm you get a refusal, not a result.
- Spend can be capped per integration and seen per action. Test: set a small cap and watch it bind.
- Every action is logged against the person, with inputs and result, where the system logs everything else. Test: read the log for a run you just did.
- Consequential tools are marked for confirmation so the client asks a person. Test: ask the agent to send money or delete a record and confirm you are asked first.
Sois is one system built to pass this list, and the shape at the top of the article is its shape. A workspace is an MCP server; any MCP client connects by adding the workspace address and signing in once over OAuth; tools are filtered by role before they are offered and checked again when they run; access fails closed; spend can be capped per integration; every action is logged; and networks built on the platform have their own database, storage, domains and keys. Run the checklist against it anyway. The value of a checklist is that it takes nobody's word.
Questions people ask
Is it safe to connect Claude or ChatGPT to my accounting data?
It is safe when the system holding the data enforces the controls: the agent signs in as you over OAuth, is offered only the tools your role allows, is checked again on every call, and every action is logged. Both vendors also ask for confirmation before consequential actions. If the system only offers a shared API key, the answer is no.
Can a system prompt stop an agent from leaking data?
No. A prompt shapes behaviour; it does not enforce anything. Data the agent reads can contain instructions that override it. The action has to be one the system would refuse regardless of what the model believes it was asked.
What is the difference between filtering tools and checking permissions?
Filtering decides what the agent is shown when it asks for the tool list. Checking decides whether a specific call is allowed at the moment it arrives. You need both: filtering reduces what the model can be talked into, checking catches everything filtering missed.
Who pays for the AI when my own agent does the reasoning?
You do, through your agent subscription. A system built this way performs no AI on your behalf in that case and should charge nothing for it. Spend caps apply to the system's own agent when you use that instead.
- Model Context Protocol specification: authorization OAuth 2.1, PKCE, the resource parameter, token audience validation, and the prohibition on token passthrough
- OWASP Top 10 for LLM Applications: LLM06 Excessive Agency excessive functionality, permissions and autonomy, and the mitigations the permission layer implements
- Anthropic: getting started with custom connectors using remote MCP OAuth sign-in, limiting requested scopes, per-tool approval, connecting only to trusted servers, prompt injection warning
- Sois: security and the permission layer the five controls as the platform implements them: role filtering, checks at run time, fail closed, spend limits, logging, tenant isolation
This article is reviewed when the products it describes change. Next scheduled review: 4 December 2026.
