Skip to content

0.17.0

Latest

Choose a tag to compare

@cevheri cevheri released this 26 Sep 22:06
· 113 commits to main since this release
Immutable release. Only release title and notes can be modified.

An MCP server for the AI client you already use

Studio now serves the Model Context Protocol at /api/mcp, so Claude Code, Codex, Cursor, VS Code, Gemini CLI or OpenCode can read the connections an operator opts in, while the database credentials stay on the server. It is off until an operator turns it on (#1070, closes #246).

The client gets three read-only tools. list_connections and inspect_schema work on every engine. run_read_query runs one SELECT, WITH, VALUES, TABLE or EXPLAIN without ANALYZE on PostgreSQL, SQLite, DuckDB and SQL Server. Read-only is the database's own enforcement, not a filter over SQL text: a read-only transaction on PostgreSQL, a read-only open on SQLite and DuckDB, and on SQL Server a check that the login cannot write. That is also why a PostgreSQL or SQL Server connection needs a least-privilege login before run_read_query accepts it. An answer holds at most max_rows rows, 100 by default and 500 at most, and 32 KiB, and says where the next page starts; anything read from a database is preceded by a notice telling the model it is untrusted data.

Access is narrow by construction. Only seed connections marked mcp: true are visible, filtered by the caller's role. Each user mints a bearer token at /settings/mcp, shown once and valid for 30 days by default; the session cookie opens nothing on this route, and changing LIBREDB_MCP_TOKEN_LABEL revokes every token at once. Origin is checked on every method, and Host too on a loopback bind, and JSON-RPC batches are refused. Every tool call is audited as mcp_operation before any database is reached, and a call whose audit record cannot be written does not run; an Origin, Host or token refusal is logged as permission_denied.

It runs on the official TypeScript SDK and speaks revision 2026-07-28 as well as the 2025-11-25 and 2025-06-18 revisions most clients still send. It was measured live with the SDK client, Claude Code and OpenCode against PostgreSQL, SQL Server, SQLite, DuckDB and LibreDB; the docs carry a configuration for Codex, Cursor, VS Code and Gemini CLI, which were not measured. Three limits to know before you enable it. Authentication is a static bearer token with no OAuth, so a client needs its Authorization header configured. A cancel ends Studio's wait rather than the statement, and a client on a 2025 revision can cancel only by disconnecting; PostgreSQL and SQL Server end the statement themselves at timeout_ms. And a long SQLite query holds the whole Studio process until it finishes, because that driver is synchronous. Setup for each client, the Helm mcp block and the full list of limits are in docs/MCP.md.

Prometheus, read-only, over its HTTP API

Prometheus is the first engine in a new time-series family and the first with a query language of its own: the editor sends PromQL to the server unchanged, over the HTTP API, with no driver dependency at all (#1104, closes #1085). The explorer lists metrics, whose columns are their label names followed by timestamp and value, and rule groups, rules, scrape pools and targets; clicking a metric runs its selector as an instant query. Ranges are written in PromQL itself, x[1h] for raw samples or rate(x[5m])[1h:1m] for a stepped series, and a stepped series charts as lines. Only read endpoints are called, never the admin or lifecycle API. A connection authenticates with Basic over a user and password, or sends the password as a bearer token when the user is empty.

It was verified against Prometheus 3.13.3. VictoriaMetrics answers the same API and connects as prometheus, measured partial at 20 of 36 surfaces. Grafana Cloud's hosted Prometheus sits under a path prefix that this version cannot reach. In agent plan mode, the model drafts PromQL over the server's real metric names and executes nothing.

One limit matters for sizing. A response is read whole into the one Node process that serves every connection, under a 32 MiB cap, and a body well under that cap can exhaust the 384 MiB heap the image runs with: 24 MB alone did in the measurement recorded as D110 in docs/BACKLOG.md. An exhausted heap ends every in-flight query on that instance, of every engine. A small server answers in about 2 MB. If you point Studio at a large one, raise the memory limit and NODE_OPTIONS together.

Apache Kafka, read-only

Kafka topics, consumer groups and brokers appear in the explorer, and messages are read with one JSON request (#1123, closes #1088). Each line below is one request:

{ "topic": "orders", "from": "latest", "limit": 50 }
{ "topic": "orders", "partition": 0, "from": { "offset": "120" } }
{ "topic": "orders", "from": { "timestamp": "2026-09-23T00:00:00Z" } }

Each message is one row with eight columns: partition, offset, timestamp, key, key_encoding, value, value_encoding and headers. A value is shown as JSON, text or base64, and a Confluent-framed value is labelled with its schema id rather than guessed at; gzip, snappy, lz4 and zstd batches are decompressed. A consumer group shows its lag per partition, the same numbers kafka-consumer-groups.sh --describe prints.

It is read-only by construction. Studio never joins a consumer group, commits an offset or creates a topic, and a broker-side diff taken before and after the live check found nothing changed. SASL PLAIN and SCRAM are refused without TLS. It needs Apache Kafka 3.1 or later and was verified against 4.3.1, as a single KRaft node, a three-node cluster and a TLS, SCRAM and authorizer broker. Redpanda v26.2.2 connects as kafka and answers every surface, four of them with less detail than Apache Kafka gives. In agent plan mode, the model drafts a read request over the real topic names and executes nothing.

Two limits come from the protocol and are stated rather than mitigated. A batch is decompressed whole before any bound applies. And a broker's advertised addresses decide which hosts Studio connects to next, with the connection's credentials, so use TLS with verify-full against a broker you do not control. Not in this version: producing, topic administration and Schema Registry decoding.

Two security fixes

A managed seed no longer sends its secrets to the browser. GET /api/connections/managed removed only password and connectionString from a managed seed, so the Elasticsearch API key pair and ssl.clientKey reached every signed-in user whose role matched the seed. The route now withholds every field the connection store classifies as secret, using the same classification the storage layer encrypts with, so a field classified secret later is withheld with no second list. A seed file that failed to parse also put the parser's message, which quotes the failing line, into the error body of every seed: route; it now names the fault and its position only (#1109).

The HTTP transports validate their endpoints and refuse redirects. The ClickHouse, Druid, Elasticsearch, OpenSearch, Trino, libSQL and Couchbase transports built their URLs from a string template, with host and port unchecked, and could follow a redirect. They now validate both before a URL exists and treat a 3xx as a connection error (#1086). Trino also held its nextUri links to nothing: a page naming another host received the connection's Authorization header. Every link is now held to the connection's own origin, thanks to @chiliec (#1090). One visible side effect: a URL now omits the scheme's default port.

Also in this release

  • A Redis connection walks one database's key space in the sidebar with SCAN, shown as a sample against the server's DBSIZE, and a walk can be narrowed to one prefix; each key opens by its type. It reads only: renaming, TTLs, saving and deleting keys are not part of it (#1095).
  • A MongoDB collection outside the connected database is read from its own database, where clicking it used to return 0 rows from the connected database's collection of the same name. Database Name is optional in field mode, and mongodb://host:27017 no longer takes the host for the database name (#1106, closes #843).
  • A table row in the object tree expands to its columns again, with one read for the row you open and none at first paint (#1069).
  • Catalog row counts are shown compactly, and a table's menu offers Generate Count Query, which opens an editable count statement without running it (#926, closes #702).
  • The results grid has a context menu to copy a cell or a row as JSON, and the desktop grid sizes each column to fit its header, type and controls (#1068 closes #695, #1113 closes #1101). The stats strip's column count opens a column visibility menu, and the filter count reads "10 of 50", because it filters only the rows loaded so far (#1066).
  • Agent runs can be paused and resumed from the rail. A run a crashed process left running is picked up and driven again by a sweep instead of staying open, a resumed run continues its statement and database-time spend instead of starting a fresh budget, and artifacts are capped per run (#1001, #1000, #999, #998).
  • The object browser reads a folder on Apache Doris and StarRocks, where it answered 500 because they refuse a bound parameter in LIMIT (#1067).
  • On RisingWave, expanding a table shows its columns again: the object reads build their rows with jsonb, since RisingWave has no json type (#1096, closes #1075).
  • A SQLite file the process cannot write, such as a :ro Docker mount, opens read-only, where it could not be opened at all (#1070).
  • A failed Oracle connect closes the pool it created, which used to retry forever and hold a CPU core (#1105, closes #1102).
  • SQLite, libSQL and DuckDB column defaults are read as values, the schema diagram shows an empty-string default, and SQLite and LibreDB report the database size as absent rather than 0 when it cannot be read (#1048, #1036, #1050).
  • The schema diff migration view has a copy button that stays in place while the SQL scrolls (#1076 closes #751, #1082).
  • The admin Overview's Recent Activity always links to the Audit tab, and its empty state no longer reads as a statement that nothing happened, since the proxy's denials are not in that preview (#1079).
  • The example compose files mount the postgres:18 data volume at /var/lib/postgresql, the path that image expects (#1041).
  • Every localized README carries a note, in its own language, that README.md wins where the two disagree, and a guard keeps it above the first section. The Chinese README gained its install, features and agent sections (#1064, #1052).

Upgrading

MCP stays off until LIBREDB_MCP_ENABLED and LIBREDB_MCP_TOKEN_LABEL are set, together with LIBREDB_MCP_URL, which npx derives from its own host and port, or until the chart's mcp block is enabled.

Four changes can stop a setup that worked before:

  • A ClickHouse, Druid, Elasticsearch, OpenSearch, Trino, libSQL or Couchbase connection whose address answers with a redirect now fails with a connection error naming the status and the target's origin, instead of following it; point the connection at the final address. A Trino coordinator behind a proxy that rewrites Host fails the same way, because its nextUri links then name another origin, and the error says so.
  • A Redis connection whose database is not a number, or names one the server does not have, is refused at connect, where it used to read database 0 without saying so.
  • Under npx, a HOSTNAME equal to the machine's own name no longer sets the bind address, so a shell's or a container's exported name binds 127.0.0.1 rather than an address loopback cannot reach. Inside a container, pass --host 0.0.0.0 (#1053 closes #813).
  • Under npx, a relative SEED_CONFIG_PATH, STORAGE_SQLITE_PATH or other path variable resolves against the directory you run it from, rather than against the package cache, where ./seed-connections.yaml used to be reported as not found (#1070).

An adopter embedding @libredb/studio should also check the library surface:

  • DatabaseType gains prometheus and kafka, ProviderCapabilities.queryLanguage gains "promql", queryDialect gains "kafka", and QueryTab["type"] gains both. An exhaustive switch over any of them needs the new arms.
  • SchemaExplorer offers Profile Table only for sql, or json with no queryDialect, Generate Code only for sql and for json other than the kafka dialect, and Generate Test Data only where the kind accepts row writes and the engine supports inline row edits. A host that passes no capabilities sees none of the three.
  • New optional members, which need no change to compile: ProviderCapabilities.keyScan with DatabaseProvider.scanKeysPage(), ObjectKindSpec.hasColumns, ProviderLabels.tableStatsCaption, DatabaseConnection.saslMechanism and QueryTab.databaseOverride.
  • A MongoDB statement may carry a database key; without one it still means the connected database, so saved queries keep their meaning.

Helm chart: 0.1.71

Tracks app release 0.17.0. Charts 0.1.68 to 0.1.70 were published against the 0.16.2 image and added the Prometheus and Kafka metadata and the mcp block, and 0.1.71 is the first chart whose image runs them. The mcp block renders nothing by default and refuses to render when enabled without url or tokenLabel. artifacthub.io/containsSecurityUpdates is true for the two fixes above.

helm repo add libredb https://libredb.org/libredb-studio/
helm upgrade --install libredb-studio libredb/libredb-studio --version 0.1.71

Contributors

Prometheus, Kafka, the rebuilt MCP server, the managed seed and endpoint fixes and the object tree columns are by @cevheri.

Full changelog: 0.16.2...0.17.0