Skip to main content
Glama
bomsan69

mysql-mcp-server

by bomsan69
README.md
# mysql-mcp-server

A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that lets an MCP
client (Claude Desktop, Claude Code, etc.) run SQL against a MySQL database through four
tools: `select`, `insert`, `update`, and `delete`.

The server runs as a `uv`-managed Python package and communicates with the client over
stdio, as a subprocess started by the client.

## Requirements

- Python 3.11+
- [`uv`](https://docs.astral.sh/uv/)
- A reachable MySQL server

## Installation

```bash
uv sync
```

## Configuration

The server requires six values, each settable via environment variable and/or CLI flag
(CLI flags take priority over environment variables):

| Parameter | Env var | CLI flag | Required | Default |
|---|---|---|---|---|
| Mode | `MYSQL_MODE` | `--mysql-mode` | yes | — (`readonly` or `readwrite`) |
| Host | `MYSQL_HOST` | `--mysql-host` | yes | — |
| Port | `MYSQL_PORT` | `--mysql-port` | no | `3306` |
| User | `MYSQL_USER` | `--mysql-user` | yes | — |
| Password | `MYSQL_PASSWORD` | `--mysql-password` | yes | — |
| Database | `MYSQL_DATABASE` | `--mysql-database` | yes | — |

If a required value is missing, or `MYSQL_MODE` is not `readonly`/`readwrite`, the server
prints an error to stderr and exits with status code 1 without starting.

- `readonly` mode: only the `select` tool is allowed. `insert`/`update`/`delete` are
  rejected with a `PERMISSION_DENIED` error.
- `readwrite` mode: all four tools are allowed.

The mode is fixed for the lifetime of the process; it cannot be changed at runtime.

> **Security recommendation**: `readonly` mode is an application-level guard, not a
> substitute for database privileges. Where possible, point `readonly` mode at a MySQL
> account that only has `SELECT` grants.

> **Is a `.env` file required?** No. The server itself never reads `.env` files — it only
> reads CLI flags and real process environment variables (`os.environ`). How you get
> values into that environment depends on how you run it:
>
> - **As an MCP server** (see [Connecting from an MCP client](#connecting-from-an-mcp-client)
>   below): the client (Claude Desktop/Code) spawns the server process and injects the
>   `env` block from its own JSON config directly as environment variables. No `.env`
>   file is involved or needed.
> - **Running the CLI directly for local dev/testing**: `.env` is just a convenience so
>   you don't have to `export` six variables by hand. Copy `.env.example` to `.env`, fill
>   in real values, and load it explicitly — it is not read automatically:
>   ```bash
>   uv run --env-file .env mysql-mcp-server
>   ```
>   `.env` is git-ignored and must never be committed.

## Running

```bash
# Environment variables (or use `uv run --env-file .env mysql-mcp-server`, see above)
export MYSQL_MODE=readonly
export MYSQL_HOST=127.0.0.1
export MYSQL_PORT=3306
export MYSQL_USER=app_user
export MYSQL_PASSWORD=secret
export MYSQL_DATABASE=mydb
uv run mysql-mcp-server

# Or, equivalently, via CLI flags
uv run mysql-mcp-server \
  --mysql-mode readonly \
  --mysql-host 127.0.0.1 \
  --mysql-port 3306 \
  --mysql-user app_user \
  --mysql-password secret \
  --mysql-database mydb
```

## Connecting from an MCP client

### Claude Desktop / Claude Code

Add an entry to your MCP client's server config (e.g. Claude Desktop's
`claude_desktop_config.json`, or `.mcp.json` for Claude Code):

```json
{
  "mcpServers": {
    "mysql": {
      "command": "uv",
      "args": [
        "--directory",
        "/absolute/path/to/mysql-mcp-server",
        "run",
        "mysql-mcp-server"
      ],
      "env": {
        "MYSQL_MODE": "readonly",
        "MYSQL_HOST": "127.0.0.1",
        "MYSQL_PORT": "3306",
        "MYSQL_USER": "app_user",
        "MYSQL_PASSWORD": "secret",
        "MYSQL_DATABASE": "mydb"
      }
    }
  }
}
```

Restart the client after editing the config. The `select`, `insert`, `update`, and
`delete` tools (subject to `MYSQL_MODE`) should then be available to the model.

## Tools

All four tools take `{"query": string, "params"?: array}` and always use `%s`
parameter-binding placeholders in `query` — never string-format user input into a query.

| Tool | Allowed in | Query must start with | Success `data` shape |
|---|---|---|---|
| `select` | any mode | `SELECT` / `WITH` | `{rows, row_count, truncated}` (capped at 1000 rows) |
| `insert` | `readwrite` only | `INSERT` | `{affected_rows, last_insert_id}` |
| `update` | `readwrite` only | `UPDATE` | `{affected_rows}` (+ `warning` if no `WHERE`) |
| `delete` | `readwrite` only | `DELETE` | `{affected_rows}` (+ `warning` if no `WHERE`) |

Every tool call returns one of:

```json
{ "success": true, "data": { ... } }
```
```json
{ "success": false, "error": { "code": "...", "message": "..." } }
```

Error codes: `PERMISSION_DENIED`, `INVALID_QUERY_TYPE`, `MULTI_STATEMENT_NOT_ALLOWED`,
`DB_CONNECTION_ERROR`, `DB_EXECUTION_ERROR`, `INTERNAL_ERROR`.

Multi-statement queries (`;`-separated) and any DDL/privilege statement (`DROP`,
`TRUNCATE`, `ALTER`, `GRANT`, `CREATE USER`, ...) are always rejected, since only the
four whitelisted statement types above are ever accepted.

## Development

```bash
uv sync
uv run ruff format .
uv run ruff check .
uv run pytest -v
uv run uv build   # packaging check
```

## Troubleshooting

- **Server exits immediately with status 1**: a required `MYSQL_*` value is missing or
  `MYSQL_MODE` is invalid — check stderr for which one.
- **`DB_CONNECTION_ERROR`**: MySQL is unreachable, or the credentials are wrong. The
  server keeps running and will retry the connection on the next tool call.
- **`PERMISSION_DENIED` on insert/update/delete**: the server is running in `readonly`
  mode; restart it with `MYSQL_MODE=readwrite` if writes are intended.

## Version history

- **0.1.0** — Initial release: `select`/`insert`/`update`/`delete` tools, `readonly`/
  `readwrite` mode policy, stdio MCP transport, automatic reconnect-and-retry on lost
  connections.

TDQS

A4.6/5.0

Scored across 4 tools

Disambiguation5/5

Each tool maps to a distinct SQL operation (SELECT, INSERT, UPDATE, DELETE) with no functional overlap. Misselection is impossible because the actions are mutually exclusive.

Naming Consistency5/5

All tool names are single lowercase verbs that directly match their SQL counterparts, forming a perfectly consistent and predictable pattern.

Tool Count5/5

Four tools precisely cover the core CRUD operations for a database server without redundancy or excessive granularity, matching the expected scope.

Completeness5/5

The tool surface provides full coverage of data manipulation (create, read, update, delete) with no obvious gaps. Additional schema or transaction management is outside the stated read/write mode scope.

Maintenance

ActivitySlowing
ResponsivenessNo issues