Skip to content

fix(servers): data-unavailable error for every DB tool, no DB names - #601

Merged
ShuxinLin merged 4 commits into
mainfrom
fix/missing-db-guard
Oct 2, 2026
Merged

ShuxinLin merged 4 commits into
mainfrom
fix/missing-db-guard

Conversation

@ShuxinLin

@ShuxinLin ShuxinLin commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Follow-up to #554. The missing-database message from #554 was hidden in IoT/FMSR by older if not db checks, and several other servers never used it. I audited every MCP tool backed by CouchDB (85 tools, 6 servers). In each case the tool was run against (a) a CouchDB with no databases, (b) an unreachable CouchDB, and (c) databases that exist but are empty. The test databases had sentinel names, so any leaked name would show up.

Every DB-touching tool now returns the shared message when its database is missing or unreachable:

the data source does not exist or is unreachable in this environment; the data is unavailable, do not retry with other arguments

No error contains a database name, host, port or URL. Wrong-key messages (unknown site, No sensors found…, model 'x' not found, …) are unchanged when the database exists.

Fix Details

  • servers/db_errors.py (new): DATA_UNAVAILABLE, plus db_failure_text(exc), which replaces requests/httpx exception text (it embeds the URL, and so the DB name) with database request failed (<ExcType>).
  • iot: couchdb3.Database.__bool__ sends a HEAD request, so if not iot_db fired before the missing-DB path ("IoT records database not connected"), and it raised ConnectionError with the URL when CouchDB was down. The tool guards now use _missing_db_error, and helpers check is None. unknown site is checked against a missing registry first, because the registry falls back to ["MAIN"]. sites() reports a missing registry instead of returning ["MAIN"].
  • fmsr: same if not fm_db issue. Lookup failures no longer include client exception text.
  • wo: a connection failure raised "All connection attempts failed" and now raises DatabaseUnavailable. HTTP errors no longer embed the request URL (database request failed (HTTP 500)). get_failure_codes returns the shared message when the database is unavailable. next_wonum now retries only on CouchConflict (409), so HTTP errors aren't reported as counter contention.
  • vibration: if not db raised with the host when CouchDB was down. It now checks is None.
  • utilities: the catalog tools returned CouchDB's raw error body when the DB was missing, and a connection error naming the host when it was down. Both now return the shared message.
  • tsfm: CouchStore treated a 404 for a missing database as a missing document. A catalog that was never loaded therefore read as empty ("models": [], model 'x' not found), and with CouchDB down every catalog and run tool leaked /<catalog_db>/_find plus the host. Requests now raise StoreUnavailable or StoreError without the URL. Model and feature catalog reads and registrations check that the catalog exists (Store.exists). Result and run collections are still created on first write, so a missing one still means "none yet".

Wire note: sites() now returns Union[SitesResult, ErrorResult], the same as every other IoT tool. Its text content is unchanged. Its structuredContent is now wrapped in {"result": …} like the other tools. No caller in the repo reads the flat shape.

Impact on Benchmarking

  • No change to baselines: This fix only improves stability/performance.
  • Baseline change: This fix corrects a scoring error. (Please provide "Before vs. After" results).

Related Issues

Verification Steps

  1. uv run pytest src/ -q -k "not integration": 696 passed. The 8 failures left (evaluation/test_static_json_scorer.py, observability/test_file_exporter.py) also fail on main.
  2. New tests per server cover an unreachable CouchDB (a real couchdb3/httpx/requests client against a closed port) and a missing database, and assert the shared message with no DB name. On main, the IoT regression tests fail with IoT records database not connected / unknown site SITE_A.
  3. Audit harness (not committed): a fake CouchDB plus a closed port, calling all 85 tools. Results: 0 leaks in any mode, and 0 false "unavailable" when the databases exist but are empty. The 24 tools that never touch CouchDB (DSP, time, local files, templates) are unaffected.

Checklist

  • I have added tests that prove my fix is effective.
  • My code follows the project's Ruff formatting and linting rules. (ruff isn't installed locally and CI doesn't run it; I checked line lengths by hand)
  • I have signed off my commits (DCO).

couchdb3.Database is falsy when its database does not exist (__bool__ sends
a HEAD request), so the `if not iot_db` / `if not fm_db` guards fired before
the missing-database message from #554 and returned "IoT records database
not connected" / "database not connected". With CouchDB unreachable,
__bool__ raised instead.

Tool guards now go through _missing_db_error (FMSR: check()), helpers test
`is None`, and an unknown site is checked against a missing asset registry
first, since the registry falls back to ["MAIN"] when it cannot be read.

Signed-off-by: Shuxin Lin <linshuhsin@gmail.com>
Audited every CouchDB-backed MCP tool against a server with no databases and
an unreachable server. Gaps fixed:

- servers/db_errors.py: shared DATA_UNAVAILABLE message, and db_failure_text,
  which drops the URL/host/database name that HTTP client exceptions carry.
- iot: sites() reports a missing registry instead of falling back to MAIN;
  exception paths no longer pass raw client text through.
- wo: connection errors raise DatabaseUnavailable (was "All connection
  attempts failed"); HTTP errors no longer embed the request URL;
  get_failure_codes returns the shared message when the DB is unavailable.
- vibration: truthiness on couchdb3.Database sent a request and raised when
  CouchDB was down, leaking the host; use `is None`.
- utilities: catalog tools return the shared message instead of CouchDB's raw
  error body or a connection error with the host.
- fmsr: shared message; lookup failures no longer include client exception text.

Signed-off-by: Shuxin Lin <linshuhsin@gmail.com>
…able

CouchStore returned None/[] for a 404 on a missing database, so a catalog that
was never loaded read as empty ("model 'x' not found", "models": []), and with
CouchDB down every catalog/run tool returned a requests error that named the
collection, host and port.

CouchStore requests now raise StoreUnavailable when the server is unreachable
and StoreError without the URL on HTTP errors. Model and feature catalog reads
and registrations check the catalog exists first (Store.exists). Result and run
collections are still created on first write, so missing ones read as empty.

Signed-off-by: Shuxin Lin <linshuhsin@gmail.com>
HTTP errors are now CouchError (without the URL), so next_wonum's
`except CouchError: continue` would retry a 500/401 or an unavailable
database five times and report counter contention. Conflicts raise
CouchConflict, and only those are retried.

Signed-off-by: Shuxin Lin <linshuhsin@gmail.com>
@ShuxinLin ShuxinLin changed the title fix(iot,fmsr): report missing database, not "not connected" fix(servers): data-unavailable error for every DB tool, no DB names Oct 2, 2026
@ShuxinLin
ShuxinLin merged commit 6ec35fb into main Oct 2, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants