MCPDBWizard

Writing  · 

Least privilege for an Oracle MCP server: choosing the account it connects as

A common question is “How do I get my mcp server running with the least database privilege?”

In this article we discuss the basic steps you can take using Oracle and vanilla MCP + SQL, and then explain why MCP DB Wizard arguably does a better job of things.

Never, ever use the database object owner’s account.

The account that owns a table can always read it, write it and drop it. Not by grant — by ownership. There is no REVOKE that takes DELETE on PAYROLL.EMPLOYEE away from PAYROLL, because it was never granted in the first place. So if your MCP server logs in as the schema owner, least privilege is not something you forgot to configure. It is something you cannot express.

That reach is wider than people picture, and it includes DDL. An owner needs no privilege at all to TRUNCATE its own table or DROP it — DROP ANY TABLE is for other people’s tables, not your own — and it will still be holding the CREATE PROCEDURE the application was installed with, which is CREATE OR REPLACE on every package body in the schema. The account you were hoping to keep read-only is the one account that can delete the application.

It also grows on its own. A grant list is a decision somebody made, and it changes only when somebody makes another one. Ownership is not a list: the table a colleague adds to that schema next month is reachable by your MCP server the moment it is created, and nobody chose that.

The failure modes invert too. From a separate account, SQL that reaches for something you did not grant dies with ORA-00942: table or view does not exist — a loud, clean, safe stop. From the owner’s account every unqualified name resolves to something real, so the same mistake succeeds, against the whole schema.

Then there is the boring, practical argument. The password sitting in your server’s configuration should not be the password that can drop the application. A dedicated MCP account can be locked, rotated or dropped in the middle of an incident and nothing else notices; ALTER USER payroll ACCOUNT LOCK is an outage.

Create a database role, and grant that role to a special MCP login account.

So the MCP server gets an account of its own, and that account owns nothing. The grants it needs go into a role you build for this one server, and the account gets CREATE SESSION and the role. The docs page on this has the same shape without the argument around it.

-- The login. It owns no objects and has no quota, so it can create nothing.
CREATE USER mcp_agent IDENTIFIED BY "…" DEFAULT TABLESPACE users;
GRANT CREATE SESSION TO mcp_agent;

-- The privileges, one line per object you deliberately exposed.
CREATE ROLE payroll_mcp_ro;
GRANT SELECT  ON payroll.employee    TO payroll_mcp_ro;
GRANT SELECT  ON payroll.department  TO payroll_mcp_ro;
GRANT EXECUTE ON payroll.js_admin    TO payroll_mcp_ro;

GRANT payroll_mcp_ro TO mcp_agent;
ALTER USER mcp_agent DEFAULT ROLE payroll_mcp_ro;

The role earns its place by being one object rather than a scattering. You can read it back in a single query, hand it to whoever signs off the change, reuse it for a second server with the same job, and — the part that matters at three in the morning — REVOKE payroll_mcp_ro FROM mcp_agent takes every privilege away in one statement while the account and everything else stays exactly where it was.

Two Oracle details that bite:

The role has to be enabled at logon. Oracle makes a newly granted role a default role by itself, so the ALTER USER above is usually belt and braces — usually. If somebody has already set DEFAULT ROLE NONE or a restricted list on that account, or the role is password-protected, it does nothing until a SET ROLE that your server will never issue. The symptom is ORA-00942: table or view does not exist against a table whose grant you can see plainly in DBA_TAB_PRIVS, which reads like a missing privilege rather than a disabled role. DBA_ROLE_PRIVS.DEFAULT_ROLE is the column that answers it.

Privileges from a role are invisible inside definer’s-rights PL/SQL, and to CREATE VIEW. If anything the account runs depends on that, the grant has to go directly to the user instead. For an account that selects and calls other people’s packages — which is all this one does — the role is fine.

And build the role yourself. Granting an existing application role, or CONNECT and RESOURCE, puts your MCP server’s reach under somebody else’s maintenance.

MCP DB Wizard allows you to give the least database privileges to your MCP server.

MCP DB Wizard will work equally well with or without a dedicated login user with grants to the schema owner’s objects. But it has a number of advantages you should consider.

  1. Read privs on a table are limited to querying on the PK, UK, indexed and foreign key columns you selected. In our universe access to a table does not mean you can issue any query or join you want: the agent supplies the values, never the query. This is especially important when you consider that the ‘users’ are agents, who are known for pushing boundaries and behaving oddly without warning.

  2. MCP DB Wizard lets you run any SQL statement you want, as long as it was written by a grown up and listed in the config file. Worrying about which database user is not enough. You also need to worry about what happens when the agent goes rogue.

  3. A grant is silent. GRANT SELECT ON payroll.employee says nothing about what that table is for, which of its rows stopped being maintained in 2014, or why the view next to it is the one you actually wanted — it hands over no-questions-asked, no-context access and leaves the agent to guess. In MCP DB Wizard you can add a text explanation to each exposed object, and the agent reads it as part of the tool, which will, in theory at least, prevent it from doing stupid things.

What limits does MCP DB Wizard have when granting least-privilege database access to an MCP server?

Currently we don’t have an easy way of limiting access to a specific customer_id (for example). We’re looking at addressing this is in the next release, and have an issue open as we work on it.

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.