Skip to content
Merged
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **MCP server (#1133)** — Querya can serve shared PostgreSQL / MySQL / SQLite connections to AI clients (Claude Desktop, Cursor, VS Code, Gemini CLI, ...) over MCP: `list_connections`, `list_tables`, `describe_table`, `sample_rows`, `run_query`, `explain_query` and a `schema://` resource. Read-only in three layers (SQL guard, read-only database session, per-connection opt-in), credentials never sent to the model, `querya-mcp` stdio bridge, Preferences → MCP Server with copy-config buttons and an activity log — see docs/mcp-server.md.

### Fixed

- **ERD for SQLite (#1134)** — the diagram was always empty for SQLite connections (catalog columns collapsed in the driver's row maps).

### Planned

- Planned 0.5.0 — live Marketplace download and install
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Index of Querya Desktop documentation, grouped by audience.
- [Getting started](getting-started.md) — prerequisites, install, first run.
- [User guide](user-guide.md) — connections, preferences, driver manager.
- [Security / local data](security.md) — where metadata and secrets are stored.
- [MCP server](mcp-server.md) — let Claude Desktop, Cursor, VS Code and other AI clients read shared connections (read-only).
- [Testing extensions in CI](extension-testing.md) — `querya-ext-tester`, release binaries and the GitHub Action.

## For contributors
Expand Down
135 changes: 135 additions & 0 deletions docs/mcp-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# MCP server

Querya Desktop can act as an [MCP](https://modelcontextprotocol.io) server. Any
MCP client (Claude Desktop, Claude Code, Cursor, VS Code, Gemini CLI, Continue,
...) and whatever model it runs can then read the schema of the connections you
share and run **read-only** SQL through Querya. Querya has no chat of its own
and needs no AI provider keys: the client brings the model.

Supported: **PostgreSQL, MySQL, SQLite**. MongoDB, Redis and extension drivers
are not exposed yet.

## How it works

```
MCP client ──stdio──▶ querya-mcp ──127.0.0.1 + token──▶ Querya Desktop (running)
└─ connections, SSH tunnels, keyring
```

- The server runs **inside the running app**, so it uses your saved
connections, SSH tunnels and passwords from the OS keyring. The model only
sees each connection's id, name, type, environment tag and database name.
- The client starts `querya-mcp`, a small bridge that forwards the MCP session
to the app over a loopback socket. If Querya is not running (or the server is
off), every request gets the error *"Querya Desktop is not running or its MCP
server is off"*.

## Enable it

1. **Preferences → MCP Server → Enable MCP server.** The status line shows the
port and the number of connected clients.
2. Under **Shared connections**, switch on the connections the model may read.
Nothing is shared by default. Connections tagged *Production* show a `PROD`
badge: the model can read everything the connection's database user can.
3. Under **Connect a client**, press **Copy … config** for your client and paste
it where the client keeps MCP servers (see below).

## `querya-mcp`

| Build | Where it is |
|---|---|
| Linux zip / AppImage / `.deb` / `.rpm` | next to the app binary, e.g. `/opt/querya-desktop/querya-mcp` |
| Windows zip / setup | next to `querya_desktop.exe` in the install folder |
| macOS, Flatpak | not bundled yet: download `querya-mcp-v<version>-<platform>` from the GitHub release |

The copy buttons insert the bundled path automatically; otherwise replace
`/path/to/querya-mcp` with where you put the downloaded binary.

## Client configuration

**Claude Desktop** (`claude_desktop_config.json`), **Cursor** (`~/.cursor/mcp.json`)
and most stdio clients:

```json
{
"mcpServers": {
"querya": { "command": "/opt/querya-desktop/querya-mcp" }
}
}
```

**VS Code** (`.vscode/mcp.json` or user settings):

```json
{
"servers": {
"querya": { "type": "stdio", "command": "/opt/querya-desktop/querya-mcp" }
}
}
```

**Claude Code:** `claude mcp add querya -- /opt/querya-desktop/querya-mcp`

**Gemini CLI** (`~/.gemini/settings.json`): the same `mcpServers` block as above.

## Tools

| Tool | Arguments | Returns |
|---|---|---|
| `list_connections` | — | shared connections: `id`, `name`, `type`, `environment`, `database` |
| `list_tables` | `connection_id` | tables with their column count |
| `describe_table` | `connection_id`, `table` | columns, types, primary key, foreign key targets, indexes |
| `sample_rows` | `connection_id`, `table`, `rows` (1-100, default 20) | first rows of the table |
| `run_query` | `connection_id`, `sql` | `columns`, `rows`, `row_count`, `truncated` |
| `explain_query` | `connection_id`, `sql` (without `EXPLAIN`) | the execution plan, without running the query |

Resource: `schema://<connection_id>` — all tables with columns and keys, for
clients that load context up front.

SQL errors come back as tool errors with the database's message, so the model
can fix its query and retry.

## Limits

- **Read-only.** One statement per call; only `SELECT`, `WITH` without
data-modifying parts, `EXPLAIN` (without `ANALYZE`), `SHOW`, `DESCRIBE`,
`VALUES`, and schema `PRAGMA`s on SQLite. See [security.md](security.md#mcp-server).
- **Rows:** at most 1000 per `run_query` (`truncated: true` when cut), 100 per
`sample_rows`. **Cells** longer than 4 KB are cut with a marker.
- **Time:** 15 s per statement.
- **Tables:** base tables of the current schema / database; views are not
listed yet.

## Activity log

**Preferences → MCP Server → Recent calls** lists the last calls (time, client,
tool, connection, SQL, rows and duration, or the error). The app keeps the last
200 in its local database; **Clear** empties it.

## Troubleshooting

| Symptom | Fix |
|---|---|
| "Querya Desktop is not running or its MCP server is off" | Start Querya and switch on **Enable MCP server**. |
| `list_connections` is empty | Share at least one PostgreSQL / MySQL / SQLite connection. |
| "Connection N is not available" | The connection was unshared or deleted; call `list_connections` again. |
| "Invalid Querya MCP token" | The token was regenerated or the app restarted with a new one: restart the MCP client. |
| Client cannot find `querya-mcp` | Use the absolute path; on macOS / Flatpak download the release binary and `chmod +x` it. |
| Several Querya instances | Set `QUERYA_MCP_ENDPOINT` to a different file for each instance and for its `querya-mcp`. |

The endpoint file (port and token) is `$XDG_RUNTIME_DIR/querya/mcp-endpoint.json`
on Linux (fallback `~/.cache/querya`), `~/Library/Caches/Querya/` on macOS and
`%LOCALAPPDATA%\Querya\` on Windows.

## For contributors

- Query core: `lib/core/mcp/mcp_query_service.dart`, guard `mcp_sql_guard.dart`,
error redaction `mcp_redaction.dart`, read-only delegates `mcp_sql_delegates.dart`.
- Server and transport: `querya_mcp_server.dart` (`dart_mcp`),
`mcp_socket_host.dart`, `mcp_server_controller.dart`.
- Bridge: `packages/querya_mcp_bridge` — a dependency-free package so
`dart compile exe` produces one static binary (the app package has build
hooks that `dart compile exe` refuses).
- Settings UI: `lib/features/settings/preferences_mcp_section.dart`.
- Tests: `test/core/mcp/` (security suite: `mcp_security_test.dart`).
- Epic: [#1133](https://github.com/QueryaHub/Querya-Desktop/issues/1133).
5 changes: 5 additions & 0 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ Living document for planned work. Not a commitment order; adjust as priorities c

- **Done:** nested collection subtree in the connections panel (#1023), one-click return to the active Mongo collection / Redis key (#1024), Redis key folder tree by delimiter (#1025), Mongo document viewer in Table / Tree / JSON modes (#1026), Aggregation Pipeline Builder (#1027), visual Index Manager (#1028), interactive Redis CLI console (#1029) and MQL query / shell console (#1030). See the [user guide](user-guide.md#mongodb-and-redis).

## MCP server (AI clients)

- **Done (epic [#1133](https://github.com/QueryaHub/Querya-Desktop/issues/1133)):** read-only MCP server inside the app for PostgreSQL, MySQL and SQLite — tools `list_connections`, `list_tables`, `describe_table`, `sample_rows`, `run_query`, `explain_query` and a `schema://` resource; `querya-mcp` stdio bridge (bundled on Linux / Windows, release asset for all platforms); per-connection opt-in, activity log, three read-only layers — [mcp-server.md](mcp-server.md), [security.md](security.md#mcp-server).
- **Later:** writes through a `propose_write` tool confirmed in the Querya window; Streamable HTTP transport; MongoDB / Redis tools; extension drivers; views in `list_tables`; bundling `querya-mcp` in the macOS app and Flatpak.

## SSH and advanced networking

- **Done:** SSH tunnels through a bastion / jump host (password, private key or agent auth, optional second hop, host key fingerprint pinning, keep-alive) with a loopback-only forwarded port and a *Test SSH Connection* button. Security model: [security.md](security.md#ssh-tunnels-bastion-hosts). Setup guide: [user-guide.md](user-guide.md#connecting-through-a-bastion-host).
Expand Down
18 changes: 17 additions & 1 deletion docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ On upgrade from older databases, existing plaintext secrets in SQLite are **migr
## Threat model (practical)

- Anyone with **full access to your user session** can usually read app data and may extract secrets depending on OS protections.
- The app does **not** implement team features, audit logging, or network zero-trust controls.
- The app does **not** implement network zero-trust controls. Local audit trails exist for executed data changes and for MCP calls.
- **SSH tunnels / jump hosts** are built in (see below); the tunnel does not protect against an attacker who already controls your user session.

## SSH tunnels (bastion hosts)
Expand All @@ -26,6 +26,22 @@ On upgrade from older databases, existing plaintext secrets in SQLite are **migr
- **Host key verification:** with no pinned fingerprint the first key is accepted (trust on first use). Pin the server's SHA-256 fingerprint in the connection to reject a changed key; a mismatch fails the connection and reports the observed fingerprint.
- **Team sharing:** *Export Team Profile* writes connections without passwords or SSH secrets; importing re-scrubs the file.

## MCP server

Querya can serve the connections you share to AI clients over MCP ([mcp-server.md](mcp-server.md)). SQL written by a model is untrusted, so access is limited in three independent layers:

1. **Guard** (`McpSqlGuard`): one statement per call, only `SELECT` / `WITH` without data-modifying parts / `EXPLAIN` without `ANALYZE` / `SHOW` / `DESCRIBE` / `VALUES` and SQLite schema pragmas. Refused even inside a read: `SELECT ... INTO`, `INTO OUTFILE`, row locks, server functions with side effects (`pg_terminate_backend`, `pg_read_file`, `dblink`, `set_config`, `LOAD_FILE`, `load_extension`, ...) and MySQL executable comments (`/*! ... */`).
2. **Read-only database session:** PostgreSQL `default_transaction_read_only`, MySQL `SET SESSION TRANSACTION READ ONLY`, SQLite opened with `SQLITE_OPEN_READONLY`. A write that slipped past the guard is refused by the database. For defense in depth, also give MCP-shared connections a database user with read-only grants.
3. **Policy:** the server is off by default; each connection is shared individually (off by default); 15 s statement timeout, 1000-row and 4 KB-per-cell limits.

Credentials never reach the model: tools return only connection id, name, type, environment and database name, and error messages are scrubbed (`McpRedaction`) of the connection's secrets, URI user info, `password=`-style values and private keys.

**Transport:** the server listens on `127.0.0.1` only, on a random port. Clients connect through `querya-mcp`, which must present a random 256-bit token from an endpoint file readable only by your user (directory `0700`, file `0600`); a wrong token closes the connection. Any process running as your user can read that file, so the MCP server does not protect against an attacker who already controls your session.

**Prompt injection:** query results are data. Text stored in your tables can contain instructions aimed at the model; the server tells clients not to follow them, but the read-only limits above are what actually bound the damage.

**Audit:** every call is logged (Preferences → MCP Server → Recent calls).

## Tests

Automated tests use an **in-memory** secrets backend (see `test/flutter_test_config.dart`) so CI does not require a desktop keyring.
Expand Down
Loading