MCPDBWizard

Writing  · 

Role-based access for an MCP server: necessary, and not sufficient

Get the database roles right. Everything below assumes you have: an account per purpose, granted one object at a time, and never the schema owner — the shape is in choosing the account your server connects as, and Oracle has enforced that boundary for thirty years better than anything we could put in front of it.

We are not a replacement for that. We are the layer above it, and this article is about the three things a role cannot do no matter how carefully you grant it.

A role says who may touch what. It says nothing about when

It’s a classic case of ‘syntax versus semantics’. GRANT EXECUTE ON payroll.adj_bal_prior_prd tells the agent that it may call the procedure. It does not tell it that the procedure is for month-end corrections, that it must not run twice for one period, or that there is a different one for the current period. The semantics (‘why do this?’) are critical.

A human learns that from a colleague, a wiki, or a painful afternoon. An agent has no colleague. It has a tool list.

Without instructions, you have handed it a wall of buttons

Here is what that tool list looks like when nobody writes the descriptions. A routine’s tool name is its Oracle name, owner and package and routine joined by underscores — and Oracle identifiers were capped at 30 characters until 12.2, so a schema built any time in the last thirty years is full of names that were compressed to fit, by somebody who assumed a developer with context would read them:

payroll_pay_adm_adj_bal_prior_prd
payroll_pay_adm_adj_bal_curr_prd
payroll_pay_adm_recalc_ytd_agg
payroll_pay_adm_recalc_ytd_agg_v2

That is a set of buttons with 30-character labels that were never intended as an instruction manual. A model choosing between them is guessing from an abbreviation, and it will guess confidently. The role model has nothing to say here: all four are granted, so all four are fair game.

Tool-level instructions are the part that is actually missing

Every tool carries a description, and the description is what the model reads when it decides whether to call something. Ours are generated from the data dictionary — columns, parameters, their Oracle types, what comes back when there is no row — which is the right default, because it stays correct when the schema changes.

Replace it where the dictionary cannot know the answer, which is exactly the case above: what the call is for, when it applies, and when to use the other one instead. You write it per tool, in the Tools this config exposes panel, beside the text it would otherwise publish.

Vague descriptions produce wrong tool choices far more often than bad parameters do. This is the cheapest safety work available and the least glamorous, and it is invisible to every permission model.

How MCP DB Wizard adds a layer of semantic understanding to database roles

A grant is a name and a verb. Oracle records that the account may execute payroll.adj_bal_prior_prd; it has nowhere to record what that means, because it never needed to — the meaning lived in the developer who wrote the call, and in the reviewer who read it.

What we add sits on top of the grant and is authored in the config: which of the granted objects become tools at all, the typed shape of each one as above, and the sentence that says what the call is for. Together they turn a permission into something a model can act on rather than guess from.

The part a role structurally cannot reach is that this layer is per config, and therefore per audience. A grant is the same grant for everyone who holds it. A description is written for one set of callers: the finance config can say use this only for a closed prior period, after the month-end run, while the support config describes nothing of the sort because it never listed the procedure. One schema, one set of Oracle privileges, two different instruction manuals — and both of them are text a human can read and review before an agent ever sees them.

Which is what MCP DB Wizard is for. You point it at an Oracle schema and tick the tables, PL/SQL routines, sequences and your own tested SQL statements that one job needs; it reads the data dictionary and generates a working MCP server exposing exactly those, with the parameters and types it found and the descriptions you wrote. The semantic layer is not a document sitting beside the system, to be kept in step by somebody who remembers — it is compiled into the thing that runs, and the next job gets its own server built from its own config. The database keeps enforcing its grants underneath, unchanged and unbypassable; what we add is everything above them that Oracle was never asked to know.

Which tools exist at all is decided before the server runs

The other half of role-based access, and the one people expect to find as a screen. There is no mapping from roles to tools at run time: a role is a separate config. An object you did not select has no tool, no method and no class in the generated server — it is absent from the binary, not refused at request time.

What the running system does decide is which config an account may drive: the API token says who, the access matrix says what. A new account starts with no grants, and being signed in but not granted the config you asked for is a 403.

So two teams needing different tools over one schema is two configs — which is the curation advice as well, for the unrelated reason that an agent offered fewer tools chooses better.

The three controls that matter once it is running

Roles, curation and descriptions all decide things in advance. These three are what you have when something goes wrong anyway, and we would argue they are not optional.

Auditing. The proxy records who called which tool and whether it was allowed; each generated server records what the tool did, and the two are merged into one trail. Every install keeps a local one, free tier included. You need this because Oracle sees a single service account no matter which agent called — the database cannot tell you who, and this can.

Rate limiting. An agent that has decided a tool is the answer will call it again, and again, at machine speed. A per-caller rate limit bounds how often calls start; over it, the caller gets a 429 and a Retry-After. Without one, a retry loop is a denial of service you built yourself.

Call duration limiting. A rate limit does not help with one call that never ends. An approved, curated, read-only query against a table nobody expected to grow is still a query holding a connection. DAO_QUERY_TIMEOUT_SECONDS lets Oracle raise ORA-01013, the call fails, and the pooled connection goes back where it came from.

Note the shape: the first tells you what happened, the second bounds how often, the third bounds how long. They are three different questions and no one of them substitutes for the others.

What this adds up to

Database roles are the boundary and you should still get them right — narrow account, one grant per object, nothing the agent does can exceed them. What sits above is the part a grant cannot express: which tools exist at all, what each one is for, who may drive which set of them, and what happens when an agent behaves unlike any user your DBA has met.

The role model is necessary. On its own it hands a language model a wall of buttons labelled for somebody else, and trusts it to press the right one.

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.