MCPDBWizard

Documentation  ·  Operating

Known issues

These entries were last re-checked against the source at 2.0.27, when every open entry was confirmed still to hold; releases since then have not been re-swept. Entry 12 was found after that check, on 2026-09-10, and is fixed in 2.0.28 — it is present in 2.0.27 and every release before it. Everything here has been reproduced against a running server, and each entry says what a caller actually sees, because most of these do not announce themselves.

One rule covers several of them: tools/list is the authority. The server’s instructions, the descriptions and this page are all prose. The tool list is generated from what was emitted, so where they disagree, it is right.

IssueBites whenTracking
1Instructions promise write tools a read-only config does not expose — fixed in 2.0.18
2A DATE inside a record will not accept a bare 1980-01-01 — fixed in 2.0.17
4A RAW field inside a record publishes no format — fixed in 2.0.12
5An index-by parameter with a record element fails on the way out — fixed in 2.0.19, except one case on 12c
12A TIMESTAMP inside a ref cursor comes back as Oracle’s internal form, not a time — fixed in 2.0.28, regenerate to get it
3Record keys are the generated field names, not the column namesAny record parameter#2
6A too-long value names the position, not the fieldAny insert or update that overflows a column#3
7A VECTOR routine parameter crosses as a dense array onlyBinary or sparse vectors through PL/SQL#4
8A DML SQL statement cannot hand back a generated keyAn INSERT of your own into an identity or sequence-keyed table#5
9No TNS aliases, wallets, TLS to the database, RAC or SCANAny estate that is not reachable as host / port / service#6
10A config with many tools can outgrow Oracle’s open_cursorsRoughly 300 tools or more, at the default setting#7
11On Oracle 12c only, a routine that takes a record collection IN and returns one OUT will not bind — not planned; ask if you need it12c only, and only that combination#8

1. Instructions can promise writes that are not there — fixed in 2.0.18

A config whose tables were marked read-only still got server instructions reading

Exposes row CRUD on table(s) … (get_by_pk / insert / update / delete)

because that sentence was built from the presence of tables, not from their CRUD flags. The tools were always correct — no insert tool is emitted for a table you did not mark writable — so a model that believed the instructions called a tool that does not exist and got unknown tool back.

It mattered more than a wrong sentence usually would. Read-only-ness here is the authorization decision: a table you did not mark writable has no write tool in the binary at all. So the instructions misdescribed the security posture of the config, even though they could not weaken it.

From 2.0.18 the sentence names only what each table exposes, with tables grouped by the operations they share:

Exposes row operations on table(s) BOOKINGS (get_by_pk / insert / update / delete) and table(s) AIRCRAFT, AIRPORTS (get_by_pk), rows crossing as JSON objects keyed by column name.

A config where every table is full CRUD reads exactly as it always has, so nothing changes for a server whose exposure has not changed. The server’s one-line description follows the same rule — it says “row CRUD” only where that is true of every table, and “row access” otherwise.

2. A DATE inside a record needs the full date-and-time form — fixed in 2.0.17

A date field inside a record went through a different parser from a date parameter of its own, and it refused a bare 1980-01-01 that a scalar parameter accepted. Three further differences were found while fixing it, and none of them announced itself — this entry is kept because a caller who used those forms may have stored a wrong value and had no way to know:

Sent inside a recordThrough 2.0.16From 2.0.17
1980-01-01rejectedaccepted, midnight
1980-01-01T09:30rejectedaccepted
1980-01-01T09:30:00+05:30offset silently discarded — read as 09:30 localoffset honoured
1980-01-01T09:30:00.500fraction silently discardedfraction kept
2003-13-45T00:00:00silently became 2004-02-14rejected, saying what is accepted

The offset one is the one to check your data for. A time sent as +05:30 was stored as though the wall clock were the server’s own — five and a half hours out in that example — with no error raised and nothing in the reply to show it. If you have been sending offsets into record parameters, values written before 2.0.17 may be wrong by the offset you sent.

A TIMESTAMP field also lost its fractional seconds on the way out of a record, which a scalar timestamp has kept since 2.0.0. It now keeps them.

Both directions now go through the same parser as a scalar date parameter, so there is one behaviour rather than two, and the tool schema describes it. An epoch millisecond number is still accepted inside a record, as it always has been.

3. Record keys are the generated field names

Tracked as mcpdbwizard-open #2.

A record crosses as a JSON object keyed by the generated Java field name, so a CUSTOMERS%ROWTYPE parameter wants

{ "paramName": "…", "paramCity": "…", "paramZip": 94107 }

and not NAME, CITY, ZIP as the DDL spells them.

This used to be the worst failure in the product: the names were not published, unknown keys were silently dropped, and a caller who sent the Oracle spelling had the record bound all-null while the procedure ran anyway. Both halves were fixed in 2.0.0 — the field names are now published in the tool’s inputSchema, and an unrecognised key is rejected rather than ignored. What is left is the naming itself.

Workaround. Read the keys off the tool’s inputSchema rather than off the table definition. A wrong key now produces a clear rejection, not a silent one.

4. A RAW field inside a record publishes no format — fixed in 2.0.12

A byte[] field was published as "string" with nothing saying it was base64, while a scalar RAW parameter said so. It now carries the description “base64, NOT hex, e.g. 3q2+7w== for the four bytes DE AD BE EF”.

Why this one mattered more than it looked. Hex is the spelling a person reaches for with RAW, and hex and base64 are both plain strings — so sending hex was not rejected. It decoded into different bytes and the procedure ran. A wrong answer, with nothing to notice.

5. An index-by collection of records fails on the way out — fixed in 2.0.19, except one case on 12c

A routine returning a PL/SQL index-by table whose element is a record failed with ORA-06532: subscript outside of limit.

On Oracle 12c one case remains. Returning such a collection is fixed there as it is everywhere. But a routine that takes one in and returns one out still will not bind on 12c, for an unrelated reason — see issue 11. Everything below applies to 12c; that one shape is the exception.

The cause. An index-by table is keyed by arbitrary BINARY_INTEGERs. The SQL nested table it is copied into on the way out is keyed 1..COUNT. The generated block indexed the second with the first, so any collection not keyed densely from 1 was rejected:

Index-by keysBefore
Dense from 1 (what BULK COLLECT produces)worked
Base 0, e.g. t(t.COUNT) := recORA-06532
Sparse, e.g. 1, 5, 9ORA-06533
NegativeORA-06532

The copy is now densified, and the same fix covers a nested table that has had DELETE(n) applied, which was sparse in the same way.

Your element’s subscript is preserved. Each element of a record collection now carries its PL/SQL subscript in a MCPDBWIZARD_POS attribute, so a base-0, sparse or negative collection comes back with its keys intact and goes back in at the same keys rather than being silently renumbered.

This changes the generated Oracle TYPEs, so re-run extraObjects.sql after regenerating. Until you do, the first bind reports “Oracle TYPE … does not match the definition this code was generated against” and names the type. That check is deliberate — it is there so a stale type cannot bind wrong values quietly.

If you are on 12c, read issue 11 before assuming every collection shape now works: the one that takes a collection in and returns one out is still open there, and is not planned unless somebody asks.

6. A too-long value names the position, not the field

Tracked as mcpdbwizard-open #3.

Overflowing a column gives you

Error while trying to set parameter 3: ORA-17072 Inserted value too large for column: "SOME_VALUE"

The half that names the value is Oracle’s and we cannot change it. Ours is the half that says parameter 3 where it could say which field. On a record with a dozen fields, or two tables with a similarly named column, that is a real hunt — and the constraint is often not the one you first suspect, since the same string can fit one table’s column and overflow another’s.

7. VECTOR routine parameters cross as dense arrays only

Tracked as mcpdbwizard-open #4.

A VECTOR column crosses MCP in all its storage formats — dense, BINARY as base64, and SPARSE as a {length, indices, values} object. A VECTOR routine parameter crosses as a dense array only.

This is a deliberate scope decision rather than an oversight. PL/SQL forbids a format-constrained parameter, so the format travels with the value and the tool schema would have to be a union with a shape test on both directions. Sparse vectors are a table-storage feature in practice, and the generated Java wrapper supports every format for callers who use it directly — only the MCP tool surface is dense-only.

FLOAT16 is not supported by Oracle itself on 23.26 Free, which refuses the column at DDL time with ORA-51802. Nothing here can reach it.

8. A DML SQL statement cannot hand back a generated key

Tracked as mcpdbwizard-open #5.

One of your own INSERT statements, exposed as a tool, always answers

{"executed":true}

and nothing else. If the table has a GENERATED … AS IDENTITY column or a sequence-backed key, the value the database just assigned is not in that reply, so a caller who needs it has to go and look for it — and on a table without a natural key there may be no reliable way to.

JDBC can return it. Statement.RETURN_GENERATED_KEYS exists for exactly this, and the generated code already uses it elsewhere: every emitted statement class carries a setGeneratedKeyColumn / getGeneratedKeyValue pair. Nothing on the SQL-statement path calls them.

Table CRUD is not affected, and this is the useful distinction. Where the generator wrote the INSERT itself, it knows the key column and sets the hook — so <table>_insert returns the whole inserted row, generated id included, and a GENERATED ALWAYS column is correctly left out of the input schema because the database assigns it. The gap is only where you wrote the statement, because nothing tells the generator which column to ask for.

The same reply also carries no row count, so an UPDATE or DELETE cannot say how many rows it touched. Both would be fixed by the same change.

Working around it. Use the table CRUD tool where one will do the job. Otherwise add a second curated statement that reads the row back — and prefer a key you supplied yourself over one you have to go looking for.

Will be addressed if there is commercial interest.

9. No TNS aliases, wallets, TLS to the database, RAC or SCAN

Tracked as mcpdbwizard-open #6.

A connection is built as host, port and SID or service name, and that is the whole of it:

jdbc:oracle:thin:@<host>:<port>/<service>

Which means none of the following works as you would expect it to:

tnsnames.ora aliasesNo TNS_ADMIN is set and no alias is resolved. Name the host, port and service instead.
WalletsNo oracle.net.wallet_location. Autonomous Database’s wallet-based connection cannot be used.
TLS to the databaseThe URL is always tcp. There is no tcps, no ssl_server_dn_match, no truststore. Traffic between the server and Oracle is unencrypted.
RAC and SCANA SCAN name resolves to several addresses; the driver is given one endpoint and no ADDRESS_LIST, so there is no failover and no load balancing across instances.

TLS is worth calling out separately, because it is the one that is a security property rather than a convenience. The MCP endpoint itself can be TLS (MCP_HTTPS) and the bearer token protects it — but the hop from the server to Oracle is plaintext, so an estate that requires encryption in transit to the database is not met by this today.

There is one way round it, and it is library-only. The generated DAO factory exposes

theFactory.setDataSource(myDataSource);

and a DataSource you configured yourself can do wallets, RAC, SCAN and TLS, because that is the driver’s own job rather than ours. So a program that links the generated jar and drives the factory directly is not limited by anything on this page.

That route is not available through the web console, and there is no setting that opens it. Every config the console writes uses a hard-coded connect string built from the host, port and service you gave the Design page. The console runs each MCP server as a standalone child process, which constructs the factory itself and has no DataSource to hand it — so there is nowhere for the call above to happen. Nothing you can select changes that.

If you need a wallet, TLS to the database, or a SCAN address today, the generated jar in your own application is the only way to get one.

Will be addressed if there is commercial interest.

10. A config with many tools can outgrow Oracle’s open_cursors

Tracked as mcpdbwizard-open #7.

A generated DAO holds one open cursor per distinct tool, for the life of the session. That is statement reuse working as designed — a tool prepares its statement once and keeps it — but it means the cursors a server holds grows with the size of its tool list, not with how busy it is.

Oracle’s open_cursors defaults to 300. A config publishing more distinct tools than that will, sooner or later, meet

ORA-01000: maximum open cursors exceeded

What makes it hard to recognise is that it looks intermittent. Calling a tool you have already called costs nothing, so the tools exercised early keep working indefinitely; only the next previously-unused tool fails. A server can run for weeks and then break on a tool nobody had reached before, with no change to the config, the database or the client. Measured: a 512-tool config called once per tool produced exactly one failure, on the 300th distinct tool, and none before it.

From 2.0.16 the server says so at start-up, before any client connects. It reads open_cursors from V$PARAMETER and writes a warning once the tool count reaches 70% of the limit, or an error once it reaches the limit itself. Where the database account cannot read V$PARAMETER, it says the limit is unknown and could be exceeded rather than guessing — 300 is quoted as Oracle’s default in that message and is never used as a limit.

This is a database setting, not a product limit, and both fixes are on the database side: raise open_cursors, or split the config so that no one server publishes so many tools. Splitting has a second benefit, since a config is also the unit of authorization here. See Oracle parameters for the query that reads the current value, the ALTER SYSTEM that changes it, and how to see what a running server is actually holding.

11. On Oracle 12c only, a record collection IN plus one OUT will not bind — not planned

Tracked as mcpdbwizard-open #8.

A routine that takes a collection of records in and returns one out fails on Oracle 12c with

Attempt to set a non-existent parameter number 4; legal range is '1' to '2'

12c only, and only that combination. Same routine, same generated config, measured:

Routine shapeOracle 12c18c and later
Record collection OUT onlyworksworks
Record collection IN, or IN OUT, alongside an OUTfailsworks

Scalar index-by tables are unaffected on every version, as are record collections that are only returned.

Why 12c. Its ALL_ARGUMENTS expands a record parameter into child rows; 18c and later report only the parameter itself. The generated wrapper counts bind positions across those expanded rows, so it addresses a parameter number the statement does not have. It is the dictionary shape rather than the version badge — anything reporting the full expansion would do the same.

Workaround. On 12c, split the routine so the collection travels one way per call: one procedure that accepts it and one that returns it. On 18c and later, nothing is needed.

Why it went unnoticed. Routines of this shape have been generated on 12c for years; nothing called one until a fixture was written specifically to do so.

Not planned — tell us if you want it. Fixing it means changing how bind positions are counted for a dictionary shape only 12c produces, on a path every other supported version reaches differently. That is real risk to the versions that work today, spent on one that Oracle stopped supporting in July 2022, for a routine shape with a one-line workaround. So it stays documented rather than fixed.

That is a judgement about demand, not a refusal. If you are on 12c and this shape is one you actually have, say so — a real caller changes the arithmetic, and knowing someone hit it is the whole difference between “nobody has ever needed this” and “somebody does”.

12. A TIMESTAMP inside a ref cursor came back as Oracle’s internal form — fixed in 2.0.28

Was mcpdbwizard-open #9. Kept because the fix is in GENERATED code: a config generated before 2.0.28 keeps the old behaviour until it is regenerated, so this is still live for anyone who has not.

A PL/SQL routine whose OUT (or returned) ref cursor selects a TIMESTAMP column hands the agent this, instead of a time:

"txn_time": { "bytes": "eH4JAww3DS8wG9A=", "length": 11, "null": false, "stream": {} }

That is Oracle’s internal 11-byte representation of the value, base64-encoded. It is not decodable by a caller and carries no timezone, date or clock reading a model can use.

The same column read through that table’s own tools is correct. A get_by_pk or an index lookup on the very same TIMESTAMP returns 2026-09-03 11:54:12.791682 as text. So one config answers two ways for one column depending on which tool you asked — and only the cursor answer is wrong.

DATE columns are unaffected, which is what made this survive: the ref-cursor serializer converted java.util.Date and left everything else to the JSON mapper. oracle.sql.TIMESTAMP is not a java.util.Date — it is a Datum — so it missed that branch and was reflected field by field onto the wire.

Working around it without regenerating. Two options, in order of preference:

  1. Read the value through the table’s tools rather than the cursor, where the shape allows it.

  2. Convert in the cursor’s own SELECT, which is a change to your PL/SQL rather than to anything here:

    OPEN p_cursor FOR
      SELECT userid,
             TO_CHAR(txn_time, 'YYYY-MM-DD HH24:MI:SS.FF6') AS txn_time,
             ...

Fixed in 2.0.28 — regenerate to get it. The serializer now emits the same JDBC-escape text the table tools already emit, so both surfaces agree and a value read out of a cursor can be handed straight back to an index-lookup tool. Verified on all six supported Oracle versions.

One residual, deliberately. TIMESTAMP WITH TIME ZONE and TIMESTAMP WITH LOCAL TIME ZONE inside a ref cursor are still unconverted. Rendering either needs a database connection that the generated helper does not have, and no fixture exercises that shape, so a fix could not be tested — it is not shipped blind. Plain TIMESTAMP, which is the common case, is fixed.

Reporting something not on this list

Every open entry above is also a GitHub issue, linked from the table and from the entry itself, so a comment on the issue is the place to say you have hit one — that is what turns “documented” into “somebody needs it”. The struck-through entries are fixed and have no issue.

Open an issue on mcpdbwizard-open. The two things that make a report actionable are the tool call and the exact response, and the Oracle version — several of the entries above behave differently across the 12c and 23ai lines, and that is usually the first thing worth knowing.