Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- **AWS RDS IAM authentication**: Connections that authenticate with an IAM token instead of a stored password now work natively, for both PostgreSQL and MySQL. Tokens are minted with `aws rds generate-db-auth-token` (so SSO and role-chained profiles work as configured), cached for 13 minutes under their 15-minute lifetime, and re-minted per physical connection so long-lived pools keep working. TLS is forced for these connections, as RDS requires. Recognised from AWS Advanced JDBC Wrapper properties (`wrapperPlugins: "iam"`) or an `iam` auth model.
- **Database username derivation**: When an IAM connection records no username, it is derived from the caller's AWS identity — either the per-developer role name (`<profile>-<user>`) or the assumed SSO session name.
- **Custom driver support**: Drivers with an opaque id (a UUID, for instance) now route to the right native driver by falling back to the connection's `provider` and then the JDBC URL sub-protocol, including wrapped protocols such as `jdbc:aws-wrapper:postgresql://`. Previously any such driver fell through to the CLI fallback and failed.
- New `OMNISQL_AWS_CLI_PATH` and `OMNISQL_IAM_TOKEN_TIMEOUT` environment variables.

### Fixed
- **MySQL TLS options were read from the wrong place**: only top-level connection properties were checked, so the nested `properties` block written by the JSON workspace format was ignored. PostgreSQL already handled both.
- **MySQL `ssl-mode` semantics**: `REQUIRED` now encrypts without validating the certificate chain, per MySQL's documented behaviour, and only the `VERIFY_CA`/`VERIFY_IDENTITY` modes verify it. Previously `REQUIRED` implied full verification, which fails against managed engines whose CA is not in the system trust store. `REQUIRED`/`DISABLED` spellings are also recognised now.
- **Host, port and database are backfilled from the JDBC URL** when a connection config omits them.
- Unsupported-driver errors now name the raw driver id and provider alongside the resolved driver, instead of only the resolved one.

### Internal
- TLS resolution is now shared between the direct-query and pooled connection paths, which previously read different property locations and disagreed about what `require` meant.

## [2.0.1] - 2026-04-20

### Changed
Expand Down
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ Universal database MCP server — give AI assistants read/write access to your d

**Other databases**: Fall back to an external CLI configured via `OMNISQL_CLI_PATH`. Results vary by CLI.

**Custom drivers** wrapping any of the above are detected automatically — see [Custom and IAM-Authenticated Drivers](#custom-and-iam-authenticated-drivers).

## Features

- Reuses connections already configured in your local DB client workspace — no duplicate setup
- Native query execution for PostgreSQL, MySQL/MariaDB, SQLite, SQL Server
- AWS RDS IAM authentication, including custom drivers built on the AWS Advanced JDBC Wrapper
- Connection pooling with configurable pool size and timeouts
- Transaction support (BEGIN/COMMIT/ROLLBACK)
- Query execution plan analysis (EXPLAIN)
Expand Down Expand Up @@ -104,6 +107,8 @@ Add to Cursor Settings > MCP Servers:
| `OMNISQL_POOL_MAX` | Maximum connections per pool | `10` |
| `OMNISQL_POOL_IDLE_TIMEOUT` | Idle connection timeout (ms) | `30000` |
| `OMNISQL_POOL_ACQUIRE_TIMEOUT` | Connection acquire timeout (ms) | `10000` |
| `OMNISQL_AWS_CLI_PATH` | Path to the AWS CLI (used for RDS IAM authentication) | `aws` |
| `OMNISQL_IAM_TOKEN_TIMEOUT` | Timeout for minting an RDS IAM auth token (ms) | `20000` |

### Read-Only Mode

Expand Down Expand Up @@ -206,6 +211,43 @@ Supports both configuration formats written by DBeaver-compatible DB clients:

Credentials are automatically decrypted from the workspace `credentials-config.json`.

## Custom and IAM-Authenticated Drivers

### Custom drivers

Native routing normally keys off the driver id (`postgres-jdbc`, `mysql8`). Custom drivers often use an
opaque id instead — a UUID, say — which names no engine. Those connections are resolved by falling back
to the connection's `provider` (`postgresql`, `mysql`, …) and then to the JDBC URL's sub-protocol,
including wrapped ones such as `jdbc:aws-wrapper:postgresql://…`. A custom driver wrapping a supported
engine therefore works with no extra configuration.

If an engine still cannot be identified, the resulting error names both the driver id and the provider
so you can see what was missing.

### AWS RDS IAM authentication

Connections that authenticate with an RDS IAM token instead of a stored password are detected and
handled automatically. Both shapes are recognised:

- **AWS Advanced JDBC Wrapper** drivers, which record `wrapperPlugins: "iam"` alongside `awsProfile`
and `iamRegion`.
- The DB client's own **AWS IAM auth models**.

For these connections OmniSQL:

1. Mints a token with `aws rds generate-db-auth-token` (via the AWS CLI, so SSO and role-chained
profiles work as configured) and uses it as the password.
2. Caches each token for 13 minutes, under its 15-minute lifetime, and re-mints per physical
connection so long-lived pools keep working.
3. Forces TLS, which RDS requires for IAM tokens.
4. Resolves the database username from the connection when present. Where it is absent, the username is
derived from your AWS identity: either the per-developer role name (`<profile>-<user>`) or the
assumed SSO session name.

**Requirements**: the AWS CLI on `PATH` (or `OMNISQL_AWS_CLI_PATH`), a valid session for the
connection's profile (`aws sso login --profile <profile>`), and network reachability to the endpoint.
An expired SSO session produces an error naming the profile to re-authenticate.

## Development

```bash
Expand Down
41 changes: 41 additions & 0 deletions src/auth/connection-props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { DatabaseConnection } from '../types.js';

/**
* Read a connection property, looking through both levels the workspace uses.
*
* The JSON workspace format keeps engine/driver properties nested under a
* `properties` key inside the connection configuration, while the legacy XML
* format keeps everything flat. Nested values win, since that is where
* driver-specific configuration lives.
*
* Names are matched case-insensitively because casing varies by driver
* (Postgres uses `sslmode`, MySQL uses `sslMode`). Nested objects are skipped
* so a container never masks a scalar of the same name.
*/
export function readConnectionProp(
connection: DatabaseConnection,
...names: string[]
): string | undefined {
const props = (connection.properties ?? {}) as Record<string, unknown>;
const nested = (props['properties'] as Record<string, unknown> | undefined) ?? {};

for (const name of names) {
const wanted = name.toLowerCase();
for (const source of [nested, props]) {
const key = Object.keys(source).find((k) => k.toLowerCase() === wanted);
if (key === undefined) {
continue;
}
const value = source[key];
if (value === undefined || value === null || typeof value === 'object') {
continue;
}
const str = String(value);
if (str.length > 0) {
return str;
}
}
}

return undefined;
}
Loading