Writing ·
How to restrict what an MCP server can do: six places a restriction can live
There are six places you can put a restriction on an MCP server, and they are not alternatives — most estates end up with several. What differs is what each one survives. Below, each in turn, and then the question that separates them.
1. In the client
Most MCP clients let someone approve tools, hide them, or require confirmation before a call. It is the quickest restriction available and it is genuinely useful for a person working interactively.
What it survives: that person, in that client, on that machine. A second agent connecting to the same server sees the full tool list, because the restriction was never in the server. It is a preference, not a property.
2. In the prompt
“Only use the read tools.” “Never call anything with delete in the name.”
What it survives: an agent that is trying to comply. The instruction sits in the context window alongside everything else — including whatever the agent has since read out of your database, which is not a safe place to take instructions from. It is also non-deterministic by construction: you are asking a model to enforce a rule on itself.
3. In the protocol’s annotations
MCP lets a tool advertise hints — readOnlyHint and friends — and a client may show them to a user
or act on them.
What it survives: nothing, on its own. A hint is asserted by the server and interpreted by the client; no part of the protocol obliges anyone to respect it, and a hint that says a tool is read-only does not make the tool read-only. Treat annotations as documentation.
4. In a proxy or middleware
A filter in front of the server: inspect the call, decide, forward or refuse. This is real enforcement — the first on the list that stops a determined caller.
What it survives: everything except its own bugs, and the cost is that it is code on the request path. Its guarantee is exactly as good as that code being right for every tool, every argument shape and every release, which is a promise somebody has to keep repeatedly rather than once.
5. In the database grant
Outside the server’s process entirely, enforced by Oracle, and it holds even if everything above it
is wrong. If the account cannot INSERT, no amount of talking anybody into anything produces an
insert. This is the one to get right first, and it is its own
article.
What it survives: bugs in the server, bugs in the proxy, a compromised token, and whoever wrote the server in the first place. What it cannot express is which question — a grant is per object and per verb, so “may read the customers table” is as fine-grained as it gets.
6. In the build
The tool was never generated. Not disabled, not filtered, not hidden — the method and the class are absent from the binary, because the object was not selected when the server was built.
What it survives: all of the above, and the ones nobody thought of. There is no rule to get right, no path to audit, no flag to misread, and nothing to talk into a different branch. A caller cannot invoke a method that does not exist, however the request is phrased and whoever is asking.
The question that separates them
For each of the six, ask: what has to keep being true?
| what has to keep being true | |
|---|---|
| Client | this client, configured this way, for every caller |
| Prompt | the model choosing to comply, every time |
| Annotations | a client voluntarily acting on a hint |
| Proxy | the filter being correct, on every path, in every release |
| Grant | Oracle enforcing its own privileges |
| Build | nothing |
Five of the six are promises about the future. One is a fact about an artefact that already exists, and you can check it without trusting anyone’s account of it: list the tools the server publishes.
This is not an argument for using only the last one
Layers 1 to 5 all do work the build cannot. A grant survives bugs in the code that was built. A proxy can bound how often calls arrive, whatever each one costs. Client confirmation is the right control for a human doing something irreversible. Use them.
The build layer also has a real cost, and it is the same one every time: changing what a server can do means generating it again, rather than editing a setting. That is deliberate, and it is the reason the guarantee holds — but on a fast-moving set of requirements you will feel it.
And it does not stop a tool you did generate being called at the wrong moment, with plausible arguments, in a sequence nobody intended. Nothing on this list does. That is what tool descriptions, the audit trail and call limits are for, and they are a different conversation.
The better question
“How do I restrict what this MCP server can do?” is usually asked about a server that already does too much — a general-purpose one pointed at a schema, being fenced in afterwards.
The cheaper question is what it should have been built to do. A server generated from a config that names forty objects has a smaller blast radius than any filter you can put in front of one that names four thousand, and it has it on the day it is built.
How does MCP DB Wizard restrict what an MCP server can do?
If you arrived here from a search, the short version first. An MCP server is the thing an AI agent connects to in order to do something outside its own conversation: read a row, call a routine, run a query. MCP DB Wizard is not one of those and is not a gateway you put in front of your database. It is a generator: you point it at an Oracle schema, tick the objects a particular job needs, and it writes and builds a working MCP server that exposes those and nothing else, which you then run as a Docker image. The restriction is therefore a property of the artefact rather than a setting on a running product.
So it lands at number six, which is the point of it. You pick the tables, PL/SQL routines, sequences and tested SQL statements of your own that one job needs, and the generator emits a server containing those and nothing else. An object you did not tick has no tool, no method and no class — so the restriction is not a setting anybody can change at run time, and there is no list of exclusions to keep up to date. The config is the whole of it, and it is reviewable text.
Number four we give you for accounts rather than tools. Servers are reached through a proxy: an API token identifies the caller, and an access matrix decides which configs that caller may drive. What it deliberately does not do is map callers to individual tools at run time — two teams needing different tools over one schema is two configs, for the reason the table above gives.
Numbers one, two and five stay yours, and we would rather say so than imply otherwise. Your client’s confirmation prompts are your client’s. The Oracle grants are Oracle’s, and we can only document the shape we suggest and tell you not to point the server at the schema owner. What we add on top is that the tools which exist at all, and the descriptions telling an agent when to use them, are decided by somebody before anything runs.
How do I get MCP DB Wizard?
It’s available from our GitHub repo as a Docker image, and the quickstart will get you a running server against your own schema.