Replace the whole filter panel with one sentence.
Your users stop translating what they want into dropdowns, checkboxes and date pickers — they just say it, in their own words. QueryForge turns that into a query you can trust.
The model never writes the query. It fills in a typed Query AST your config constrains, and deterministic Go compiles that to whatever database you run — Postgres SQL and MongoDB today, and a new backend is a generator, not a rewrite.
SELECT or a find. There is no operator or config option that can write, update, or delete — and the library never connects to your database. It hands you the query; running it stays yours.A user asks a question in their own words:
"orders over 500 dollars in the last 30 days that were not cancelled, newest first, top 20"
You get a parameterized query you can trust — values bound, never pasted in:
SELECT status, created_at, amount, customer_name FROM orders
WHERE (status <> $1 AND created_at >= $2 AND amount > $3)
ORDER BY created_at DESC LIMIT 20
args: ["CANCELLED", "2026-06-29T08:49:06Z", 500]
The same question, against MongoDB, compiled from the identical intermediate representation:
{ amount: {$gt: 500}, createdAt: {$gte: "2026-06-29T08:49:06Z"}, status: {$ne: "CANCELLED"} }
Because you cannot check what comes back. Ask a model for SQL directly and it can invent a column, invent a table, quietly widen a filter, or hand you a statement that is only probably right — and you have no way to tell before you run it.
QueryForge never lets the model near a query string. The model fills in a typed form — the Query AST — constrained to a vocabulary your config registers. Ordinary deterministic Go does everything after that.
Natural language → [ AI planner ] → Query AST → [ generators ] → SQL / Mongo
(unbounded) model + config (typed) pure code, no AI
Everything that must be guaranteed lives on the right-hand side, where it can be tested offline.
Rejected by the validator before anything compiles, with "did you mean" suggestions.
The model emits structure, never a string. Values are bound; only config-supplied identifiers reach the statement.
Not expressible. The AST has no mutation node, so the guarantee holds by construction.
returnable: false is enforced on the default projection too, not only explicit select.
You get a typed refusal, not a plausible query built on a lookalike field.
Scope filters are AND-ed on after the model has answered — it never learns the field exists.
Gemini, Groq, Anthropic, local Ollama — a config change, never a code change. Fallback chains included.
Pick how you want to call it. Every one of these needs the same two things: a config file describing your data, and your model API key in the environment. Nothing else — no server, no Docker, no database connection.
Install the server:
go install github.com/awsaman-ai/queryforge_mcp@latest
Add it to claude_desktop_config.json:
{
"mcpServers": {
"queryforge": {
"command": "queryforge-mcp",
"args": ["--config", "orders.config.json"]
}
}
}
Restart Claude and ask "cancelled orders over $200" — it comes back as a validated query. Docs →
Install it:
go get github.com/awsaman-ai/queryforge
Ask a question:
cfg, _ := qf.LoadConfig("orders.config.json")
engine := qf.New(cfg)
res, _ := engine.Translate(ctx,
"cancelled orders over 200 dollars", "sql", nil)
fmt.Println(res.Query.SQL) // SELECT … WHERE (status = $1 …)
fmt.Println(res.Query.Args) // [CANCELLED 200]
No third-party dependencies — standard library only. API docs →
Add two dependencies — the classes, and the engine binary for the platform you run on.
Set queryforge.version to the
latest on Maven Central:
<properties>
<queryforge.version>LATEST</queryforge.version>
</properties>
<dependency>
<groupId>io.github.awsaman-ai</groupId>
<artifactId>queryforge</artifactId>
<version>${queryforge.version}</version>
</dependency>
<dependency>
<groupId>io.github.awsaman-ai</groupId>
<artifactId>queryforge</artifactId>
<version>${queryforge.version}</version>
<classifier>linux-amd64</classifier>
</dependency>
Ask a question:
QueryForge forge = QueryForge.postgres(
Paths.get("orders.config.json"));
String sql = forge.query("cancelled orders over $200").toSql();
List<Object> args = forge.query("…").toArgs();
Zero runtime dependencies, not even a JSON library. Docs →
Install it:
pip install queryforge-ai
Ask a question:
from queryforge import QueryForge
qf = QueryForge.postgres("orders.config.json")
pending = qf.query("cancelled orders over $200")
print(pending.to_sql()) # SELECT … WHERE (status = $1 …)
print(pending.to_args()) # ('CANCELLED', 200)
The engine ships inside the wheel — no Go toolchain needed. Docs →
Whichever you pick, the answer is the same query: the model fills in an AST, your config
validates it, and deterministic code compiles it. Swap postgres for
mysql or mongo and the same sentence compiles for that backend instead.
The last argument to Translate is the scope — filters your application imposes on every query, whatever the user asked. Pass nil for none, or a map for multi-tenancy:
res, err := engine.Translate(ctx, "cancelled orders over 200 dollars", "sql", qf.Scope{
"subscriptionId": session.SubscriptionID,
"userId": session.UserID,
})
// … WHERE (subscriptionId = $1 AND userId = $2 AND status = $3 AND amount > $4)
bugs.csv.Apache-2.0 licensed. Phase 1: SQL (Postgres dialect) and MongoDB.