Skip to main content
Glama
liorcodev

mysql-mcp-server

by liorcodev
README.md
# MySQL Model Context Protocol (MCP) Server

A high-performance, lightweight **Model Context Protocol (MCP) Server** built with **Bun** and **TypeScript**. It provides a secure `mysql_query` tool allowing AI assistants (such as Claude, Cursor, VS Code Copilot, and custom MCP clients) to execute SQL queries against a MySQL database.

---

## Features

- ⚡ **Built on Bun**: Fast startup and lightweight footprint.
- 🔌 **Streamable HTTP Transport**: Implements standard MCP HTTP/SSE transport (`/mcp`).
- 🛡️ **Safe by Default**: Read-only query execution by default. Write (`INSERT`, `UPDATE`, `DELETE`) and DDL (`CREATE`, `ALTER`, `DROP`) operations require explicit configuration.
- 🔒 **File-Based Secrets (`_FILE`)**: Supports reading credentials from mounted files (`MYSQL_USER_FILE`, `MYSQL_PASSWORD_FILE`).
- ⚡ **Connection Pooling**: Uses `mysql2` with automatic connection management.

---

## Quick Start

### Basic `docker run`

Run the container by passing your MySQL database credentials:

```bash
docker run -d \
  --name mysql-mcp-server \
  -p 127.0.0.1:3000:3000 \
  -e MYSQL_HOST=your-mysql-host.example.com \
  -e MYSQL_PORT=3306 \
  -e MYSQL_DATABASE=my_database \
  -e MYSQL_USER=my_user \
  -e MYSQL_PASSWORD=my_password \
  liorcodev/mysql-mcp-server:latest
```

The MCP HTTP endpoint will be available at:
`http://localhost:3000/mcp`

---

## Connecting to MySQL on `localhost` / Host Machine

If your MySQL database runs directly on your host machine (outside Docker), standard `localhost` or `127.0.0.1` refers to the container itself. Use `host.docker.internal` instead:

```bash
docker run -d \
  --name mysql-mcp-server \
  -p 127.0.0.1:3000:3000 \
  -e MYSQL_HOST=host.docker.internal \
  -e MYSQL_DATABASE=my_database \
  -e MYSQL_USER=my_user \
  -e MYSQL_PASSWORD=my_password \
  liorcodev/mysql-mcp-server:latest
```

---

## Environment Variables

| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `MYSQL_HOST` | **Yes** | — | MySQL server hostname or IP address |
| `MYSQL_PORT` | No | `3306` | MySQL server port |
| `MYSQL_DATABASE` | **Yes** | — | Target database name |
| `MYSQL_USER` | **Yes**\* | — | MySQL username |
| `MYSQL_USER_FILE` | **Yes**\* | — | Path to file containing MySQL username (overrides `MYSQL_USER`) |
| `MYSQL_PASSWORD` | No | — | MySQL password |
| `MYSQL_PASSWORD_FILE` | No | — | Path to file containing MySQL password (overrides `MYSQL_PASSWORD`) |
| `MYSQL_CONNECTION_LIMIT` | No | `10` | Maximum connections in the pool |
| `MYSQL_ALLOW_WRITE_OPERATIONS` | No | `false` | Set to `true` to allow write queries (`INSERT`, `UPDATE`, `DELETE`, `REPLACE`, `CALL`, `LOAD`) |
| `MYSQL_ALLOW_DDL_OPERATIONS` | No | `false` | Set to `true` to allow DDL statements (`CREATE`, `ALTER`, `DROP`, `TRUNCATE`, etc.) |
| `HOST` | No | `0.0.0.0` | Container IP interface to bind to (leave as `0.0.0.0` so Docker port mapping works) |
| `PORT` | No | `3000` | Internal container port for HTTP server binding |

*\* Either `MYSQL_USER` or `MYSQL_USER_FILE` must be provided.*

---

## Reading Credentials from Files (`_FILE` Environment Variables)

To avoid passing raw credentials as plain-text environment variables, store your credentials in files, mount them into the container using volume mounts (`-v`), and reference their paths using `MYSQL_USER_FILE` and `MYSQL_PASSWORD_FILE`:

```bash
docker run -d \
  --name mysql-mcp-server \
  -p 127.0.0.1:3000:3000 \
  -v /path/to/secrets/user.txt:/run/secrets/mysql_user:ro \
  -v /path/to/secrets/password.txt:/run/secrets/mysql_password:ro \
  -e MYSQL_HOST=your-mysql-host.example.com \
  -e MYSQL_DATABASE=my_database \
  -e MYSQL_USER_FILE=/run/secrets/mysql_user \
  -e MYSQL_PASSWORD_FILE=/run/secrets/mysql_password \
  liorcodev/mysql-mcp-server:latest
```

*(Binding to `127.0.0.1` keeps the port off your LAN/public interfaces — only processes on the host machine can reach it.)*

*(Note: Native Docker Secrets `--secret` are supported by Docker Swarm and Docker Compose. For standalone `docker run`, volume mounting local files into `/run/secrets/` achieves the exact same security benefit for file-based secret reading).*

---

## Docker Compose

For repeatable local/production deployments, use the included `docker-compose.yml` (plain env vars) or `docker-compose-secret.yml` (file-based secrets via Docker's `secrets:` block, reading from `./secrets/mysql_user.txt` and `./secrets/mysql_password.txt` — see [secrets/README.md](secrets/README.md)):

```bash
# plain env vars (edit docker-compose.yml directly, or add MYSQL_USER/MYSQL_PASSWORD)
docker compose -f docker-compose.yml up -d --build

# file-based secrets
docker compose -f docker-compose-secret.yml up -d --build
```

Both compose files build the image locally from the repo's `Dockerfile` and publish it on `127.0.0.1:3000` only.

---

## Local Development

Requires [Bun](https://bun.sh).

```bash
bun install
cp .env.example .env   # fill in MYSQL_HOST / MYSQL_DATABASE / MYSQL_USER / MYSQL_PASSWORD etc.
bun run dev            # watch mode, http://localhost:3000/mcp
bun run start           # no watch
```

---

## MCP Tool Documentation

### `mysql_query`

Executes a SQL statement against the configured MySQL database.

#### Parameters:
- `sql` (string, required): SQL query to execute. Use `?` placeholders for parameterized queries.
- `params` (array, optional): Array of parameter values substituted into `?` placeholders.
- `maxRows` (number, optional): Max rows returned for `SELECT` queries (default: `500`, hard cap: `5000`).

#### Safety & Permissions:
- By default, only **read queries** (`SELECT`, `SHOW`, `EXPLAIN`, etc.) are permitted.
- Set `MYSQL_ALLOW_WRITE_OPERATIONS=true` to enable write operations.
- Set `MYSQL_ALLOW_DDL_OPERATIONS=true` to enable schema modifications.

---

## MCP Client Configuration Example

To connect an MCP client using Streamable HTTP transport:

```json
{
  "mcpServers": {
    "mysql": {
      "url": "http://localhost:3000/mcp"
    }
  }
}
```