MCPDBWizard

Documentation  ·  Operating

Known issues

Current as of 2.0.12. 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 when
1Instructions promise write tools a read-only config does not exposeAny config whose tables are not full CRUD
2A DATE inside a record will not accept a bare 1980-01-01A routine taking a %ROWTYPE or record parameter with a date field
4A RAW field inside a record publishes no formatfixed in 2.0.12
3Record keys are the generated field names, not the column namesAny record parameter
5An index-by parameter with a record element fails on the way outReading such a collection back
6A too-long value names the position, not the fieldAny insert or update that overflows a column
7A VECTOR routine parameter crosses as a dense array onlyBinary or sparse vectors through PL/SQL
8A DML SQL statement cannot hand back a generated keyAn INSERT of your own into an identity or sequence-keyed table
9No TNS aliases, wallets, TLS to the database, RAC or SCANAny estate that is not reachable as host / port / service

1. Instructions can promise writes that are not there

A config whose tables are marked read-only still gets server instructions reading

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

because that sentence is built from the presence of tables, not from their CRUD flags. The tools themselves are correct — no insert tool is emitted — so a model that believes the instructions will call a tool that does not exist and get unknown tool back.

This matters 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. Advertising writes misdescribes the security posture of the config, even though it cannot weaken it.

Workaround. Put the truth in the config’s own instructions — the Design → Service Options tab lets you write the server’s instructions text, and yours is prepended to the generated sentence.

2. A DATE inside a record needs the full date-and-time form

These two behave differently for the same Oracle type:

Where the DATE is1980-01-011980-01-01T00:00:00
A parameter of its ownacceptedaccepted
A field inside a recordrejectedaccepted

A scalar DATE parameter goes through a lenient parser that treats a bare date as midnight, accepts an offset and converts it, and rejects anything it cannot consume whole. A record’s fields are deserialized by the JSON mapper instead, which is pinned to yyyy-MM-dd'T'HH:mm:ss exactly.

From 2.0.12 the schema tells you, which it did not before. A date field inside a record is published with a description reading “ISO-8601 date-time, pattern yyyy-MM-dd'T'HH:mm:ss — the T form is REQUIRED inside a record, unlike a scalar date parameter, which also accepts a bare 1980-01-01”. The pattern in that sentence is taken from the same constant the mapper is pinned to, so it cannot drift from what is actually enforced.

The asymmetry itself remains, which is why this entry is still here: the record path is still strict where the scalar path is lenient. What changed is that you can now discover that from tools/list instead of from a rejection.

Workaround. Always send the T form inside a record. It is accepted on both paths, so you can use it everywhere and not think about which one you are on.

3. Record keys are the generated field names

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

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

Same data, same sessionResult
PL/SQL table, SQL object array, nested tablereturns correctly
Index-by tableORA-06532
Writing into the same index-by parametersucceeds

So it is the outbound binding specifically, not the collection and not the package.

Workaround. Where the PL/SQL is yours to change, return a nested table or an object array instead. Index-by tables of scalars are unaffected.

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

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

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

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

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.

Reporting something not on this list

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.