Confirming this on our self-hosted instance — with additional detail on the root cause and a data-integrity angle that may not be in the thread yet.
Environment
- Baserow self-hosted, version 2.2.2
- Backend served via ASGI (Server: uvicorn confirmed in response headers)
- PostgreSQL backend
- Accessed through the built-in MCP server endpoint (/mcp//sse) via the mcp-remote stdio<->SSE bridge
Symptom
Every MCP write tool call (create_rows) returns:
Error: select_for_update cannot be used outside of a transaction.
MCP read tools (list_databases, list_tables, get_table_schema, list_table_rows) work fine. The identical write via the REST API (POST /api/database/rows/table/{id}/) also works fine. So the fault is specific to the MCP server’s write path.
Data-integrity angle (the important part)
Despite returning an error, the row is actually created and committed. The caller sees a failure, but a row appears in the table. A client that retries on error therefore produces duplicates — in our case 4 retries created 4 duplicate rows plus the
original. This isn’t just a feature outage; it’s silent partial-write + duplication.
Root cause analysis
select_for_update cannot be used outside of a transaction is Django’s TransactionManagementError, raised when QuerySet.select_for_update() runs with the DB connection in autocommit mode and no active transaction.atomic() block.
- Baserow’s REST mutating endpoints execute inside a transaction (ATOMIC_REQUESTS / explicit atomic()), so the row-creation logic — which internally uses select_for_update to lock rows for order computation and link-field integrity — works correctly.
- The built-in MCP server’s write tools dispatch into the same RowHandler code, but the MCP tool-handler path is not wrapped in transaction.atomic(). Under autocommit, the internal select_for_update raises immediately.
- Because there is no surrounding transaction, the work completed before the failing select_for_update (the row INSERT, and link/m2m writes) is auto-committed and never rolled back → error returned to the client and an orphan row left behind.
Workaround
Use the REST API with a Database token for writes; the MCP endpoint remains usable for reads.