querybridge-mcp
Provides similar database management capabilities as MySQL, including querying, schema analysis, and administrative tools, for MariaDB databases.
Provides tools for querying, schema introspection, profiling, foreign key navigation, ERD generation, stored procedures, database diffing, and migration advisory for MySQL databases. Supports SSH tunnels, SSL/TLS, and multiple simultaneous connections.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@querybridge-mcpshow me all tables in the database"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
querybridge-mcp
A Model Context Protocol (MCP) server that connects Claude Code to MySQL databases. Supports SSH tunnels, SSL/TLS, and multiple simultaneous connections.
Contents
Tools — 39 tools across 9 categories
HTTP transport (remote MCP) — flags, security defaults, production deployment
Features
39 tools across schema introspection, querying, per-column profiling, FK navigation, ERD generation, stored programmability, operator admin, diagnostics (server snapshot, locks, slow queries), cross-database diffing, and advisory migration SQL.
2 MCP resources for browsable schema access.
4 MCP prompts for guided database workflows.
Two transports — local stdio (Claude Code) and remote Streamable HTTP with bearer auth (Cursor, n8n, hosted agents).
SSH tunnel support with password or private key authentication (and host-fingerprint pinning).
SSL/TLS support for direct encrypted connections.
Multi-database connections with independent configs.
Read-only by default with per-connection write control.
CLI for managing connections without editing JSON.
How it fits together
flowchart LR
C["MCP client<br/>(Claude Code, Cursor, …)"]
S["querybridge-mcp<br/>(stdio JSON-RPC)"]
P["mysql2 connection pool<br/>(per connection)"]
T["SSH tunnel<br/>(optional, ssh2)"]
DB[("MySQL / MariaDB")]
C -- stdin/stdout --> S
S --> P
P -- direct TCP --> DB
P -. via loopback .-> T
T -. forwards to remote MySQL .-> DB
classDef ext fill:#eef,stroke:#446,color:#000
classDef int fill:#efe,stroke:#464,color:#000
classDef opt fill:#fee,stroke:#644,color:#000,stroke-dasharray:4 3
class C ext
class S,P int
class T opt
class DB extThe MCP client speaks JSON-RPC over the server's stdin/stdout. Each configured connection gets its own mysql2 pool — when SSH is configured the pool talks to a local ephemeral port that the ssh2 tunnel forwards to the remote MySQL. Tools call the pool; the pool checks out a connection, runs the query, and the result flows back as a tool response (plus an MCP notifications/message echo for observability).
How it compares
There are several MCP-over-MySQL options. The honest pitch:
Capability |
| Typical alternative |
Read-only by default (whitelist + server-side | ✅ | Usually whitelist only, or off by default |
| ✅ | Rarely addressed |
SSH tunnel built in (with host-fingerprint pinning) | ✅ | Often requires an external |
Secrets indirection ( | ✅ | Usually plaintext in config |
Cancellation via MCP signal → | ✅ | Typically abandoned client-side |
Cross-database schema diff ( | ✅ | Rare |
Multi-arch Docker image with SBOM + Sigstore provenance | ✅ | Sometimes one platform only |
Integration tests against real MySQL (Testcontainers) | ✅ | Often unit tests only |
Tool annotations + | ✅ | Many still on the legacy |
If you need write access for migrations, raw replication setup, or admin operations beyond KILL QUERY, you may want a tool with looser defaults. querybridge-mcp leans hard on "give Claude a database without giving it the ability to destroy your data."
Quickstart
1. Install
Install globally from npm:
npm install -g querybridge-mcpOr run on demand without installing:
npx querybridge-mcp <command>
npx querybridge-mcp-server # starts the MCP serverOr pull the Docker image (no Node install needed):
docker pull ghcr.io/mahmoudhassanmustafa/querybridge-mcp:latestCheck the version on any of the above:
querybridge-mcp --version # or: querybridge-mcp-server --version2. Create a config file
querybridge-mcp init # creates an empty config
querybridge-mcp add production # adds a connection interactivelySee Configuration for the file shape, env-var alternatives, and SSH / SSL options.
3. Register with Claude Code
claude mcp add querybridge-mcp -e QUERYBRIDGE_MCP_CONFIG=/path/to/config.json -- querybridge-mcp-serverOr manually in ~/.claude.json:
{
"mcpServers": {
"querybridge-mcp": {
"type": "stdio",
"command": "querybridge-mcp-server",
"env": {
"QUERYBRIDGE_MCP_CONFIG": "/path/to/config.json"
}
}
}
}If querybridge-mcp-server isn't on your PATH (e.g. not installed globally), swap command for npx with "args": ["-y", "querybridge-mcp-server"].
3b. Register via Docker (optional)
For environments where Node/pnpm aren't installed, run the server from the published image. Mount your config read-only and let the container handle the rest:
claude mcp add querybridge-mcp -- \
docker run --rm -i \
-v /path/to/config.json:/config/config.json:ro \
-e QUERYBRIDGE_MCP_CONFIG=/config/config.json \
ghcr.io/mahmoudhassanmustafa/querybridge-mcp:latestOr manually in ~/.claude.json:
{
"mcpServers": {
"querybridge-mcp": {
"type": "stdio",
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"-v",
"/path/to/config.json:/config/config.json:ro",
"-e",
"QUERYBRIDGE_MCP_CONFIG=/config/config.json",
"ghcr.io/mahmoudhassanmustafa/querybridge-mcp:latest"
]
}
}
}Notes:
--rm -iis required —-iwires stdio (the MCP transport);--rmcleans up the container after the client disconnects.For SSH tunnels you also need to bind-mount the private key: add
-v ~/.ssh/id_ed25519:/keys/id_ed25519:roand reference/keys/id_ed25519in your config'sssh.privateKeyPath.Pin to a specific version (
:v0.11.0) for reproducibility;:latestfollows the current release.The image runs as a non-root
nodeuser. Mounts must be readable by UID 1000.Multi-arch:
linux/amd64+linux/arm64. Apple Silicon and Linux servers work out of the box.
For remote-client setups (Cursor, n8n, browser-based clients, hosted agents) see HTTP transport (remote MCP).
Configuration
Three ways to configure, in order of precedence:
1. Config file (recommended)
Set QUERYBRIDGE_MCP_CONFIG to a JSON file path, or use the CLI to build one.
{
"connections": [
{
"name": "local",
"host": "127.0.0.1",
"port": 3306,
"user": "root",
"password": "secret",
"database": "myapp",
"readonly": true,
"queryTimeout": 30000
}
]
}2. Inline JSON
Set QUERYBRIDGE_MCP_CONFIG_JSON to a JSON string:
QUERYBRIDGE_MCP_CONFIG_JSON='{"connections":[{"name":"dev","host":"localhost","user":"root","password":"secret","database":"myapp"}]}'3. Environment variables (single connection)
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=secret
MYSQL_DATABASE=myapp
MYSQL_READONLY=true
MYSQL_QUERY_TIMEOUT=30000
MYSQL_CONNECTION_NAME=defaultConnection options
Field | Type | Default | Description |
| string | required | Unique connection identifier |
| string | required | MySQL hostname or IP |
| number |
| MySQL port |
| string | required | MySQL username |
| string | MySQL password | |
| string | Default database/schema | |
| boolean |
| Block write operations |
| number |
| Query timeout in milliseconds |
| number |
| mysql2 connection-pool size |
| object | SSH tunnel configuration | |
| object or | SSL/TLS configuration |
Secrets indirection
password, ssh.password, and ssh.passphrase accept either a plain string OR an indirection object so credentials don't need to live in the config file:
{
"password": { "env": "PROD_DB_PASSWORD" },
"ssh": {
"host": "bastion.example.com",
"username": "deploy",
"passphrase": { "file": "~/.secrets/ssh-passphrase" }
}
}Form | Behavior |
| Plain string (back-compat, fine for dev) |
| Read from |
| Read file contents (tilde-expanded, trailing whitespace trimmed). |
Resolution happens once at config load; downstream code only sees the resolved string.
SSH tunnel
Tunnel MySQL traffic through an SSH bastion host. Supports password and private key authentication.
{
"name": "production",
"host": "rds-internal.example.com",
"port": 3306,
"user": "app",
"password": "secret",
"database": "prod",
"readonly": true,
"ssh": {
"host": "bastion.example.com",
"port": 22,
"username": "deploy",
"privateKeyPath": "~/.ssh/id_rsa",
"passphrase": "optional",
"hostFingerprint": "SHA256:AAAA...=="
}
}Field | Type | Default | Description |
| string | required | SSH server hostname |
| number |
| SSH port |
| string | required | SSH username |
| string | SSH password | |
| string | Path to private key (supports | |
| string | Private key passphrase | |
| string | Pinned SHA256 fingerprint of the SSH server's host key (format: |
SSL/TLS
For direct encrypted connections (without SSH):
{
"ssl": true
}Or with custom certificates:
{
"ssl": {
"ca": "~/.ssl/ca.pem",
"cert": "~/.ssl/client-cert.pem",
"key": "~/.ssl/client-key.pem",
"rejectUnauthorized": true
}
}CLI
The CLI manages your config.json without editing it by hand. After npm install -g querybridge-mcp, the querybridge-mcp command is on your PATH.
Commands
Command | Description |
| List all configured connections |
| Add a new connection (interactive) |
| Remove a connection |
| Test one or all connections |
| Create an empty config file |
Examples
# Create config and add first connection interactively
querybridge-mcp init
querybridge-mcp add production
# Test all connections
querybridge-mcp test
# Test a specific connection
querybridge-mcp test production
# Remove a connection
querybridge-mcp remove stagingSet QUERYBRIDGE_MCP_CONFIG in your shell profile so the CLI always finds your config:
export QUERYBRIDGE_MCP_CONFIG=~/.config/querybridge-mcp/config.jsonTools
Every tool takes connection: string as its first argument; schema-targeted tools also take an optional database. Tools that operate on a single table accept either table: string (returns a flat shape) or tables: string[] (returns { results: [...] } — convenient for batch lookups). Where supported, omitting both runs against every table in the database.
Connection management
Tool | Description |
| List all connections with status, host, SSH/SSL indicators |
| List all databases accessible on a connection |
| Switch the active database/schema for a connection |
Schema introspection
Tool | Description |
| List tables with row counts and engine info |
| List views with definer, security type, updatability |
| Show columns, indexes, and CREATE TABLE statement. Single-table via |
| Show columns and CREATE VIEW DDL of a view |
| Get clean CREATE TABLE DDL. Single-table via |
| Get clean CREATE VIEW DDL (raw, not truncated) |
| Show FK relationships with cascade rules. |
| Show indexes with duplicate detection. |
| Find columns by name pattern across all tables |
Query execution
Tool | Description |
| Run SQL with parameterized values. Writes blocked on read-only connections |
| Run EXPLAIN in TRADITIONAL, JSON, or TREE format |
| Stream a large SELECT to a file on disk. |
Data inspection
Tool | Description |
| Row counts, data/index sizes, timestamps. Three modes: |
| Preview rows from a table (default: 5 rows) |
| Per-column profile — null %, distinct count, min/max/avg, optional top-N most common values. One combined-aggregation query per call; type-aware metric selection (no AVG on text, no MIN/MAX on BLOB) |
| Walk FK relationships outward from a seed row ( |
Stored routines and programmability
Tool | Description |
| List stored procedures and functions |
| Get full DDL for a procedure or function |
| List triggers, optionally filtered by table |
| Get full trigger definition |
| List scheduled events with status and timing |
| Get full event definition |
Visualization
Tool | Description |
| Generate a Mermaid ER diagram with tables, columns, PKs, FKs, and relationships |
Operator / admin
Tool | Description |
| Show running connections + their current queries (filter by minimum duration) |
| KILL QUERY (or KILL CONNECTION) by process ID. Gated: requires |
| Detect secondary indexes with zero reads in |
| Show character set and collation at database, table, and column levels |
Diagnostics
Tool | Description |
| Bird's-eye snapshot — version, hostname, uptime, thread counts (running/connected/max), key charset & collation, SQL mode, time zone, read-only flags |
|
|
|
|
| Active InnoDB lock-wait pairs from |
| Raw |
| Top query digests from |
Cross-database diffing
Tool | Description |
| Diff a checked-in |
| Diff two databases (potentially across connections). Reports drift across 9 aspects: tables, table attributes (engine/charset/partitioning), columns (incl. comments, generated cols), indexes (incl. MySQL 8 invisible indexes, functional indexes, prefix lengths), foreign keys, views, routines, triggers, events. SQL bodies are whitespace-normalized; int display widths are normalized for cross-version (5.7 ↔ 8.0+) sanity. Restrict with |
| Advisory-only ALTER/CREATE/DROP SQL generator. Diffs source vs target, emits SQL without executing — phased for safe application (drop FKs → drop indexes → drop columns → modify → add columns → add indexes → add FKs → drop tables → create tables). DROP and MODIFY are opt-in via flags. Every destructive line is preceded by a |
Resources & Prompts
Resources
MCP resources let Claude browse schema information without explicit tool calls.
URI Pattern | Description |
| Table schema with columns and DDL |
| Database overview with all tables and row counts |
Prompts
Pre-built prompt templates that guide Claude through multi-step database workflows. They appear in the MCP prompt list — select one and provide the required arguments (connection name, database, etc.) to start a guided workflow.
Prompt | Description |
| Discover tables, schemas, FKs, routines, triggers, events, and generate an ERD |
| Analyze a query with EXPLAIN, check indexes, suggest improvements |
| Search columns by pattern, sample tables, build a query |
| Check for missing PKs, redundant indexes, empty tables, catalog routines and triggers |
HTTP transport (remote MCP)
querybridge-mcp-server ships two transports: stdio (default — what Claude Code uses) and Streamable HTTP (for Cursor, n8n, hosted agents, browser-based clients). The HTTP transport implements the MCP Streamable HTTP spec with stateful sessions so log forwarding and progress notifications work end-to-end.
Quick start (local)
export QUERYBRIDGE_MCP_HTTP_TOKEN=$(openssl rand -base64 32)
querybridge-mcp-server --transport=http --port=8080Client config:
{
"mcpServers": {
"querybridge-mcp": {
"type": "streamable-http",
"url": "http://127.0.0.1:8080/mcp",
"headers": { "Authorization": "Bearer <YOUR_TOKEN_HERE>" }
}
}
}Flags
Flag | Default | Notes |
|
| |
|
| |
|
| Loopback by default. Set |
|
| The HTTP path the transport serves on. |
| (none) | Comma-separated Host-header allowlist. Required when binding to a non-loopback address. Defends against DNS-rebinding. |
| off | Opt out of bearer auth. Logged as a warning on every startup. Useful for |
| (none) | Permissive CORS for a single origin. Skip unless a browser-based MCP client needs it. |
Environment variables
QUERYBRIDGE_MCP_HTTP_TOKEN— bearer token clients must send. Required unless--no-authis set; the server refuses to start otherwise.
Security defaults
Read these once before exposing the server:
Bearer required by default. The two-key opt-out (token absent AND
--no-auth) prevents accidentally disabling auth via env-var typo.Loopback-only by default. External binding (
--host=0.0.0.0) requires--allowed-hostsso a DNS-rebinding attack from a browser can't reach the server.No CORS by default. Same-origin RPC is the norm for MCP; cross-origin is opt-in.
All security guarantees of the stdio transport still apply — read-only enforcement, LOAD INFILE block, KILL QUERY cancellation, error sanitization. The HTTP layer is just a different envelope.
Production deployment
The defaults — loopback + bearer token — are designed for the "operator and agent on the same host" trust boundary. For anything beyond that (LAN, public internet, multi-tenant access), the bearer-only model is necessary but not sufficient. Bearer tokens travel on every request, never expire, and grant access to every tool on every configured connection. The standard recipe to harden this is to front the server with a TLS-terminating auth proxy and keep querybridge itself on loopback.
What the bearer token alone does NOT protect against, and how to address each:
Gap | Mitigation |
Wire interception. The server speaks plaintext HTTP; an observer on any non-loopback network captures the token. | Terminate TLS at a reverse proxy (Caddy / nginx / Cloudflare) and run querybridge on loopback. |
No expiry / rotation. A token leaked once is valid until the next restart. | Rotate by restarting the server with a new env var. For real rotation discipline, put an OAuth2/OIDC proxy in front that issues short-lived service tokens. |
No scope. One token → every tool, every connection. A leaked token can | Set |
No replay protection. A captured request + header is replayable. | TLS prevents the capture in the first place. |
No rate limiting. | Reverse-proxy-level rate limits ( |
No per-user audit. Every call logs as the same anonymous bearer. | OIDC proxy that forwards a verified |
Minimal TLS-terminating proxy (Caddy)
mcp.example.com {
reverse_proxy 127.0.0.1:8080
}Start querybridge alongside it:
QUERYBRIDGE_MCP_HTTP_TOKEN=$(openssl rand -base64 32) \
querybridge-mcp-server --transport=http --host=127.0.0.1 --port=8080Caddy obtains and renews the certificate automatically. Don't pass --allowed-hosts in this layout — Caddy already filters incoming Host, and querybridge sees the proxy connection on loopback. Clients connect to https://mcp.example.com/mcp with Authorization: Bearer <token>.
Stronger identity (OAuth2 / OIDC)
When you need per-user identity, SSO revocation, or short-lived tokens, put oauth2-proxy, Pomerium, or Cloudflare Access in front of the reverse proxy. The auth proxy verifies the user against your IdP, then injects a static service token toward querybridge:
Internet → Cloudflare Access → Caddy → querybridge (loopback, --no-auth)
(verifies user) ↑ trusts the upstream layerPass --no-auth to querybridge in this layout — the upstream proxy is the security boundary, and a double-token check just confuses operations. Keep --host=127.0.0.1 so the only path to querybridge is through the proxy.
Defense-in-depth checklist
TLS terminated by something in front (querybridge itself does not do TLS).
querybridge bound to
127.0.0.1— never0.0.0.0on a public interface.Every connection that doesn't strictly need writes has
"readonly": true.Strong token (
openssl rand -base64 32or stronger), or--no-authonly when the upstream proxy is the trust boundary.Reverse-proxy rate limits set per your traffic shape.
Network ACLs restrict who can reach the proxy (VPN / VPC / Tailscale / Cloudflare Access — pick one).
Logs from querybridge (stderr) shipped somewhere you can audit. Every tool invocation is logged with tool name, connection, and elapsed ms.
querybridge is intentionally not an identity provider — building JWT verification, key rotation, and per-user scoping into the server would duplicate purpose-built infrastructure. The pattern the industry has settled on for tools like this is "delegate auth to a sidecar," which is why the recommended path is a reverse proxy rather than additional flags.
Container example
docker run --rm -p 8080:8080 \
-e QUERYBRIDGE_MCP_CONFIG_JSON='{"connections":[...]}' \
-e QUERYBRIDGE_MCP_HTTP_TOKEN="$YOUR_TOKEN" \
ghcr.io/mahmoudhassanmustafa/querybridge-mcp:latest \
--transport=http --host=0.0.0.0 --allowed-hosts=localhostSafety
Read-only by default. Write queries (INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, TRUNCATE) are blocked unless
"readonly": falseis set on the connection.Server-side read-only enforcement. Read-only pools also run
SET SESSION transaction_read_only = 1, sql_safe_updates = 1on every connection, so even a parser bypass is rejected by MySQL itself.LOAD DATA LOCAL INFILE disabled. The
LOCAL_FILEScapability is dropped from the client handshake and theinfileStreamFactoryis hard-wired to throw — a malicious MySQL server cannot read files from the MCP host.Parameterized queries. The
execute_querytool uses prepared statements with?placeholders to prevent SQL injection.Result limits. Unbounded SELECT queries are auto-limited to 1000 rows. Table output is additionally capped at 256KB with a truncation note; individual cell values are truncated at 120 characters.
Bounded file output.
streaming_querywrites to operator-supplied paths but refuses/proc,/dev,/sys,/boot, refuses to clobber existing files unlessoverwrite: true, and enforces both a 1 GiB byte cap and a 1M-row cap by default (10 GiB / 100M hard ceilings). Cap-stop triggersKILL QUERYon the worker rather than just abandoning the stream.Scoped scratch privileges.
compare_schema_filerequires a writable scratch connection but always creates temp databases under the_qbmcp_check_*prefix. Scope the scratch MySQL user to that namespace rather than*.*so a hostile agent with that user can't reach beyond the scratch space — seeSECURITY.mdfor the recommendedGRANTstatement.Cancellable queries. If the MCP client cancels a request,
execute_queryandexplain_queryissueKILL QUERYon a sibling connection so the in-flight statement is stopped at the server, not just abandoned by the client.Tool annotations. Every tool advertises MCP
readOnlyHint/destructiveHint/idempotentHintso clients (and humans) can gate confirmation prompts appropriately.Structured results. Tools return both human-readable text AND
structuredContentJSON, so clients that support the modern MCP spec can render rich tables instead of monospace ASCII.LLM-friendly errors. Tool failures carry a stable
codeand a list ofsuggestions({ tool, reason, args? }) instructuredContent, plus a rendered "Try one of these tools next:" bullet list in the text body. When the failing call already knew the relevantconnection/database/table, those values are pre-filled in the suggested invocation — the agent doesn't re-derive context from the error message.Audit logging. Every tool invocation is logged to stderr with the connection, elapsed ms, and pre-condition rejections — so operators can see exactly what the agent did. Logs are also forwarded to the MCP client via
notifications/message(per spec) so connected clients see them inline.Config file in .gitignore. The
config.jsonfile containing credentials is excluded from version control.
For exposing the HTTP transport beyond loopback, see Production deployment.
Project structure
src/
server/ Transport — stdio + Streamable HTTP + admin CLI
tools/ One file (or directory) per tool family
db/ Repository layer (introspection, runner, resolve, cancel, retry)
sql/ SQL primitives (identifier escape, read-only whitelist)
types/db.ts Shared domain types
connection.ts mysql2 pool + SSH tunnel lifecycle
schema.ts Zod config schemas
errors.ts QueryBridgeError + typed subclasses
limits.ts Centralized constants (timeouts, budgets, chunks)
log.ts Logger + AsyncLocalStorage trace context
resources.ts / prompts.ts MCP resource & prompt templates
config.json Your connections (gitignored)
config.example.json Sanitized templateImports flow one direction: Transport → Tools → Infrastructure, enforced by .dependency-cruiser.cjs. See CONVENTIONS.md §1 for the full rules and CLAUDE.md for a 60-second agent briefing.
Contributing
See CONTRIBUTING.md. PRs add a changeset describing the user-visible effect; releases are automated.
License
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/MahmoudHassanMustafa/querybridge-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server