Skip to content

feat(connections): forward raw connection URIs to plugin drivers#495

Open
Robbyfuu wants to merge 2 commits into
TabularisDB:mainfrom
Robbyfuu:feat/plugin-connection-uri-srv
Open

feat(connections): forward raw connection URIs to plugin drivers#495
Robbyfuu wants to merge 2 commits into
TabularisDB:mainfrom
Robbyfuu:feat/plugin-connection-uri-srv

Conversation

@Robbyfuu

Copy link
Copy Markdown

Closes #494.

Opened as a draft for one reason, stated up front: the Rust tests have never executed in my environment. src-tauri/tests/test_ca.pem is tracked in git but absent from my worktree, and pool_manager_tests.rs:466 pulls it in with include_bytes!, so cargo test --lib fails to compile before reaching anything. I wrote 13 Rust tests that compile cleanly under cargo check --tests but have never run. I am opening this as a draft specifically so CI executes them. Everything else below was verified locally.

The problem

Pasting a real MongoDB Atlas connection string fails. Three separate gaps, all on the connection-string import path:

The protocol is rejected. buildProtocolRegistry derives protocols from only two sources per driver — normalizeProtocol(driver.id) and the scheme of connection_string_example. PROTOCOL_ALIAS_GROUPS is a hardcoded const. A plugin has no way to register a second scheme, so:

Unsupported database driver: mongodb+srv. Supported: mariadb, mongodb,
mongodb-atlas, mysql, postgres, postgresql, redis-rust, sqlite, sqlite3

The URI is destroyed even when accepted. parseConnectionString decomposes the URL and toConnectionParams forwards only host/port/username/password/database. The scheme and the entire query string are discarded. For mongodb+srv:// that cannot be recovered: the scheme mandates a DNS seedlist lookup, and Atlas cluster hostnames publish SRV records but no A record, so the decomposed host:27017 fails at resolution:

Server selection timeout: No available servers.
Servers: [ { Address: cluster0.xxxxx.mongodb.net:27017, Type: Unknown,
  Error: I/O error: failed to lookup address information } ]

Query parameters (tls, retryWrites, w, authSource, replicaSet, appName) are lost with it.

A database path is mandatory. Real Atlas URIs routinely omit it — mongodb+srv://user:pass@cluster0.xxxxx.mongodb.net/?tls=true&retryWrites=true&w=majority&appName=Cluster0 — but the parser rejects that with "Database name is required in connection string".

Approach

Two new capabilities: connection_uri (bool) to declare that a driver takes the string verbatim, and connection_uri_schemes (string[]) to register additional schemes. Both accept camelCase aliases, matching the existing convention for connection_string / connectionString.

The URI travels on ConnectionParams rather than through plugin settings. That is the load-bearing decision, so it is worth stating why: plugin settings are pushed once via the initialize RPC in RpcDriver::new, which makes them global per plugin — an Atlas cluster and a local mongodb://localhost could not coexist. ConnectionParams is passed to every driver method, so putting the URI there makes it per-connection. It also means plugins/driver.rs needed no production change: serde already serializes the whole struct into each request.

Both new fields are Option + #[serde(default)] + skip_serializing_if, so existing stored connections deserialize unchanged and re-serializing does not introduce the keys. There is a test asserting the serialized output of legacy params contains no connection_uri substring. Both new capability fields are #[serde(default)], so existing plugin manifests keep parsing.

Non-passthrough parsing is untouched: passthrough is a property of the resolved driver and branches before the existing local/remote split. The database requirement still applies to every driver that does not opt in.

Note that fixing only the protocol rejection would have been strictly worse than the status quo — the URI would parse, get decomposed, and persist a connection that fails at DNS instead of showing a clear validation error. Both gaps land in the same function for that reason.

Security

The URI embeds credentials in cleartext inside the string, so it is treated as a secret throughout:

  • Stored in the OS keychain under {id}:connection_uri, never in connections.json. params_for_persistence strips it on every write path and leaves only a connection_uri_in_keychain marker.
  • Saving or updating requires the keychain and fails loudly rather than silently downgrading to plaintext. The frontend mirrors the check, but the backend enforces it independently.
  • Test Connection may use an ephemeral in-memory URI with no keychain requirement.
  • If connections.json persistence fails after the secret was written, both the keychain entry and the in-memory cache roll back. If the rollback itself fails, both errors are reported and the cache entry is dropped rather than left disagreeing with the keychain.
  • A marker pointing at a missing keychain entry fails closed with a clear error rather than silently connecting with the decomposed fields.
  • Declared schemes cannot displace a protocol another driver already owns. Drivers reach the parser sorted by id, so without that guard a plugin could claim postgres and — because passthrough hands the string over verbatim — capture a URI the user pasted intending the built-in driver. This mirrors the guard the alias-expansion pass already had.

Import, export and the MCP surface were each brought onto the same terms: apply_export_payload validates and routes an inline URI to the keychain instead of persisting it, export_connections_payload resolves it from the keychain when include_secrets is set so a restored backup is not left with a marker and no secret, and resolve_db_params restores it so an MCP query reaches the driver with the same params the app would send.

Verification

Run locally against this branch:

  • pnpm typecheck — clean
  • pnpm lint — clean
  • pnpm test --run tests/utils/connectionStringParser.test.ts — 47 passed
  • cd src-tauri && cargo check — compiles, 2 warnings, both pre-existing and unrelated (ssh_tunnel.rs:91, plugins/driver.rs:34)

The full JS suite reports 33 failures across SettingsProvider, ThemeProvider and useSidebarResize, all on localStorage.clear(). These are pre-existing on main — I confirmed by stashing this entire change and reproducing the identical 3 files / 33 tests on a clean tree. None of those files are touched here.

The regression test guarding scheme shadowing was verified to be meaningful rather than decorative: removing the guard makes it fail, restoring it makes it pass.

Known gaps

  • The Rust tests have not run. 13 tests across commands.rs, models_tests.rs and plugins/driver.rs compile but are unexecuted, for the test_ca.pem reason above. This is the reason for the draft status.
  • No plugin exercises this end to end. Nothing in this repo declares connection_uri: true, so the passthrough path has not been driven against a live cluster. The SRV resolution that motivates the change is the plugin's responsibility.
  • The i18n strings need native review. One new key across 10 locales. Each quotes that locale's existing saveKeychain label verbatim and uses its established keychain terminology, but they are not native-speaker reviewed.
  • The modal changes are covered by typecheck and lint only — no component test.

Out of scope deliberately: no UI affordance marking a connection as URI-backed, no reveal-stored-URI flow, and no plugin SDK changes under packages/ — external plugin authors will need the capability documented there before they can adopt it.

Happy to adjust the capability naming or split this if you would rather see the parser and the secret-handling land separately.

Plugin drivers could not receive a full connection URI, which made
`mongodb+srv://` unusable. Three gaps combined:

- `buildProtocolRegistry` derived protocols only from a driver's id and
  the scheme of `connection_string_example`, so a plugin could not
  register `mongodb+srv` at all.
- `parseConnectionString` decomposed the URL into host/port/user/pass/db
  and dropped the scheme and every query parameter. For `mongodb+srv`
  that is unrecoverable: the scheme mandates a DNS seedlist lookup, and
  Atlas hostnames publish SRV records but no A record.
- A database path was mandatory, while real Atlas URIs routinely omit it.

Drivers can now declare `connection_uri` to receive the string verbatim
and `connection_uri_schemes` to register additional schemes. The URI
travels on `ConnectionParams`, which reaches every driver call, rather
than through plugin settings, which are global per plugin and are only
sent once at `initialize`.

The URI embeds credentials, so it is treated as a secret: stored in the
OS keychain, never in connections.json. Saving requires the keychain and
fails loudly instead of downgrading to plaintext; Test Connection may use
an ephemeral URI; a failed persist rolls back both the secret and the
cache. Import, export and the MCP surface handle it on the same terms.

Declared schemes cannot displace a protocol another driver already owns,
so a plugin cannot capture connection strings meant for a built-in.

Refs TabularisDB#494
Reconnecting to a saved connection sends the on-disk params to
test_connection and list_databases, and the on-disk params never carry
the URI — it lives in the OS keychain. The password already had a
restore path in both commands; the URI did not, so a saved passthrough
connection enumerated databases at creation time and then failed at
reconnect with the decomposed host:port. Found driving a real Atlas
cluster end to end.
@Robbyfuu

Copy link
Copy Markdown
Author

Update: the "no plugin exercises this end to end" gap from the PR description is now closed — I drove the full loop against a live Atlas cluster with a real plugin, and it surfaced one bug in this PR, fixed in the new commit.

Setup. A MongoDB plugin declaring the new capabilities (connection_uri: true, connection_uri_schemes: ["mongodb", "mongodb+srv"]) and consuming connection_uri verbatim in its connect path: https://github.com/Robbyfuu/tabularis-mongodb-plugin/tree/feat/mongodb-atlas-full-uri. Host built from this branch, production Tauri bundle, macOS arm64.

What worked immediately:

  • Pasting a real mongodb+srv://.../?tls=true&retryWrites=true&w=majority&appName=... URI (no database path) parses, shows the derived host/username for display, and keeps the URI verbatim — the old Unsupported database driver: mongodb+srv error is gone.
  • Save refused while "Save passwords in Keychain" was off, with the new message; with it on, the connection saved and databases enumerated during creation.
  • On-disk state after save is exactly the contract: connections.json carries connection_uri_in_keychain: true and no URI; the secret exists in the OS keychain under {id}:connection_uri; SRV resolution works (the driver talks to the individual ac-...-shard-00-0N replica-set members, which only SRV can discover).

The bug this found (fixed in 5e13eee1): reconnecting to a saved connection failed with the decomposed host:27017 DNS error even though creation-time enumeration had worked. The frontend's reconnect flow fetches get_connections — on-disk params, URI stripped by design — and passes those params to test_connection / list_databases. Both commands already restore the password from the keychain when the request params lack one and a connection_id is present; the URI needed the identical treatment and had no restore path. Same asymmetry class as the MCP gap already fixed in this branch — the third instance, and the reviews missed it because none of them enumerated frontend-initiated flows that pass stale params by value. Worth keeping in mind for any future field that lives in the keychain rather than connections.json.

After the fix: reconnect works, both databases enumerate, collections list, schema inference and data browsing all return real documents from the cluster.

(Plugin-side, for anyone wiring their own driver to this: the host sends database as an array for multi-database drivers, the selected database arrives in the request-level schema field, and browsing queries quote identifiers — SELECT * FROM "collection". All three tripped the plugin before it handled them.)

@Robbyfuu
Robbyfuu marked this pull request as ready for review July 18, 2026 20:04
Comment thread src-tauri/src/mcp/mod.rs
// connections.json never holds the URI, so a passthrough connection
// that works in the app would reach the driver without its endpoint
// and credentials here unless it is restored the same way.
if conn.params.connection_uri_in_keychain.unwrap_or(false) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: URI restoration nested inside save_in_keychain conditional

A connection can legitimately have save_in_keychain = false (password stored inline or not required) while connection_uri_in_keychain = true (URI stored in the OS keychain). Because this restoration block is inside the if conn.params.save_in_keychain.unwrap_or(false) conditional, the URI is never restored for such connections in MCP requests. The driver receives the decomposed host/port instead of the full URI, causing a runtime connection failure.

Move this block outside the save_in_keychain conditional so it runs whenever connection_uri_in_keychain is true, matching the non-MCP restore path in commands.rs.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
src-tauri/src/mcp/mod.rs 271 URI restoration nested inside save_in_keychain block, missing connections with URI-only keychain storage
Files Reviewed (27 files)
  • src-tauri/src/commands.rs - 0 issues
  • src-tauri/src/credential_cache.rs - 0 issues
  • src-tauri/src/drivers/driver_trait.rs - 0 issues
  • src-tauri/src/drivers/mysql/mod.rs - 0 issues
  • src-tauri/src/drivers/postgres/mod.rs - 0 issues
  • src-tauri/src/drivers/sqlite/mod.rs - 0 issues
  • src-tauri/src/keychain_utils.rs - 0 issues
  • src-tauri/src/mcp/mod.rs - 1 issue
  • src-tauri/src/models.rs - 0 issues
  • src-tauri/src/models_tests.rs - 0 issues
  • src-tauri/src/plugins/driver.rs - 0 issues
  • src/components/modals/NewConnectionModal.tsx - 0 issues
  • src/i18n/locales/de.json - 0 issues
  • src/i18n/locales/en.json - 0 issues
  • src/i18n/locales/es.json - 0 issues
  • src/i18n/locales/fr.json - 0 issues
  • src/i18n/locales/it.json - 0 issues
  • src/i18n/locales/ja.json - 0 issues
  • src/i18n/locales/ko.json - 0 issues
  • src/i18n/locales/ru.json - 0 issues
  • src/i18n/locales/tl.json - 0 issues
  • src/i18n/locales/zh.json - 0 issues
  • src/types/plugins.ts - 0 issues
  • src/utils/connectionStringParser.ts - 0 issues
  • src/utils/connections.ts - 0 issues
  • src/utils/credentials.ts - 0 issues
  • tests/utils/connectionStringParser.test.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by kimi-k2.6 · Input: 154.3K · Output: 48.7K · Cached: 3.1M

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.

[Bug]: external plugin drivers cannot receive a full connection URI (mongodb+srv rejected, then decomposed)

1 participant