dbtrail ships an MCP server, bintrail-mcp, that lets Claude — in Claude
Code, Claude Desktop, or claude.ai — search your change history and draft
recoveries in plain English. Once connected, you can ask:
"What got deleted from the
orderstable in the last 10 minutes?""Generate the SQL to bring back customer 42."
"What did customer 42's row look like at 3pm yesterday?"
"What schema changes happened this week?"
It exposes six read-only tools — query, recover (generates reversal SQL,
never runs it), recover_cascade (reversal SQL for foreign-key cascade side
effects), reconstruct (a row's state at a point in time), status, and
list_schema_changes — and never writes to your database.
First time? If you run the web console, the shortest path is the 5-minute Connect-AI guide — console URL + token + a one-click bundle, no JSON and no DSN. This page is the full reference.
You need two things on the machine where Claude runs:
- The
bintrail-mcpbinary. It ships in the release archives, the.deb/.rpm(bintrailpackage), and the Docker image — or install it directly:go install github.com/dbtrail/dbtrail/cmd/bintrail-mcp@latest
- Your index DSN — the same
BINTRAIL_INDEX_DSNyour dbtrail stack uses, e.g.user:pass@tcp(127.0.0.1:3306)/binlog_index.
Using the one-click bundle or bridge mode? Skip both — you only need the remote endpoint's URL and token; the DSN stays on the server side.
Pick the setup that matches where you run Claude. Claude Code is the simplest — start there.
Add an .mcp.json at your project root (or ~/.claude.json to use it
everywhere):
{
"mcpServers": {
"bintrail": {
"command": "bintrail-mcp",
"env": { "BINTRAIL_INDEX_DSN": "user:pass@tcp(127.0.0.1:3306)/binlog_index" }
}
}
}Restart Claude Code. The query, recover, recover_cascade, status,
list_schema_changes, and reconstruct tools appear — now ask: "What changed in the orders table in the last hour?"
Working inside the dbtrail source repo? Use
"command": "go", "args": ["run", "./cmd/bintrail-mcp"]instead — no separate install needed.
Every release publishes MCP Bundle artifacts — dbtrail-<os>-<arch>.mcpb
files on the releases page —
that Claude Desktop installs without any JSON editing:
- Download the
.mcpbmatching the machine where Claude Desktop runs. - Double-click it (or Claude Desktop → Settings → Extensions, drop the file in).
- Fill in the two-field form:
- Console / MCP endpoint URL — your bintrail MCP endpoint, e.g.
http://localhost:8090/mcp(abintrail-mcp --httpserver; keep the/mcppath) - Access token — sent as an
Authorization: Bearerheader; stored by Claude Desktop as a sensitive value
- Console / MCP endpoint URL — your bintrail MCP endpoint, e.g.
Under the hood the bundle runs the bundled bintrail-mcp in bridge mode
(--connect, next section), so it works against a LAN/VPN/tunneled deployment
— the endpoint never needs to be publicly exposed. No DSN appears anywhere:
the remote endpoint owns the database connection.
Release bundles currently cover the release build matrix (Linux amd64/arm64).
On macOS or Windows, build a host-native bundle from source with make mcpb
(output in dist/mcpb/), or configure bridge mode by hand as shown below.
bintrail-mcp --connect <url> is what the bundle runs, and you can use it
directly with any stdio MCP client: the process serves MCP over stdio locally
and proxies every request to a remote bintrail Streamable-HTTP MCP endpoint,
forwarding the remote's tools verbatim.
{
"mcpServers": {
"bintrail": {
"command": "bintrail-mcp",
"args": ["--connect", "http://192.168.1.10:8080/mcp", "--token", "YOUR-TOKEN"]
}
}
}--token is optional (a plain bintrail-mcp --http server today has no
auth); when set, it is sent as Authorization: Bearer. --connect cannot be
combined with --http or --tenant-dsns, and no DSN is needed — the remote
end resolves it. If the endpoint is unreachable or the token is rejected, the
bridge exits non-zero with a one-line error (visible in Claude Desktop's MCP
logs) instead of hanging.
Same idea, in Claude Desktop's config file
(~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"bintrail": {
"command": "bintrail-mcp",
"env": { "BINTRAIL_INDEX_DSN": "user:pass@tcp(127.0.0.1:3306)/binlog_index" }
}
}
}Restart Claude Desktop.
If the index runs on a different host, start the server there over HTTP:
BINTRAIL_INDEX_DSN='user:pass@tcp(127.0.0.1:3306)/binlog_index' \
bintrail-mcp --http :8080Then connect from your laptop with the one-click bundle or
bridge mode above (--connect http://192.168.1.10:8080/mcp). If you'd rather not install a binary locally,
the Python proxy.py does the same job — see
proxy.py (remote bridge) below.
claude.ai and the mobile apps connect outbound to a public HTTPS URL and
authenticate with OAuth. Neither bintrail-mcp nor bintrail-console speaks
OAuth, so this path needs an OAuth-capable gateway in front — an advanced,
optional setup. Most people use Claude Code or Claude Desktop above instead;
those reach a private index over stdio or bridge mode and need no public
endpoint at all.
- dbtrail's hosted gateway (managed-service customers). dbtrail operates one
at
https://mcp.dbtrail.com/mcpand provisions your tenant. This requires a dbtrail account — it is a managed service, not part of this repository. - Your own gateway. This repository does not ship one. The binaries here are
bintrail,bintrail-console,bintrail-mcp, andbintrail-pg— none of them terminate OAuth. If you want the claude.ai path on your own infrastructure, put an OAuth-capable reverse proxy (any of the usual identity-aware proxies) in front ofbintrail-mcp --httpor the console's/mcpendpoint, and have it forward the authenticated request. For a shared backend serving several indexes,bintrail-mcp --tenant-dsnsresolves a per-tenant DSN from anX-Bintrail-Tenantrequest header your proxy sets.
Once such a gateway is reachable at a public URL: in claude.ai → Settings →
Integrations → Add custom integration, enter the gateway URL (your domain, or
https://mcp.dbtrail.com/mcp for managed customers), complete the OAuth login,
and the six tools appear. Works from web, Desktop, and mobile; tokens refresh
automatically.
| Tool | CLI equivalent | What it does |
|---|---|---|
query |
bintrail query |
Search indexed row changes with filters |
recover |
bintrail recover --dry-run |
Generate reversal SQL (never executes it) |
recover_cascade |
bintrail recover-cascade --dry-run |
Generate reversal SQL for foreign-key ON DELETE/ON UPDATE cascade side effects InnoDB ran below the binlog — the child rows plain recover cannot see. Fails with the reasons when the synthesis is provably partial, unless allow_incomplete is set |
reconstruct |
bintrail reconstruct |
A single row's full state at a point in time (needs a baseline) |
status |
bintrail status |
Indexed files, partitions, and summary |
list_schema_changes |
reads schema_changes (see DDL tracking) |
DDL changes recorded while indexing/streaming, with the full statement, binlog coordinates, and the covering snapshot_id (null = no auto-snapshot) |
All six are read-only — annotated ReadOnlyHint: true and IdempotentHint: true,
so the client knows they're safe to call repeatedly and never modify state.
If your client lists tools beyond these six, you are running a build that
registers extras through the extension seam (ext/mcpext) — a distribution
that wraps this core, not the stock binary. Such tools resolve their index
through the same routing as the built-in six, so they read the server you
selected and inherit its posture, including the console's refusal of a
tool-level index_dsn.
The same six tools are also served by the web console at /mcp (Streamable
HTTP, console-token auth, per-server routing by URL path) — if you already run
bintrail-console, you may not need this binary at all; see
console.md.
list_schema_changes accepts schema, table, ddl_type
(CREATE/ALTER/DROP/RENAME/TRUNCATE, prefix-matched so ALTER matches
ALTER TABLE), since, until, limit (default 100), and uncovered_only
(only changes whose snapshot_id is null — the rows behind the status
tool's "DDL(s) detected without auto-snapshot" warning); results come back
newest-first.
Prompts that work well:
"Someone deleted rows from
usersthis afternoon — show me what and when.""Generate the SQL to undo the bad UPDATE on order 1234."
"What was customer 42's row before the last change?"
The query and recover tools share the CLI's filters and validation. Beyond
the basics (schema, table, pk, event_type, gtid, since, until),
both accept:
pks— multiple primary key values (each pipe-delimited for composite keys); requiresschemaandtable, mutually exclusive withpklimit_per_pk— cap events perpk_valuesto the latest N (0 = unlimited); requirespkorpkscolumn_eq— repeatablecolumn=valueequality filtersflag— table/column flag filterprofile— RBAC table-deny + column-redactionno_archive— disable Parquet archive auto-routing (see below)
query also takes changed_column — events that touched a given column.
recover does not: a changed-column filter selects row versions, and
reversing a filtered subset of a row's history can produce a state that never
existed — the same reason the bintrail recover CLI has no --changed-column
flag (a recover call passing changed_column is rejected by the tool's
input schema).
query also takes query_hash — the 64-character statement digest of an event
you already have, returning every event that statement produced across every
table it touched. It selects a statement shape (literals are normalised
away), so it is a read filter only: recover does not accept it, because a
reversal scoped to a shape would undo executions nobody named. It is refused on
surfaces that withhold statement text (the console's /mcp) and whenever a
profile is active — the digest is blanked on every returned event there, so
filtering on it would confirm what is withheld.
The pk parameter takes the stored pk_values spelling — for a binary PK
whose bytes are not valid UTF-8 that is 0x + uppercase hex, see
Binary primary keys.
query additionally takes format (json, table, or csv). Time values
(since / until) accept MySQL datetime (2006-01-02 15:04:05), RFC 3339, or
date-only (2006-01-02) — the same formats as the CLI.
Query row ceiling (#654). The
query tool caps an explicit limit at a hard ceiling (default 1,000,000
rows; env BINTRAIL_MCP_QUERY_MAX_LIMIT) so an oversized request can't exhaust
the long-lived server's memory; when it caps, the returned text says so. A limit
of 0 or omitted falls back to the tool default (100), not unbounded — the
unbounded path is the bintrail query CLI, not the agent-facing tool. The
recover tool is not capped this way: it refuses oversized output on a memory
budget instead (see Query and Recovery).
Archive auto-discovery. query and recover automatically discover Parquet
archive sources from archive_state in the index database (or from the
BINTRAIL_ARCHIVE_S3 + BINTRAIL_ID env vars). When archives are found, results
from MySQL and Parquet are merged, deduplicated, and sorted before output or SQL
generation. Pass no_archive to disable this.
When archives misbehave, the two tools diverge on purpose
(#1285): query degrades and
says so — the result carries trailing Warning: archive_source_skipped: … lines
for sources that failed to read, Warning: archive_discovery_failed: … when
discovery itself failed, and Warning: archive_scan_incomplete: … when the
misfiled-archive registry scan failed — while recover refuses to generate
a script in any of those cases: a reversal missing part of the matched events is
a partial undo, not a smaller one. The refusal names the escape hatch — retry
with no_archive: true to generate from the live index only, accepting that
archived events will not be reversed.
Index DSN. The server connects to the index via the BINTRAIL_INDEX_DSN
environment variable (set once at startup) or the per-call index_dsn parameter,
which overrides the env var. Set the env var at startup so callers don't repeat
it on every call.
recover is delta-only: it reverses events it actually has, so it can't
answer "what did this row look like at 3pm" for a row nobody touched in the
retained window. reconstruct folds a baseline snapshot (from
bintrail baseline) with the events after it, so every
column resolves — the same engine behind bintrail reconstruct and the console's
Time-travel tab.
Parameters: schema, table and pk (required; pipe-delimited for composite
keys), at (defaults to now), history (every transition instead of one state),
and allow_gaps.
Baseline location: baseline_dir (a local directory) or baseline_s3
(s3://bucket/prefix) per call, falling back to the BINTRAIL_BASELINE_DIR /
BINTRAIL_BASELINE_S3 env vars — set those at startup, like the index DSN. When
the console serves /mcp these parameters are rejected: the baseline is that
server's own configuration, and an MCP client must not point the console at
arbitrary storage.
allow_gaps defaults to false, unlike query's degrade-with-warnings
posture (recover likewise refuses on archive trouble — see Archive
auto-discovery above). A hole in the
captured history makes a reconstruction silently wrong (a state that never
existed), not merely incomplete, so the tool aborts with an actionable error and
you opt in explicitly. It also refuses outright — no override — when a
TRUNCATE/DROP/RENAME hit the table inside the window: no archive can refill
that, and the fold would resurrect rows that are gone.
The same allow_gaps: false default covers two further refusals, both of which
allow_gaps: true overrides — and when you override one, the reason comes back
as a capture_gap: entry in warnings, so a known-incomplete result is never
returned as a clean one:
- the stream recorded events permanently lost at the source inside the
window (
gap_lost_at, shown asGAP LOSTbybintrail status) — no archive can refill those; - the index is too old to answer the question: its
stream_statepredates the gap-tracking columns, so a permanent loss cannot be ruled out. Migrating the index schema (any indexing or streaming command does it) enables the real check.
Archive sources come from archive_state (not the BINTRAIL_ARCHIVE_S3 env
pair), because gap detection reads the same registry — run
bintrail archive reconcile if a source drifted.
Returns JSON: state (or history), found, deleted, baseline_time,
event_count, and any warnings (a coverage gap or permanent capture loss
you allowed, a stale-baseline fallback, a suspected
PK-changing UPDATE the fold can't follow, or — under allow_gaps — an archive
source that failed or could not be discovered, and coverage the planner could
not verify).
When the index runs on a different machine than Claude Desktop, proxy.py
bridges Claude Desktop (which speaks stdio) to a remote bintrail-mcp --http
server:
Claude Desktop → proxy.py (stdio, runs locally) → bintrail-mcp --http :8080 → MySQL
It's a single Python file with zero dependencies (stdlib only, Python 3.7+).
-
Copy
proxy.py(fromcmd/bintrail-mcp/proxy.py) to your laptop. -
Add it to
~/Library/Application Support/Claude/claude_desktop_config.json:{ "mcpServers": { "bintrail": { "command": "python3", "args": ["/Users/you/proxy.py"], "env": { "BINTRAIL_SERVER": "http://192.168.1.10:8080/mcp" } } } } -
Test connectivity before restarting Claude Desktop:
BINTRAIL_SERVER=http://192.168.1.10:8080/mcp python3 ~/proxy.py <<'EOF' {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}} {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}} EOF
Two JSON responses = working. No response or a connection error = check the server address and firewall.
Stale session after a restart: when bintrail-mcp --http restarts, existing
sessions are invalidated, but proxy.py still holds the old Mcp-Session-Id and
tool calls start failing. Fix: restart Claude Desktop (that restarts the proxy
and clears the session) — no need to restart the HTTP server.