Repository navigation
Description
Description
seed_table commits destructive changes before validating or inserting the replacement data. An ordinary invalid value causes the tool to return an error after deleting all existing rows. With recreate=true, an invalid column type drops the entire existing table before returning the validation error.
Verified against MiniLake 1.9.0 source at local commit e24f46a7753ff0be30df3d83978c370c817f89cd (based on upstream fe8837dcfac9df621dfabc440abc07ebb4cdbcbb; the affected code is unchanged). Reproduced using a real disposable HTTP server and fresh data, with Python 3.14.3, DuckDB 1.5.6, FastAPI 0.162.0 and, for MCP, mcp 1.30.0. No mocks or user data were used.
Affected Components
- mcp/tools/composite.py:49 —
seed_table - Drop/create sequence at lines 90–109
- Separate DELETE/INSERT requests at lines 118–122
- MCP endpoint
/mcp, toolseed_table
Steps to Reproduce
Start a disposable instance with MINILAKE_MCP=1. Use an initialized MCP client session against /mcp.
- Call
seed_tablewith:{"full_name":"seed_guard.s.t","columns":[{"name":"id","type_text":"INT"}],"rows":[[41],[42]]} - Verify
SELECT * FROM seed_guard.s.treturns rows 41 and 42. - Call
seed_tableagain with:{"full_name":"seed_guard.s.t","columns":[{"name":"id","type_text":"INT"}],"rows":[["not an integer"]]} - Query the table after the tool error.
- Restore the original two rows using step 1, then call:
{"full_name":"seed_guard.s.t","columns":[{"name":"id","type_text":"NOT_A_TYPE"}],"rows":[[41]],"recreate":true} - Query the table after the second error.
These calls were verified using the real MCP SDK over Streamable HTTP and the real DuckDB-backed Statement Execution path.
Expected Behavior
Reject an invalid replacement without losing the existing table/data. Validate column types and replacement rows before destructive changes, and make the replacement atomic.
Actual Behavior
- Step 3 returns
isError=truewithConversion Error: Could not convert string 'not an integer' to INT32. The original table is now empty:[]. - Step 5 returns an unsupported-column-type error. The subsequent SELECT fails because the original table has been dropped.
Root Cause Analysis
The normal path sends an autocommitted DELETE before sending a separate INSERT. An INSERT error cannot roll back the earlier DELETE. The recreate path sends DROP TABLE and removes metadata before the create endpoint validates the new schema. There is no staging or transaction encompassing the replacement.
Impact
A failed fixture/data refresh destroys a previously valid dataset. A model or developer can correct the input after receiving an error, but the original data is already lost. This is real on-disk data loss in the supported local workflow, not merely an incorrect result message.
Suggested Fix
Validate the requested schema, row arity and value conversions first. Build and populate a staging table before replacing the target, or execute validated deletion/insertion in one locked transaction for the relevant catalog. For recreate, retain the original physical table and metadata until the replacement is ready; roll back both on failure.
Test Coverage Gaps
MCP tests cover successful seed calls, quote escaping, malformed names and invalid types when creating a new table. Add invalid values/types/row counts while replacing a populated 56AB table, with and without recreate, and assert the original schema, metadata and rows survive each failure.
Severity
High
Confidence
High — both destructive failure paths reproduced through the real MCP protocol.
Recommended Labels
bug, high-priority
Existing Issue Matches
No reasonable duplicate found. Reviewed all open issues, the closed-issue search (no results), and all open PRs, then searched the affected functions/components and symptoms. Open PR #54 fixes duplicate Arrow columns only; #29 updates virtualenv. Neither addresses this defect. The documented local/single-user limitations were checked. Searches included seed_table, MCP, rollback, transaction, INSERT and replacement data loss.
Activity