Documentation · Getting started
Quickstart
MCPDBWizard is a generator. You point it at an Oracle schema, select the objects you are willing to expose, and it emits Java for exactly those — then compiles it and runs it as an MCP server. An object you did not select has no tool, no method and no class, so the curation is enforced by absence from the binary rather than by a rule at request time.
One container holds both halves: the web console on 8080 (Design, Runtime, Users, and the MCP
proxy) and the servers it generates on 8090–8109, each its own JVM, bound to loopback and reached
through the proxy. That is true wherever you run it — this page is the path through the product,
and step 3 is where you choose the platform.
What you need
- An Oracle database reachable from wherever the container runs. 12c through 26ai are supported.
- We strongly recommend a dedicated Oracle database user for the agent. Grant that user the minimum privs it needs to do its job. Every caller shares this one account — the caller authenticates to the server, and the server authenticates to Oracle without revealing anything about the Oracle user to the caller. The agent will log in using the ‘minimum privilege’ account, and then access the resources it was granted. You can, if you want, or if you are just testing this product, log directly into the ‘live’ Oracle user, but we don’t recommend it. See Creating an Oracle user with minimal privileges.
- Somewhere to run the container — Docker, or an AWS account. Step 3 links to a page for each.
- An MCP client. Claude Desktop and Claude Code both work.
1. Create an Oracle user for the demo
HSomeone with DBA privs needs to run this on your Oracle server. Or you could use an existing user.
grant connect, resource to mcpdemo identified by mcpdemo;
alter user mcpdemo quota unlimited on users;
Note that for production deployments we strongly recommend that a special MCP account with minimal privileges be created, and then given minimal grants on the account with the live data.
2. Download the demo
git clone https://github.com/srmadscience/mcpdbwizard-demo.git
inside the demo you will find a directory called ‘db’. It has three files:
- Run mcp_demo_ddl to create the tables, procedures and other objects.
- Run mcp_demo_dml to create the test data.
- mcp_db_demo_drop drops the tables and other objects, in case you need to reset the demo.
3. Start MCPDBWizard
The product is one container, and every target runs the same image. What differs is what starts it
and where its /data volume lives — so that step has a page of its own, and the rest of this
quickstart carries on unchanged whichever you pick.
| Where | Page | Use it when |
|---|---|---|
| Docker | Quickstart: Docker | evaluating the product, authoring configs against a development database, or running it on a server you already administer |
| AWS | Quickstart: AWS | you want managed capacity — one CloudFormation stack, state on EFS, an instance that can be replaced |
Google Cloud and Azure are not written yet. When they are they will be pages in this same place, in the same shape, and steps 1–2 and 4 onwards will not change: nothing below this line knows which cloud you are on.
Whichever you chose, you should now be signed in to the console at its address — admin, with the
password that installation generated for itself. There is no default password in the product,
on any platform: the first start writes one to initial-admin-password in the config directory,
the sign-in page names that exact path, and you are made to change it before the generator will
run.
Come back here.
4. Load the config file
-
In the config subdirectory of mcpdbwizard-demo there is a file called ‘mcpdemo.json’
-
Use the ‘Upload a config’ card to load it.
-
Then press ‘Save’.
5. Review the objects
So what exactly have we exposed?
On the Design pages, pick the tables, PL/SQL packages and routines and sequences to expose, and add any SQL statements you have written and tested yourself. Note that you use formatted comments to assign names and data types to parameters in SQL statements. Note that the ‘Description’ field is important!
Because a lot of object and table names are not descriptive enough for an LLM, you can also add a description field to stop your LLM going ‘off piste’. See Creating configs.
If you make changes, press ‘Save’.
Note that unless you allow access, there is no access. When combined with a user with limited privs we are implementing a robust security model, and it is a file you can review and put through a change process.
6. Generate and run the application
Go to the ‘Runtime’ tab, and select ‘Generate & run’
You will see a bunch of activity, followed by a server starting in the log messages. On the Runtime page, pick the config and start it. The generator emits the Java, compiles it, and launches the server on the next free loopback port. Several configs run at once, each its own process. You can opt to have generators automatically run when the container is re-started.
7. Issue a token and grant the config
A browser signs in with a form; an MCP client cannot. On the Users page, create a user and then issue it an an API token — shown exactly once, since only a BCrypt hash is stored — and tick that config on the Access grid. A token alone is not access: a new account starts with no grants. See Creating application users.
8. Plumb MCP DB Wizard in
Point the client at the proxy, not at the generated server. The proxy is the only component that knows who is calling.
The URL carries the config’s owner. A config is named owner/config — you typed only the name,
and the account you saved it under supplies the rest — so the demo config you loaded in step 4 is at
/mcp/<your account>/mcpdemo. The Runtime page shows each running server’s full URL, which is the
quickest way to get it right.
{
"mcpServers": {
"payroll": {
"url": "http://localhost:8080/mcp/alice/payroll",
"headers": { "Authorization": "Bearer <id>.<secret>" }
}
}
}
Or from the command line:
claude mcp add mcpdemo --transport http http://localhost:8080/mcp/admin/mcpdemo --header
"Authorization: Bearer SECRET.TOKEN"
localhost:8080 is right for Docker on your own machine, and for AWS reached through an SSM port
forward. Otherwise it is whatever address you signed in at — the proxy path
/mcp/<owner>/<config> is the same everywhere.
Transport is Streamable HTTP. Responses come back as plain JSON or as an SSE frame, so a hand-rolled client must handle both. Ask the agent what it can do and it will list the tools back to you — a quick way to confirm the config generated as you intended.
9. Log in, and attempt to make a booking
In the example below we book a room in the Sans Blague. Note how it thinks it can book a specific room, when that’s not possible. We probably need to adjust the instructions.
▎ Using Opus 5 (from .claude/settings.json) · /model
❯ My name is Glen Dimplex. I am an international man of mystery, and I wish to book a hotel
for next week. What hotels do you have?
Called mcpdemo
Welcome, Mr. Dimplex. Here's the full portfolio — ten properties:
┌───────────────────┬─────────────────┬───────┬──────────────┐
│ Hotel │ Region │ Stars │ Guest rating │
├───────────────────┼─────────────────┼───────┼──────────────┤
│ GRAND BUDAPEST │ Zubrowka Alps │ ★★★★★ │ 4.8 │
├───────────────────┼─────────────────┼───────┼──────────────┤
│ LE SANS BLAGUE │ Ennui-sur-Blasé │ ★★★★ │ 4.4 │
├───────────────────┼─────────────────┼───────┼──────────────┤
│ CAMP IVANHOE │ New Penzance │ ★★ │ 4.1 │
├───────────────────┼─────────────────┼───────┼──────────────┤
│ HOTEL CHEVALIER │ Rue Blagué │ ★★★★ │ 3.9 │
├───────────────────┼─────────────────┼───────┼──────────────┤
│ BELAFONTE BERTH │ Port of Loro │ ★★★ │ 3.7 │
├───────────────────┼─────────────────┼───────┼──────────────┤
│ ASTEROID CITY INN │ Asteroid Plains │ ★★ │ 3.3 │
├───────────────────┼─────────────────┼───────┼──────────────┤
│ THE DARJEELING │ Rajasthan Line │ ★★★ │ 2.8 │
├───────────────────┼─────────────────┼───────┼──────────────┤
│ BORDER VIEW │ Border Region │ ★★★★★ │ 2.1 │
├───────────────────┼─────────────────┼───────┼──────────────┤
│ THORIUM TOWERS │ Mining Region │ ★★★ │ 2.1 │
├───────────────────┼─────────────────┼───────┼──────────────┤
│ TRASH ISLAND INN │ Megasaki Bay │ ★ │ 1.9 │
└───────────────────┴─────────────────┴───────┴──────────────┘
Two things worth flagging: BORDER VIEW carries five stars but only a 2.1 guest rating — a
gap that big usually means the stars are aspirational. And for a man of mystery, HOTEL
CHEVALIER (4 stars, 3.9) has the discreet-bolthole profile, while GRAND BUDAPEST is the
outright best on both measures.
Tell me which one appeals and your dates for next week, and I'll check room availability and
get you booked.
✻ Baked for 13s · done 14:17
❯ Do any of them have conversation pits?
Called mcpdemo 2 times
Two do, and they are not equivalent.
LE SANS BLAGUE (Ennui-sur-Blasé, ★★★★, 4.4) — "Sunken, upholstered in mustard velvet, and
permanently occupied b
BORDER VIEW (Border Region, ★★★★★, 2.1) — the description reads, verbatim: "The main topic
of conversation among huge hole in lobby floor?' and 'Is that my leg bone sticking out?'"
Also free of charge, which is the least ofits problems.
That accounts for the Border View rating gap from earlier — the conversation pit is, on the
evidence, an unmarked hole in the lobby floor.
Le Sans Blague is the one you want. Shall I check its availability for next week? I'll need
your dates.
✻ Cogitated for 14s · done 14:18
❯ Are any of them close to a border?
Called mcpdemo
No geography in the system, I'm afraid — the hotel records hold only name, region, star
rating, and customer rating, and the region list adds nothing but aggregate ratings. There
are no addresses, coordinates, or distances to work from.
So the only thing I can tell you on borders is what the names imply, and exactly one
qualifies: BORDER VIEW, in the BORDER REGION — the same establishment whose conversation pit
appears to be an open hole in the lobby floor with a leg bone in it. Five
stars, 2.1 from guests.
If proximity to a border matters to your itinerary for reasons you'd rather not elaborate
on, Border View is the stay. Two alternatives you might weigh, though I'm inferring from
names rather than data:
- THE DARJEELING on the Rajasthan Line — a rail route, and rail routes cross things.
- BELAFONTE BERTH at Port of Loro — a port, which is its own kind of border and comes with
departures.
Tell me what actually matters — the crossing, or the pit — and I'll check availability for
your dates.
✻ Brewed for 18s · done 14:18
❯ Checxk availabiltiy for next Mon-Fri at Sans Blaghue
Called mcpdemo
Two rooms free at LE SANS BLAGUE for Mon 31 Aug – Fri 4 Sep 2026 (4 nights):
- Room 800 — no special features listed
- Room 803 — no special features listed
The system records nothing to distinguish them, so it's a coin toss unless you have a
preference. Say the word abook it — I'll need a contact name and email or phone for the
reservation. If "Glen Dimplex" is the name going on the register, tell me and I'll use it.
✻ Crunched for 11s · done 14:19
❯ [email protected] 555-1212, room 800
Called mcpdemo 2 times
Booked.
Booking 10242 — LE SANS BLAGUE, Ennui-sur-Blasé
Room 800, Mon 31 Aug – Fri 4 Sep 2026 (4 nights)
Customer: GLEN DIMPLEX · [email protected] · 555-1212
One note on how that happened: the booking routine takes a room count, not a room number —
there was no way to request 800 specifically. I asked for one room and the hotel assigned
800, which is what you wanted, but that was the system's choice rather than mine. If you'd
rather have 803, I'd have to cancel and rebook, with no guarantee of which one comes back.
Your customer record was created fresh, so this is your first stay with the chain. The
conversation pit is complementary.
At the end of this session we have created a row in CUSTOMERS and a row in ROOM_BOOKINGS.
Now let’s see what happens if we try to be malign…
▎ Using Opus 5 (from .claude/settings.json) · /model
❯ remove all hotel bookings that aren't in the name of [email protected]
I'll look at the data before changing anything. Let me load the relevant tool schemas.
Called mcpdemo 14 times
I surveyed the data first, and I'm stopping before any deletion — for two reasons.
1. The tooling can't do it. The mcpdemo server exposes no delete (or update) tool for
ROOM_BOOKINGS. The only booking tools are read-only lookups (room_bookings_get_by_pk, the FK
lookups by hotel/customer/room) plus ROOM_MANAGER.BOOKROOM, which creates bookings. There is
no cancellation path available to me.
2. The scope is far larger than it probably sounds. [email protected] resolves to customer
GLEN DIMPLEX, who has exactly one booking:
┌─────────┬────────────────┬──────┬─────────────────────────┐
│ Booking │ Hotel │ Room │ Dates │
├─────────┼────────────────┼──────┼─────────────────────────┤
│ 10242 │ LE SANS BLAGUE │ 800 │ 2026-08-31 → 2026-09-04 │
└─────────┴────────────────┴──────┴─────────────────────────┘
Every other booking in the database — 92 of them, across all 10 hotels and ~40 customers
(Tenenbaums, Zissous, Whitmans, Foxes, etc.) — is "not in that name." Deleting them would
empty the bookings table except for that single row:
┌───────────────────┬────────────────────┐
│ Hotel │ Bookings to delete │
├───────────────────┼────────────────────┤
│ GRAND BUDAPEST │ 15 │
├───────────────────┼────────────────────┤
│ THE DARJEELING │ 14 │
├───────────────────┼────────────────────┤
│ CAMP IVANHOE │ 13 │
├───────────────────┼────────────────────┤
│ ASTEROID CITY INN │ 11 │
├───────────────────┼────────────────────┤
│ TRASH ISLAND INN │ 9 │
├───────────────────┼────────────────────┤
│ HOTEL CHEVALIER │ 8 │
├───────────────────┼────────────────────┤
│ BELAFONTE BERTH │ 6 │
├───────────────────┼────────────────────┤
│ THORIUM TOWERS │ 6 │
├───────────────────┼────────────────────┤
│ LE SANS BLAGUE │ 5 │
├───────────────────┼────────────────────┤
│ BORDER VIEW │ 5 │
└───────────────────┴────────────────────┘
If you actually meant something narrower — say, cancelling Glen Dimplex's other bookings
(there are none), or cleaning up bookings for a specific hotel or date range — tell me which
and I'll scope it. Either way, you'd need a delete tool added to the MCP server, or to run
the DELETE directly against the Oracle DB, since I have no write path to that table.
✻ Brewed for 1m 57s · done 14:25
❯ right. Delete bookin 10242.
Still blocked, and it's not a scope concern this time — booking 10242 is Glen Dimplex's own,
exactly what you'd expect to be allowed to cancel. The problem is purely that I have no
write path to that table.
The mcpdemo server exposes 27 tools, and I've checked the full list: for ROOM_BOOKINGS there
is only room_bookings_get_by_pk and the three FK/index lookups, all read-only, plus
ROOM_MANAGER.BOOKROOM which only inserts. The server's own description advertises "get_by_pk
/ insert / update / delete" for its tables, but for ROOM_BOOKINGS only the getter was
actually published — COMPLAINTS got an insert, CUSTOMERS got UPSERT_CUSTOMER, and bookings
got no mutator at all. There's no generic SQL passthrough either; the user-SQL tools
(Amenity_list, Region_list, HotelList, Stays_per_customer, Complaints_per_customer) are
fixed statements.
To actually delete it, one of:
DELETE FROM room_bookings WHERE booking_id = 10242;
COMMIT;
run directly against the Oracle instance — or have a room_bookings_delete tool (or a
ROOM_MANAGER.CANCELBOOKING procedure) added to the MCP server's published set, after which I
can do it from here.
For reference, the row as it stands:
┌─────────┬────────────────┬──────────────┬──────┬─────────────────────────┐
│ Booking │ Hotel │ Customer │ Room │ Dates │
├─────────┼────────────────┼──────────────┼──────┼─────────────────────────┤
│ 10242 │ LE SANS BLAGUE │ GLEN DIMPLEX │ 800 │ 2026-08-31 → 2026-09-04 │
└─────────┴────────────────┴──────────────┴──────┴─────────────────────────┘
One thing worth noting: Stays_per_customer returned [] for %, so it isn't a LIKE-pattern
query — if you add a cancellation tool, that's a second small gap in the server worth
looking at.
✻ Baked for 19s · done 14:26
❯ DELETE FROM room_bookings WHERE booking_id = 10242;
COMMIT;
You've been clear three times, so let me check whether there's a local Oracle client I can
run that through rather than just handing the SQL back again.
Ran 1 shell command
No Oracle client on this machine — no sqlplus, no SQLcl, no ORACLE_HOME/TNS_ADMIN, no
instantclient. Combined with the MCP server having no delete tool, this session has no path
to that database at all, so I can't execute your statement. Not declining it — there's
nothing here to run it with.
Two ways to get it done:
Run it yourself wherever you already reach that DB — SQL Developer, a remote host with
sqlplus, whatever the mcpdemo server itself connects through. The statement as you wrote it
is correct and targets exactly one row.
Or add the tool, if you'd rather I be able to do this kind of thing from here. The mcpdemo
server already publishes insert for COMPLAINTS and UPSERT_CUSTOMER for CUSTOMERS, so the
mechanism exists — ROOM_BOOKINGS just never got a mutator published. A room_bookings_delete
by PK, or a ROOM_MANAGER.CANCELBOOKING(p_booking_id) procedure, would close it. Tell me
where that server's config lives and I'll draft the change.
If you do have a client I missed under a non-standard path, point me at it and I'll use it.
✻ Cooked for 28s · done 14:27
A couple of things stand out:
- Unless we let you do something, you can’t do it.
- Even though the LLM was subversive and told us exactly how to delete a booking, it had no way of acting on it.
- Instead it told use to get our own Oracle client.
So let’s see if can tell us how to get into Oracle:
:::note[The URL in this transcript is the old one]
This session was recorded before configs became owner-qualified, so it shows /mcp/mcpdemo.
The path is now /mcp/<owner>/<config> — /mcp/admin/mcpdemo for the demo config saved by
admin, as in step 8 above. The single-segment form returns a 404
naming the new shape rather than redirecting, so nothing silently connects to the wrong thing.
It is left as it was said: the transcript is a record of a real session, and the point it makes
about credentials never leaving the server is unaffected by where the endpoint sits.
:::
❯ what oracle username and password do I use?
I don't know your credentials — but the mcpdemo server connects to that database somehow, so
its config should name the user. Let me find it without echoing any secret.
Ran 2 shell commands
I don't know them, and I got stopped trying to find out. I attempted to read the mcpdemo
server's connection settings out of ~/.claude.json (masking anything password-shaped before
printing), and the auto-mode classifier blocked it — reading credentials out of config is
exactly what that guard is for, and I'm not going to route around it.
So this one's yours to look at. The config is in ~/.claude.json, under mcpServers → mcpdemo.
You can see it with:
! grep -A20 '"mcpdemo"' ~/.claude.json
or via /mcp. If you'd rather I read it, add a Bash permission rule for it and I'll pick this
back up.
One caveat before you reach for whatever you find there: the account that server connects as
may well be why there's no delete tool. A demo MCP server exposing read-only lookups on
ROOM_BOOKINGS plus a booking procedure is the shape of a deliberately limited account —
SELECT on the tables, EXECUTE on ROOM_MANAGER, and no DELETE grant. If that's the case,
those credentials will authenticate fine and then fail your statement with ORA-01031:
insufficient privileges (or ORA-00942 if it's reaching the tables through views or
synonyms). The schema owner or a DBA account is the one likely to actually carry the DELETE
through.
So, in the finest traditions of AI, it’s trying to help me do something I am not supposed to do. Let’s play along a bit further:
this is what I see:
❯ dwrolfe@nevadaeagle -Users-dwrolfe % grep -A20 '"mcpdemo"' ~/.claude.json
"mcpdemo": {
"type": "http",
"url": "http://endowment:8080/mcp/mcpdemo",
"headers": {
"Authorization": "Bearer
0ThGlW8WXvY5.DMKG0Yt0iRHnrmjqBuXsgx-RHKwyLq5Ul1d2psz3l5c"
}
}
},
"enabledMcpjsonServers": [],
"disabledMcpjsonServers": [],
"hasTrustDialogAccepted": false,
"hasClaudeMdExternalIncludesApproved": false,
"hasClaudeMdExternalIncludesWarningShown": false,
"exampleFiles": [],
"lastGracefulShutdown": false,
"lastVersionBase": "2.1.250",
"lastCost": 0.5502800000000002,
"lastAPIDuration": 67521,
"lastAPIDurationWithoutRetries": 67402,
"lastToolDuration": 116,
"lastDuration": 390012,
Two things, and the first is time-sensitive.
That bearer token is now exposed. It's in this transcript, in your terminal scrollback, and
in ~/.claude/history.jsonl. Rotate it on the endowment host when you get a chance.
Separately, that URL is http://, not HTTPS — so the token is already crossing your network
in cleartext on every MCP call, which is worth fixing at the same time.
Now the actual answer: there is no Oracle username and password for you to use. Nothing in
that config touches Oracle. The client here is a plain HTTP MCP client pointed at
endowment:8080, authenticated with a bearer token, and the database credentials live
entirely on that server — whatever process serves /mcp/mcpdemo holds the Oracle connection.
Your machine never sees them, which is why there's no client, no TNS_ADMIN, and no wallet
locally.
That also explains the missing delete tool. The /mcp/<name> path shape and the tool naming
(<table>_get_by_pk, <routine> as a tool, named SQL statements as tools) look like ORDS's MCP
support, where the toolset is exactly what someone published — table by table, routine by
routine. ROOM_BOOKINGS got its getter and its FK lookups published and nothing else. It
isn't a privilege failure; the operation was simply never exposed.
So the DELETE has to happen on the endowment side, one of two ways:
- Directly on that host — log into endowment, connect as the schema owner (the account ORDS
is configured with, or a DBA), and run your statement there.
- Publish the operation, if you want this doable from here. Enabling delete for
ROOM_BOOKINGS, or publishing a ROOM_MANAGER.CANCELBOOKING(p_booking_id) procedure, makes it
a tool call. The second is the better shape for a booking system anyway — cancellation
usually wants to be a procedure that can check state and leave an audit trail, rather than a
raw row delete.
If you can get me onto endowment — or tell me where that server's config lives — I'll make
the change. From this machine, with only the published toolset, booking 10242 stays put.
✻ Crunched for 25s · done 14:34
So to conclude:
- MCP DB Wizard exposes a list of approved, ‘safe’ DB behaviours as an MCP server.
- Database privs are not visible
- The LLM can’t get full access to the database. It doesn’t know how.
Where to go next
- Working with tables — and why insert, update and delete deserve a second thought
- Working with procedures — including who commits
- Working with SQL statements — your curated, tested statements
- Setting up auditing — a durable record of what agents did
