A Write Tool for an AI Agent Needs Four Gates and One Audit Trail
Giving an AI assistant read access to a cloud estate is a visibility decision, and 83 new write tools make it an autonomous governance decision instead. Giving it write access is a control decision, and the two have almost nothing in common. A read tool that gets authorisation wrong shows somebody data they should not have seen. A write tool that gets it wrong stops a production database.
ZopNight’s MCP catalogue grew from 122 tools to 275 in one release, a 125% increase: 70 new reads and 83 new writes. The writes cover schedules, resource groups, overrides, budgets, dashboards, autoscaler policies, notifications, recommendations, smart tags, roles, teams, members, integrations, unit metrics, and starting or stopping resources outright. That is a large surface to hand an agent, and it is the surface any closed-loop or auto-remediation workflow would drive. The interesting part of the work is not the tools. It is the four checks each one has to clear.
One Permission Check Is the Wrong Number for a Write Tool
A single authorisation check has no safe failure mode. Whatever that check gets wrong is granted, because nothing downstream disagrees with it. If the check is a token scope, a leaked token is total. If it is a role lookup, a stale role is total. If it is a per-tool allowlist, a tool added without an entry inherits whatever the default is, and the default is where the accident lives.
Four checks fail differently. They ask different questions, they read different state, and a mistake in one is caught by the others as long as none of them can grant on its own. That is the invariant worth naming: any gate can refuse, no gate can approve. A write executes only when all four independently agree, and a bug in any single gate degrades the system to three gates rather than to none.
The four are the org’s MCP write tier, the token’s scope, the RBAC policy table, and a group-scoped-resource guard on mutations that target one resource. RBAC is role-based access control: permissions are attached to named roles rather than to individuals, and a caller’s access is whatever the roles they currently hold allow at the moment of the call.
The tier gate is off by default. An org that installs nothing and configures nothing has no MCP write surface, which means the 83 new write tools are inert until somebody makes a deliberate decision. That default is doing more work than the other three gates combined, because it covers the population that has not thought about this yet.
Audit logging is worth one sentence on its own. Agent writes flow through the existing gateway into per-org Redis Streams and the same aggregator pipeline as everything else. There is no separate agent audit log, which matters because a separate log is a log somebody forgets to read.
Three Tiers, Because a Budget Edit and a Role Change Are Not the Same Risk
Grouping 83 write tools into one on/off switch would force every org into the worst-case decision. Grouping them per tool would produce a configuration surface nobody maintains. The split is by blast radius, into three tiers.
| Tier | What it covers | Worst realistic mistake |
|---|---|---|
| Tier 1: non-destructive config | Schedules, overrides, resource groups, budgets, dashboards, policies | A schedule fires at the wrong hour, or a budget alerts at the wrong threshold |
| Tier 2: resource lifecycle | Start and stop, autoscaler apply, smart-tag push, recommendation accept and dismiss | A running resource is stopped, or capacity scales against a policy nobody reviewed |
| Tier 3: org management | Roles, teams, members, integrations | Permissions change, which is the mistake that removes your ability to fix the other two |
The ordering is not about how often a tool is used. It is about how quickly a mistake becomes self-correcting. A wrong schedule is corrected by editing the schedule. A stopped instance is corrected by starting it. A wrong role change can remove the access needed to correct it, which is why org management is its own tier and why most teams should leave it off.
There is a second reason the tiers are ordered this way. Tier 1 is where an agent is genuinely useful and genuinely low-stakes: reading a cost trend, proposing a schedule, writing it, and letting a human look at it tomorrow. An org that enables tier 1 alone gets most of the value of agent-driven governance with a failure mode that is a config edit.
Fail-Open Authorisation Is Invisible Until It Is Not
Three MCP correctness fixes shipped alongside the expansion, and all three are the same class of bug: the system reported success while doing something other than what the caller asked.
| Fix | Previous behaviour | Why it survived |
|---|---|---|
| Viewer could trigger remediation | start_remediation and apply_recommendation_policy were mapped to recommendation:view in the policy table, so read-only access authorised a write | The mapping was wrong in a table, not in code, and the tool worked correctly for every caller who did hold write access |
| Routes with no policy entry were allowed | ApplyPolicy returned allow when no matching entry existed | A missing entry is exactly the case nobody writes a test for, and the tool appeared to work |
| Unknown tool returned an empty response | A call to a tool that does not exist looked like a successful call returning nothing | An agent reads empty as “no results”, not as “no such tool” |
The first two are now correct: those two tools map to recommendation:update, and ApplyPolicy denies any route with no matching entry. The change from allow to deny on a missing entry is the one to copy into your own systems. Fail-open authorisation is not a bug you find in review, because the routes it affects are the routes nobody remembered existed. It is found in an incident, by the person explaining why a viewer restarted a cluster.
The third fix returns HTTP 404 with a not_found code for unknown tools. That reads as a nicety until you consider who the caller is.
An Agent Cannot Tell an Empty Result From an Ignored Filter
Several filters were accepted by the API and ignored server-side: resource type on list_resources, provider on list_cloud_accounts and count_cloud_accounts, provider on get_recommendation_summary and count_recommendation_rules. The call succeeded. The response was well-formed. It was simply unfiltered.
For a human at a console this is a confusing afternoon. For an agent it is a wrong action, because an agent has no independent model of what the answer should look like. The Model Context Protocol specification says nothing about whether a filter was honoured, so the guarantee has to come from the server.
Run the same shape with an unknown tool name and an empty success response. The agent asked whether any resources match, got nothing back, and concluded there are none. It then does the thing you do when there is nothing to clean up, which is nothing, and reports that the estate is clean.
This is why honesty about failure is a correctness property rather than an ergonomic one for agent-facing APIs. A human treats an empty result as a prompt to check the query. An agent treats it as data. Every ambiguous response you leave in an MCP surface becomes a fact the agent is willing to state confidently to somebody who trusts it.
The Setup Step Is Part of the Security Surface
The other half of the release is smaller and easy to skip past: revealing an API token in Developer Settings now produces a ready-to-copy configuration block for the specific client you are connecting, across six of them (Cursor, Claude Code, Codex, Devin, Antigravity and Grok). The drawer appears on token reveal, with the server URL and the client’s own config format both copyable.
Hand-assembled connection config is where scoping mistakes are made. Somebody finds a config snippet written for a different client, adapts it, guesses at a field, and pastes in a token that is broader than the task needed because narrowing it required reading three pages. The token then lives in a config file for a year. Nothing in the four-gate model catches an over-scoped token that a human deliberately minted, which is precisely why the minting step deserves to be the easy path rather than the improvised one.
This works when the four gates are genuinely independent and each is evaluated per call. It fails when a gate is cached, because a cached role decision converts call-time enforcement back into a mint-time snapshot. None of this makes agent-driven cloud writes risk-free, and it should not be sold that way. It makes the risk explicit, tiered, defaulted off, and logged in the same place as every other write. That is the honest version of the claim, and it is the one worth building on.
