Every AI Assistant Needs Its Own Grant and Its Own Audit Trail
The usual way to connect an AI assistant to a cloud platform is to mint an access token, copy it, and paste it into the assistant’s config file. It works on the first try, which is most of why it survives. It also creates a credential the platform cannot reason about: once 4 assistants hold copies of the same token, every call any of them makes arrives with the same identity, and the platform has no way to tell them apart.
That is not a theoretical gap. It is the reason you cannot answer “which assistant ran that write?” from an audit trail, and the reason turning off the one that misbehaved means turning off all four.
ZopNight and ZopDay now accept an OAuth sign-in from MCP clients. You paste the MCP server URL into the assistant once, it opens a browser, and a consent screen asks which organisations that assistant may reach and what it may do there. You approve, and the connection exists. The discovery step that makes this work without a per-vendor integration is RFC 9728, and the scoping rules come from RFC 9396. Personal access tokens still work, so nothing that already runs has to be rebuilt.
A Pasted Token Makes Every Assistant Look Like You
A bearer token is a secret that proves whoever holds it is you. Copying it into a second assistant does not create a second identity. It creates a second holder of the first one.
Everything a platform would want to show you about a machine caller depends on the grant being distinct per caller. When it is not, each of those questions collapses into the same unhelpful answer.
| Question | Pasted token shared across assistants | Delegated grant per assistant |
|---|---|---|
| Which assistants are connected? | Unknown, the token was copied off-platform | Listed under Connected apps |
| Which one made this call? | Indistinguishable, one identity | The grant that authorised it |
| Can I narrow one to a single org? | No, scope is fixed at mint time for all holders | Yes, chosen per assistant at consent |
| Can I disconnect one? | No, rotating the token disconnects every holder | Yes, revoke that grant alone |
The through-line is that a pasted token puts the platform on the wrong side of the boundary. The user transports the secret, so the platform learns about a connection only when a call shows up, and by then it cannot attribute it.
OAuth Moves the Grant From Your Clipboard to a Consent Screen
The delegated flow inverts that. The assistant does not receive a secret from you. It asks the platform for access, and the platform asks you.
Underneath, the gateway is the resource server and the OAuth front door, and the auth service is the authorization server. A call to the MCP endpoint without a usable token comes back with a challenge that points at the protected-resource metadata, which is how a client that has never seen this platform before discovers where to send the user. That handshake is part of the Model Context Protocol specification rather than anything specific to this platform. That discovery step is what makes “works with any assistant that supports custom MCP servers” true rather than a per-vendor integration list.
The part worth dwelling on is what the platform ends up holding. Consent writes one authorization row per consented organisation, stored the same way a personal access token grant is stored. The grant is now a record the server owns, not a string the user carries, and every capability in the table above follows from that single change.
The Consent Screen Is Where the Grant Gets Narrow
A consent screen that only asked “allow this app?” would move the secret without narrowing anything. The organisation picker is the part that does the work: the grant covers the organisations you ticked and no others.
The important design decision is whose word the platform takes for that list. The client asks for scope in its authorization request, and a naive server would record what was asked. Here the requested detail is stripped and the grant is derived from the organisations you selected, filtered through your actual membership. An assistant cannot widen its own access by asking for more, because what it asked for is not what gets written down.
Approval at consent time is also not the last check. Every call re-runs the same authorization path the platform already used for tokens, and all four conditions must hold.
This is fail-closed: any condition that cannot be evaluated denies rather than defers. It matters because the four conditions drift apart over time. Consent is a snapshot of what you approved on a Tuesday; RBAC is what your role permits right now. Re-deriving the intersection per call is what stops a months-old grant from outliving the role that justified it, which is exactly how long-lived machine credentials usually go wrong.
Re-deriving per call works when the four inputs are all live and quick to read. It fails when one of them is not: if the configuration service that holds the consented policies is unreachable, the write is refused rather than waved through on the strength of the older approval. That is the correct trade for a machine caller, and it is a real cost, because an outage in a dependency of the authorization path presents to the user as an assistant that suddenly cannot write. The alternative, treating a stale snapshot as good enough, converts every dependency outage into a window where a revoked or downgraded grant still works.
Multi-organisation write consent is deliberately all-or-nothing. Partial approval across a set of orgs would produce a grant whose behaviour depends on which org a call happens to name, and that is a grant nobody can reason about later.
Revocation Blast Radius Should Be One Assistant
Call the number of working connections you break when you shut off one misbehaving caller its revocation blast radius. With a shared pasted token, that number is every holder, which is why teams hesitate and leave the token live.
| Action | Shared pasted token | Per-assistant grant |
|---|---|---|
| Shut off one assistant | Rotate the token | Revoke that grant |
| Other assistants | All break, all need re-pasting | Unaffected |
| Time to take effect | However long re-pasting takes | Checked on the next call |
| Revocation blast radius | Every holder | One |
Revocation takes effect on the next call rather than when the token expires: a marker keyed on the token’s subject is written with a 16-minute TTL and checked on every MCP call, while the underlying per-organisation authorization rows are deleted behind it. The marker covers the window in which an already-issued token would otherwise still verify; the row deletion is the durable half. Neither alone is sufficient, which is why both exist.
One hardening detail is worth naming because it closes the obvious escape. The token-minting routes refuse any bearer that identifies itself as an OAuth access token. Without that, an assistant holding a revocable grant could use it to mint a long-lived personal access token and keep working after you revoked it, and your Connected apps page would show the disconnection you asked for while the access continued. This is the same class of problem as an identity that can create a second identity outside the policy governing the first: the control surface reports a state that the system is not actually in. Cloud governance work fails on exactly this gap far more often than it fails on a missing control, because a control that reports success is harder to notice than one that was never built. The audit trail is what makes the difference visible, which is why the grant, the revocation and the record all have to move together.
A Consent Screen Across Two Hosts Is Harder Than It Looks
The consent flow leans on a host-prefixed cookie set with the strictest same-site policy, which the gateway writes when the flow starts and reads back when you approve. On a deployment where the app and the API share a host, that is invisible. Where they sit on different hosts, the browser’s default credentials mode strips the cookie from the request, and approval fails on a flow that looked fine in every same-host environment.
The fix is for the client to send credentials on the OAuth paths, matched by prefix so unrelated routes stay same-origin. The ordering is the interesting part, because the two halves cannot ship together.
| Step | Change | What happens if you do this second |
|---|---|---|
| First | Server allows a specific origin and permits credentials | Nothing changes yet; the client is not sending credentials |
| Second | Client sends credentials on the OAuth paths | Consent works |
| Reversed | Client sends credentials while the server still allows any origin | The browser refuses the response outright, and the consent page cannot even load |
That refusal is a browser rule, not a server bug: a credentialed request cannot be answered with a wildcard allow-origin. Flipping the client half first therefore makes consent strictly worse than leaving it alone, which is why the change shipped behind a flag that defaults off and gets enabled only after the server side is in place.
The general lesson holds beyond this one cookie. Any authorization flow that spans two origins has an ordering constraint between the permission the server grants and the credential the client sends, and getting it backwards fails in a way that looks like the feature was never built.
If you are still pasting tokens into assistant config files, the practical next step is to connect one assistant over consent, scope it to a single organisation, and compare what Connected apps shows you against what your current audit trail can tell you about the same caller. The gap between those two views is the argument.
