I spent the last few months building a Claude Code plugin that runs HubSpot in plain English. You type "find duplicate contacts and merge them," and it routes the request to one of 44 specialist sub-agents, each scoped to one corner of the CRM: contacts, deals, workflows, hygiene, and so on. It ships with 79 tools, and it is open source under MIT.
The AI part turned out to be the easy half. The hard half was a design question: how do you give an agent write access to a production CRM when you know it will eventually try to do something dumb at scale?
My answer became one rule. The agent does the work, and a person approves every single change before it lands. The part I want to defend in this article is where that gate lives: inside the tool itself, on every write, with no way around it. Not in a flag. Not in a client setting. In the tool.

Why optional safety fails
HubSpot ships its own agent tooling, and I used it properly before building mine. The Agent CLI, in beta as of July 2026, has a preview. It is a --dry-run flag you add to each command. Forget the flag and the writes, just run. HubSpot's own beta warning says that running it against a production account means you accept the risk of unintended changes, including data loss.
Their remote MCP server puts approval somewhere else: in the client. In Claude, for example, you can set write tools to require approval. You can also set them to always allow. And once someone flips that switch for convenience, every write runs unchecked. Most users, me included, click yes on permission dialogs until the dialogs stop appearing.
Both designs share the same assumption: the human will remember to be careful. I built on the opposite assumption. The tool should stay safe even when the human is tired, distracted, or has stopped reading the prompts.
What a write actually looks like
In my plugin, no tool call mutates the CRM. A write tool returns a preview of the affected records plus an action_id. That is all it can do. The actual mutation happens in a separate step, when the user runs approve <action_id>. Until then, nothing has touched HubSpot.
One complication: HubSpot has no dry-run API. So previews are built from reads. Before proposing an update, the tool fetches the records it would change and shows you their current state next to the proposed change. It is not a simulation of the write. It is a snapshot of what the writer would hit.
That distinction matters, and I will come back to it in the section about weaknesses.
The count re-check
For destructive operations, approval alone is not enough. You have to type the number of records you expect the operation to touch:
HubSpot approves a1b2c3 3
The tool then re-counts the affected records at execution time and compares. If the preview said 3 and execution would now hit 300, the operation refuses to run.
This exists because of the gap between preview and approval. CRM data changes constantly. A workflow fires, a colleague imports a list, and a form submission creates contacts. A filter that matched 3 records when you previewed it can match 300 by the time you approve. The typed count is the user's statement of intent, and the re-check verifies that reality still matches it at the moment of execution.
It also defends against a lazier failure: approving without reading. Typing a number forces you to have understood the preview, at least enough to know its size.
Undo and audit
Every approved create, update, or delete writes two things before it executes: an undo snapshot of the prior state and an entry in an audit log. If a change turns out to be wrong, you can reverse it. If someone asks what the agent did last Tuesday, the log answers.
The audit log turned out to be more useful than I expected. When an agent handles a fuzzy request like "clean up stale deals," the log becomes a record of every judgment call it made. You can review those decisions after the fact and turn the good ones into rules.
One handler set, three call paths

The architecture behind this is deliberately boring. Claude orchestrates. The sub-agents are stateless and call a single CLI over Bash. For speed, most calls route through a warm daemon that reuses one HTTP client and a schema cache. If the daemon is unreachable, the call falls back to an in-process path. There is also a plain synchronous CLI path.
Three call paths, and all three share one handler module. The approve-to-execute contract, meaning the destructive count gate, the undo snapshot, the audit entry, and the clearing of the pending action, is the same code everywhere.
This was the design decision I was most stubborn about. Safety logic that exists in more than one place will diverge. Someone fixes a bug in the daemon path and forgets the fallback path, and now your gate has a hole that only opens when the daemon is down. One handler set makes that class of bug impossible. The full component map, the agent roster, and the version history are in the architecture brief.
Where it still leaks
I said I would come back to the previous weakness. A read-based preview is not a transaction. The count re-check catches size drift between preview and execute. It does not catch content drift. If a record's fields change in that window but the count stays the same, the operation runs against data you did not see. I do not have a fix for this that does not require locking, and HubSpot's API does not offer locks. It is a documented limitation, not a solved problem.
Second leak: the install hook that provisions the plugin's Python environment never blocks the session, which means it also fails silently. A broken install looks fine until the first command errors. There is a log file, but you have to know where to check it.
And the whole thing is a beta. The test suite has 962 passing tests, but I am still finding rough edges.
One more honest thing
Agents wrote most of this code. My job was to define what safe means, review what came back, and refuse to ship anything I could not defend. I have been doing HubSpot implementations for over a decade, and that depth is exactly what made the review worth something. But the leverage has changed. One person who knows a domain deeply can now ship what used to take a team.
Try to break it
The repo is at github.com/promptmetrics/hubspot-claude. There is also a sister MCP server, hubspot-mcp, which carries the same approval model into any MCP client. If you want the design on one page before reading any code, start with the architecture brief
If you run HubSpot, try it against a sandbox or a free developer test portal, never your live account. You do not even need real data. Ask it to seed the test portal with sample records and let it work against that.
And if you think the gate design is wrong, I want to hear it. Is a typed count real friction or theater? Would you have put the approval in the client instead? Tell me where I'm wrong.
