How a marketing-data MCP server should be designed: scope, read-only and cost
Connecting an AI to sales and ads data is a URL and an authorization. Doing it without the AI reading the wrong customer, deleting a dataset or scanning a billion rows is design — and most defects we found, including in our own, share the same shape: the rule was written and nobody enforced it.

MCP removed the friction of connecting an AI to a system. That is good and it is the problem: when connecting is easy, the server is built in a hurry and is born with full access, because it is the simplest way to make it work. Then the customers come, and full access becomes the risk of one customer reading another.
This article is the list of what a marketing-data MCP server needs in order to serve several companies on the same installation. Each item was born from a real defect — most found in an audit of our own implementation, and all with the same signature: no test broke, because a part that is not called breaks nothing.
1. The active project does not live in the token
The temptation is to record “this token is valid for project X” at authorization time and trust it until the token expires. The defect: if someone is removed from the team, their token stays valid — and the refresh copies the project forward without rechecking. We measured a 90-day window of access after removal. The rule: every call that touches data goes through a single funnel that checks membership NOW, with a short cache, and fails CLOSED — an error while checking permission cannot become a free pass.
2. Read-only is enforced, not declared
MCP lets you annotate a tool as “read-only”. The annotation is documentation: it prevents nothing. The server has to derive the required scope from the annotation and refuse the call on the spot, for every tool — including old ones registered through another path. We found a set of legacy tools where a read token could delete a dataset, because their registration skipped the check the new ones had.
Reading the authorization and throwing it away is worse than not reading it: it looks like it was considered.
3. The query runs isolated
If the AI can write SQL — and it should, that is what makes it useful — the server needs a sandbox that refuses: another customer’s schema cited by name, global database tables, functions that read files or the network, and any command that is not SELECT. And the refusal has to happen on the server, on the query tree, not in the prompt. A prompt is a suggestion; a parser is a rule.
4. The secret never crosses the model
Connecting a source requires the customer’s database password, the ad platform token, an API key. None of them should pass through the conversation with the AI — the model records everything it sees. The pattern we adopted: the tool creates the resource WITHOUT the secret and returns an authenticated link where the person pastes the credential on the product screen. The AI asks; the app completes. It is the same principle as AI and confidential data, applied to the act of connecting itself.
5. Every question has a cost, and the cost has a ceiling
A curious AI scans tables. We measured: one project’s AI connector read 143 GB in a month, 16 times more executions than the interface. It is not the AI’s fault; it is the absence of a ruler. The server needs to measure bytes read per call, attribute them to project and actor, have a concurrency limit per plan, and return the cost in the response — so the person knows what the question cost. We wrote about it in how much a question to the data costs.
6. Personal data is a scope, not a role
An analyst may run SQL and may not see e-mail and phone. That is not one more role; it is a separate dimension. The safe way to apply it is by a VIEW that is born without contact fields — not by dropping columns, because contact leaks inside JSON. And the list of what counts as contact comes by NAME PATTERN, not a fixed list: a fixed list did not know the tax-ID field of a new table and handed it to the “no personal data” scope.
7. What the tool promises has to exist
The most silent defect of all: tools registered in a list nobody served. Eleven audience tools stayed out of the connector for months because the HTTP route assembled its list by hand and the code’s list was another. Tests passed — they exercised the dead list. The rule: one list only, the served set derives from it, and a test that scans the disk for every tool group and breaks when one is not plugged in.
In CrazyLeads each of these seven items is code and has a guard that proves it — because each was, at some point, a defect.