Letting AI agents query production databases without giving them a database account A developer released Truejoin, an open-source (Apache 2.0) data gateway that lets AI agents query production PostgreSQL, MySQL/MariaDB, SQL Server and Oracle databases without granting them a database account. Truejoin rewrites each query before execution, replacing named tables with subqueries that carry the caller's row filters and column masks, so joins, CTEs and aggregates only ever read the restricted view. On 16 held-out questions over a banking-shaped schema with qwen2.5-coder:7b, the developer reports 12 correct answers and 0% hallucination using approved business terms with no model, versus 8 correct and 31.2% hallucination when the model wrote SQL from the DDL. Once a company has an AI assistant, someone asks whether it can answer questions over production data. I do data consulting for companies that keep everything on-premises, and I kept hearing that question. The usual answer is a read-only database account plus a text-to-SQL prompt. That leaves three things unsolved: I built Truejoin https://github.com/yasinyaman/truejoin to close those three, on your own servers, with Oracle and SQL Server included. Truejoin is an open-source Apache 2.0 data gateway. It connects to PostgreSQL, MySQL/MariaDB, SQL Server and Oracle, and serves them three ways from one Python process and one config file: Every query, whoever wrote it, runs under the caller's row rules and column masks and leaves one audit event. It is not a warehouse, an ETL tool or a BI dashboard. It reads the databases you already have, live. Filtering the result is too late. An aggregate over rows the caller may not see has already leaked, and so has a masked column read through an alias, a join or a subquery. So Truejoin rewrites the query before it runs. Each table the query names is replaced by a subquery that carries that caller's row conditions and masks. Aliases, joins, CTEs and aggregates then all read the restricted view, because there is nothing else to read. The rules are a few lines of config trimmed here; the full nesting is in config/reference.yaml : row filters: orders: {column: tenant id, value: "${tenant}"} row filter exempt roles: dpo masking: enabled: true rules: {email: partial, national id: hash} Three things follow from doing it this way: A model reads the schema and drafts table and column descriptions, PII labels, relationships and business terms truejoin catalog analyze . Sample values are masked before they reach it, and with Ollama nothing leaves your network. A person then reviews, approves and certifies the draft. Nothing unapproved is used. The approved catalog does two jobs. Masks key off its semantic types, and questions are read against its business terms. answer question first reads a question against the approved vocabulary, with no model involved. It returns the request it understood and the SQL it compiled, so you can check what was asked before you trust the answer. Joins follow declared foreign keys, so a request cannot write a wrong join. Only a question the vocabulary cannot read goes to a model, and that answer is marked as model-written. I benchmarked the approaches on a banking-shaped schema with qwen2.5-coder:7b , graded by result set against a gold query. On the 16 held-out questions: | Approach | Correct of 16 | Hallucinated | |---|---|---| | Model writes SQL from the DDL | 8 | 31.2% | | Model writes SQL from the catalog | 10 | 18.8% | | Approved terms, no model | 12 | 0% | Sixteen questions is a small set and the intervals are wide. The table in the repo shows 13 for the last row; one of those was right by luck and is now refused, so I count 12. The method and every caveat are in bench/README.md https://github.com/yasinyaman/truejoin/blob/main/bench/README.md . Truejoin merges three earlier projects of mine into one package and one process: truejoin mcp convert , which turns an OpenAPI or Postman description into an MCP server definition. Their code moved over with its history. The query rewriter is the one component with no ancestor in the three, and it carries the privacy promise, so its tests were written first. You need Python 3.12+ and uv https://docs.astral.sh/uv/ . git clone https://github.com/yasinyaman/truejoin.git cd truejoin uv sync --all-extras export TRUEJOIN SECRET KEY=$ uv run python -c 'from cryptography.fernet import Fernet; print Fernet.generate key .decode ' uv run truejoin config validate -c config/database.yaml uv run truejoin users add admin --permission all -c config/database.yaml uv run truejoin serve -c config/database.yaml The console is at http://localhost:8000/ui . Any MCP client can connect over HTTP at http://