v0.10.0 — queries that finish, bounded execution, an honest health probe
A correctness release. Two tools that could never return on a real database now do, every query is bounded, and the health probe stops lying about it.
All three were found by running v0.9.1 against a live TeslaMate database with ~3.1M rows in positions.
Two tools never returned
get_battery_health_summary and get_current_car_status both picked the newest position per car with a correlated subquery:
WHERE p.date = (SELECT MAX(date) FROM positions p2 WHERE p2.car_id = p.car_id)That re-scans the entire positions table once per candidate row. PostgreSQL costed the plan at roughly 2.6 × 10¹¹ — these queries did not run slowly, they did not finish.
Both now take the latest position through a LATERAL top-1 driven from cars, which also lets the car_name filter cut the set before any positions lookup happens.
| Before | After | |
|---|---|---|
get_battery_health_summary |
never returned | 0.9s |
get_current_car_status |
never returned | 1.1s |
either, with a non-matching car_name |
never returned | 0.02s |
If you have a small database you may never have noticed. If you have years of history, these two tools were simply broken.
Every query is now bounded
fetch_all — the path every bundled report takes — set no statement_timeout. QUERY_TIMEOUT_MS only ever applied to run_sql.
The consequence was worse than a slow tool call: the MCP client hung with no error at all, and killing the client did not cancel the query. PostgreSQL kept executing it. During testing, five orphaned backends accumulated and pushed the database host's load average to 8.
The connection pool now sets a connection-level statement_timeout that bounds every query the server can issue, tunable with the new STATEMENT_TIMEOUT_MS (default 30000). If your DATABASE_URL already carries a libpq options= parameter, yours is left alone. run_sql keeps its tighter QUERY_TIMEOUT_MS bound inside its own read-only transaction.
/health now checks the database
The probe never touched the database, so a container whose pool could not reach PostgreSQL — returning 500 on every single MCP call — was still reported healthy by the Docker HEALTHCHECK. This was observed directly, not theorised: {"status":"ok"} for 30 seconds while every request failed.
It now runs a real SELECT 1 under a 3s cap and returns 503 when that fails:
{"status": "degraded", "version": "0.10.0", "database": "unreachable", "detail": "…"}The healthy response gains a database field:
{"status": "ok", "version": "0.10.0", "database": "ok"}If you orchestrate this container, a database outage will now correctly mark it unhealthy — which is the point, but worth knowing before you upgrade.
Documentation
The README has been rewritten around the 0.9 feature set and cut from 229 to 122 lines. Everything it used to carry now lives in the wiki: Tool Reference, Configuration, Deployment, Writing Queries, Write Tools, and Development.
Upgrading
No configuration changes are required. STATEMENT_TIMEOUT_MS defaults to 30s; raise it if you have a legitimately long-running custom query, or set a libpq options= in DATABASE_URL to manage it yourself.
Tests: 119 → 124. Full detail in CHANGELOG.md.