MySQL (read-only, switchable) について
AI アシスタント向けの読み取り専用 MySQL MCP サーバーです。クライアントを再起動せず、セッション中にデータベース、サーバー、認証情報を切り替えられます。接続先は検証後に反映されます。
主な機能
- 01読み取り専用 SQL クエリ
- 02セッション中の DB・サーバー切り替え
- 03名前付き接続プロファイルの管理
- 04テーブル、インデックス、外部キーの確認
- 05サンプル行とテーブル定義の確認
- 06クエリ結果は最大 100 行
活用シーン
- 01会話中に複数のデータベースを比較
- 02テーブル定義、インデックス、外部キーの調査
- 03サンプルデータの参照と読み取り専用クエリ
MCP 対応
MCP クライアントに MySQL 接続管理と読み取り専用クエリのツールを提供します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
npx -y @shibbirweb/mcp-mysql-read-onlyMYSQL_HOSTHostname of the MySQL server. Defaults to 127.0.0.1MYSQL_PORTPort of the MySQL server. Defaults to 3306MYSQL_USERMySQL user. Prefer an account holding only SELECT grantsMYSQL_PASSWORDシークレットPassword for MYSQL_USER. Empty when the account has noneMYSQL_DATABASEDatabase to open at startup. Can be changed later without restartingMYSQL_PROFILESシークレットJSON object of named connections to switch between, keyed by profile nameMYSQL_DEFAULT_PROFILEWhich MYSQL_PROFILES entry to start onMYSQL_CONNECT_TIMEOUT_MSConnection timeout in milliseconds. Defaults to 10000MYSQL_QUERY_TIMEOUT_MSServer-side query timeout in milliseconds. Defaults to 30000ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。
ツール
12掲載時のツール一覧 · 2 週間前に更新
このツールにパラメーターはありません。
詳細
mcp-mysql-read-only
AI アシスタントに MySQL への読み取り専用アクセスを提供し、クライアントを再起動せずに、会話の途中でデータベース、サーバー、認証情報を変更できるMCP サーバーです。
ほとんどの MySQL MCP サーバーは、起動時に環境変数から接続情報を一度だけ読み込みます。別のデータベースを指定するには、設定ファイルを編集してアシスタントを再起動する必要があり、その際に会話が失われます。このサーバーでは接続を実行時の状態として保持するため、別の接続への切り替えはツールを呼び出すだけです。
npx を使って npm から実行することも、マシンに何もインストールせず Docker だけで実行することもできます。
flowchart LR
A["AI assistant<br/>Claude Desktop / Claude Code"]
B["mcp-mysql-read-only<br/>one process, whole session"]
C[("app_dev")]
D[("staging")]
E[("analytics")]
F[("any server<br/>reached with connect")]
A <-->|"MCP over stdio"| B
B -.->|"pooled per target"| C
B -.->|"pooled per target"| D
B -.->|"pooled per target"| E
B -.->|"opened at runtime"| F
サーバーはセッション全体を通して稼働するため、アクティブな接続はサーバー内部の状態として保持されます。切り替え時には再接続せず、別のプールを選択します。元に戻す場合は、すでにウォームアップ済みのプールを再利用します。
クイックスタート
実行方法は 2 つあります。npm はセットアップが簡単で、Docker ではサーバーをコンテナー内に保持できます。
npm
MYSQL_HOST=127.0.0.1 \
MYSQL_USER=readonly \
MYSQL_PASSWORD=secret \
MYSQL_DATABASE=my_database \
npx -y @shibbirweb/mcp-mysql-read-only
Node 22 以降が必要です。コンテナーを介さないため、127.0.0.1 は想定どおりに機能します。
Docker
docker run -i --rm \
--add-host host.docker.internal:host-gateway \
-e MYSQL_HOST=host.docker.internal \
-e MYSQL_USER=readonly \
-e MYSQL_PASSWORD=secret \
-e MYSQL_DATABASE=my_database \
shibbirweb/mcp-mysql-read-only
Docker と同じマシン上で実行されている MySQL に接続するには、host.docker.internal を使用してください。コンテナー内では、localhost はコンテナー自体を指します。
2 つの方法のうち、コンテナーを使うほうがより分離されています。サーバーはイメージと環境から与えられたものだけで実行されます。npm を使う場合は、ユーザーのアクセス権限でマシン上で直接実行されます。どちらも同じ読み取り専用の保証を適用します。
Claude Desktop
claude_desktop_config.json に追加します。
{
"mcpServers": {
"mysql": {
"command": "npx",
"args": ["-y", "@shibbirweb/mcp-mysql-read-only"],
"env": {
"MYSQL_HOST": "127.0.0.1",
"MYSQL_USER": "readonly",
"MYSQL_PASSWORD": "secret",
"MYSQL_DATABASE": "my_database"
}
}
}
}
同じサーバーを Docker で実行する場合:
{
"mcpServers": {
"mysql": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"--add-host", "host.docker.internal:host-gateway",
"-e", "MYSQL_HOST=host.docker.internal",
"-e", "MYSQL_USER=readonly",
"-e", "MYSQL_PASSWORD=secret",
"-e", "MYSQL_DATABASE=my_database",
"shibbirweb/mcp-mysql-read-only"
]
}
}
}
Claude Code
プロジェクトのルートにある .mcp.json で、同様に設定します。
{
"mcpServers": {
"mysql": {
"command": "npx",
"args": ["-y", "@shibbirweb/mcp-mysql-read-only"],
"env": {
"MYSQL_HOST": "127.0.0.1",
"MYSQL_USER": "readonly",
"MYSQL_PASSWORD": "secret",
"MYSQL_DATABASE": "my_database"
}
}
}
}
Claude Desktop のセクションにあるどちらの形式も、ここで使用できます。
クライアントを一度再起動してください。その後は、データベースを変更するためにクライアントを再起動する必要はありません。
これらのファイルに記載された認証情報は、ディスク上に平文で保存されます。
SELECT権限のみを持つ MySQL アカウントを使用し、ファイルをバージョン管理の対象から外してください。セキュリティを参照してください。
接続の切り替え
そのまま指示してください。次のような指示は、接続ツールに対応します。
「ステージングデータベースに切り替えて」 「
analyticsにはどんなテーブルがある?」 「10.0.0.5 の MySQL に reporting_user として接続して」
| 希望する操作 | 再起動が必要? |
|---|---|
| 同じサーバー上の別のデータベース | いいえ |
| 別の名前付きプロファイル | いいえ |
| 別のサーバーまたは認証情報 | いいえ |
MYSQL_PROFILES に新しい永続プロファイルを追加 | はい。一度だけ |
sequenceDiagram
autonumber
actor You
participant A as Assistant
participant S as MCP server
participant M as MySQL
You->>A: "how many users in staging?"
A->>S: use_connection(staging)
S->>M: open + SELECT 1
M-->>S: ok
Note over S: verified, so the switch is committed
S-->>A: Switched to staging
A->>S: run_query(SELECT COUNT(*) ...)
S->>M: SELECT COUNT(*) ...
M-->>S: 4821
A-->>You: 4821 users in staging
You->>A: "and in production?"
Note over A,S: same session, no restart
A->>S: use_connection(production)
検証に失敗した切り替えは確定されないため、以前の接続がアクティブなまま維持され、セッションも引き続き使用できます。
sequenceDiagram
participant S as MCP server
participant M as MySQL
S->>M: open "no_such_db" + SELECT 1
M-->>S: Unknown database
Note over S: active connection left untouched
S-->>S: Error: Unknown database 'no_such_db'
名前付きプロファイル
MYSQL_PROFILES に JSON オブジェクトを指定して、複数の接続をあらかじめ定義します。
{
"local": { "host": "host.docker.internal", "user": "root", "password": "", "database": "app_dev" },
"staging": { "host": "db.staging.internal", "user": "readonly", "password": "secret", "database": "app" },
"reports": { "host": "db.staging.internal", "user": "readonly", "password": "secret", "database": "analytics" }
}
単一の環境変数として渡します。
docker run -i --rm \
-e MYSQL_PROFILES='{"local":{"host":"host.docker.internal","user":"root","password":"","database":"app_dev"}}' \
-e MYSQL_DEFAULT_PROFILE=local \
shibbirweb/mcp-mysql-read-only
host のデフォルトは host.docker.internal、port は 3306、password は空です。user と database は必須です。どちらかがないプロファイルは、サーバーを停止させるのではなく、警告を出してスキップされます。
プロファイルにない接続先への接続
connect ツールは、実行時にホスト、ユーザー、パスワード、データベースを受け取り、エイリアスを付けてセッション終了まで保持します。ディスクへの書き込みや再起動は不要なので、一度だけデータベースを確認するために MYSQL_PROFILES を編集する必要はありません。
ツール
接続
| ツール | 目的 |
|---|---|
current_connection | アクティブなサーバーとデータベースを確認します |
list_connections | 使用可能なプロファイルを一覧表示します。* はアクティブなプロファイルを示します |
list_databases | 接続先サーバー上のデータベースを一覧表示します |
use_database | 現在のサーバーでスキーマを切り替えます |
use_connection | 名前付きプロファイルに切り替えます。database の上書きは任意です |
connect | 明示的な認証情報で任意のサーバーに接続します。alias の指定は任意です |
読み取り
| ツール | 目的 |
|---|---|
list_tables | アクティブなデータベース内のテーブルを一覧表示します |
describe_table | 1 つのテーブルの列と型を確認します |
get_table_indexes | 1 つのテーブルのインデックスを取得します |
get_foreign_keys | 1 つのテーブルの外部キー関係を取得します |
get_table_sample | 最大 50 件のサンプル行を取得します |
run_query | 読み取り専用のステートメントを 1 つ実行します |
すべての読み取りツールでは、任意で database を指定できます。指定したデータベースはその呼び出しにのみ適用され、アクティブな接続は変更されません。切り替えを繰り返さずに 2 つのデータベースを比較する場合に便利です。
use_database、use_connection、connect はそれぞれ、切り替えを確定する前に接続を開き、SELECT 1 を実行します。そのため、データベース名が無効な場合やホストに到達できない場合は、すぐに失敗します。切り替えに失敗しても、以前の接続はアクティブなままです。
設定
| 変数 | デフォルト | 目的 |
|---|---|---|
MYSQL_PROFILES | なし | 名前付きプロファイルの JSON オブジェクト |
MYSQL_DEFAULT_PROFILE | なし | 起動時にアクティブにするプロファイル |
MYSQL_HOST | host.docker.internal | 単一接続のフォールバック |
MYSQL_PORT | 3306 | " |
MYSQL_USER | なし | " |
MYSQL_PASSWORD | 空 | " |
MYSQL_DATABASE | なし | " |
MYSQL_QUERY_TIMEOUT_MS | 30000 | ステートメントのタイムアウト |
MYSQL_CONNECT_TIMEOUT_MS | 10000 | 接続のタイムアウト |
MYSQL_USER と MYSQL_DATABASE を設定すると、default という名前のプロファイルが登録されます。これらはいずれも必須ではありません。設定がまったくなくてもサーバーは起動し、ツールから connect を呼び出すよう案内されます。
起動時のプロファイル:MYSQL_DEFAULT_PROFILE が実在するプロファイル名であればそれを使用します。そうでなければ default を使用し、それもなければ最初に定義されたプロファイルを使用します。
セキュリティ
独立した 2 つのレイヤーで読み取り専用を維持するため、一方に抜け穴があっても、それだけで書き込みが可能になることはありません。
flowchart TD
Q["run_query"] --> V{"SQL validator"}
V -->|"DELETE, DROP, stacked statements,<br/>write behind a CTE, INTO OUTFILE"| R1["rejected, no connection used"]
V -->|"reads only"| D{"mysql2 driver"}
D -->|"multipleStatements: false"| R2["a second statement<br/>cannot even be sent"]
D --> M{"MySQL session"}
M -->|"SET SESSION TRANSACTION READ ONLY"| R3["writes rejected by the server<br/>with error 1792"]
M -->|"read"| OK["rows returned"]
style R1 fill:#fde,stroke:#b55
style R2 fill:#fde,stroke:#b55
style R3 fill:#fde,stroke:#b55
style OK fill:#dfd,stroke:#5b5
SQL バリデーター。 ステートメントの先頭に指定できるのは SELECT、WITH、SHOW、DESCRIBE、DESC、EXPLAIN のみです。キーワードを確認する前に、文字列リテラル、バッククォートで囲まれた識別子、および --、#、/* */ コメントを空白に置き換えるため、リテラル内に隠されたキーワードやセミコロンが SQL と誤認されることはありません。複数のステートメントを連結することは拒否されます。どちらも無害な先頭語の後ろに書き込みを含められるため、WITH と EXPLAIN ANALYZE では本文内の書き込みキーワードも検査されます。INTO OUTFILE、INTO DUMPFILE、LOAD DATA、SLEEP()、BENCHMARK() はブロックされます。ツール引数として渡されるテーブル名とデータベース名は ^[A-Za-z0-9_$]+$ に一致する必要があるため、補間先の識別子から抜け出すことはできません。
MySQL セッション。 プール内の各接続で SET SESSION TRANSACTION READ ONLY と SET SESSION MAX_EXECUTION_TIME を実行します。オートコミットが有効な場合、各ステートメントはそれぞれ独立した読み取り専用トランザクションとなるため、バリデーターを何らかの方法で回避できたとしても、サーバーはエラー 1792 で書き込みを拒否します。ドライバーは multipleStatements: false で実行されるため、2 つ目のステートメントを紛れ込ませることもできません。
これは何ではないか
これはガードであり、権限システムではありません。 このサーバー経由でアシスタントが書き込みを行うのを防ぎます。同じ認証情報を持つユーザーが、ほかのクライアント経由で書き込むことまでは防げません。
読み取り専用の MySQL ユーザーを指定してください。 これが本当の保護策です。
CREATE USER 'readonly'@'%' IDENTIFIED BY 'a strong password';
GRANT SELECT ON your_database.* TO 'readonly'@'%';
これにより、このサーバーにバグがあっても、書き込みはできません。
知っておくべきその他の制限事項:
SLEEP()とBENCHMARK()は、セッションが応答しなくなるのを防ぐための簡易的なガードとして、無条件にブロックされます。WITHクエリ内にupdateまたはdeleteという名前の列があると、拒否されます。バッククォートで囲んでください。- ツールの出力では、結果は100行に切り詰められます。クエリ自体には制限がないため、大きなテーブルを読み込む場合は
LIMITを追加してください。
既知の動作
ツール呼び出しの並列処理。 アクティブな接続は、プロセス状態の単一の要素です。クライアントが1つのバッチで複数のツール呼び出しを行うと、それらは同時に処理されるため、use_database を run_query と同じバッチで実行しても、先に反映されるとは限りません。逐次的に呼び出した場合は、想定どおりに動作します。特定のデータベースを指定して読み取る必要がある場合は、同じバッチで切り替える方法に頼らず、呼び出しごとの database 引数を渡してください。
シャットダウン。 サーバーは stdin が閉じたときではなく、SIGINT/SIGTERM を受け取ったときに終了します。開いているプールソケットによってイベントループが動作し続けます。また、stdin が EOF に達したことは、これ以上バッファーされたリクエストがないことを意味するだけです。これをシャットダウンの合図として扱うと、処理中の呼び出しがある状態でプールが終了してしまいます。サーバーを直接実行するスクリプトでは、期待するレスポンスを受け取るまで読み取りを続けてから、プロセスを終了してください。
開発
すべて Docker 内で実行されるため、必要なのはリポジトリのクローンと Docker だけです。
git clone https://github.com/shibbirweb/mcp-mysql-read-only.git
cd mcp-mysql-read-only
./scripts/test-in-docker.sh
これにより、一時的な MySQL コンテナーを起動し、テストイメージをビルドして、そこで全テストを実行した後、すべてを破棄します。お使いの MySQL には一切触れません。
Node 22 がローカルにインストールされている場合:
npm ci
npm run build
npm run test:unit # no database needed
npm test # integration tests need MySQL, see below
統合テストでは、TEST_MYSQL_HOST、TEST_MYSQL_PORT、TEST_MYSQL_USER、TEST_MYSQL_PASSWORD を読み取ります。テストでは一時的なデータベースを2つ(mcp_test、mcp_test_alt)作成して削除するため、使い捨てのサーバーを指定してください。MySQL に接続できない場合は、失敗とせずにテストをスキップします。
プロジェクト構成
src/
index.ts Entry point
ApplicationFactory.ts Composition root: the only file that wires things together
types/ Interfaces and type aliases, one file per concern
errors/ Named error classes
domain/ ConnectionTarget, ConnectionProfile (immutable value objects)
config/ Reading configuration from the environment
connections/ Target factory, profile registry, connection manager
database/ Pool manager, read-only session initializer, query executor
validation/ SQL skeletonizer, validators, rules/
formatting/ Response and row rendering
tools/ BaseTool, DatabaseScopedTool, connection/, reading/
server/ McpMySqlServer
依存関係は内側を向いており、クラスが独自に依存オブジェクトを生成することはありません。すべて ApplicationFactory から注入されるため、データベースや環境に依存せず、各部分を単体テストできます。
各クラスがそのように構成されている理由や、どこでどのデザインパターンを使っているかを含む開発者向けドキュメントは、wiki(ソースは docs/wiki/)にあります。
コントリビューション
プルリクエストの対象ブランチは master です。CI では MySQL 8.0 と 8.4 に対して全テストを実行し、amd64 と arm64 向けにイメージをビルドします。変更にはテストを追加し、動作が変わる場合は docs/wiki/ も更新してください。
このドキュメントのコピーとして README.dockerhub.md もあります。リリースワークフローでは、こちらを Docker Hub の説明文として公開します。Docker Hub では Mermaid も相対リンクもレンダリングされないため、こちらのコピーでは ASCII 図と絶対 URL を使用しています。ここでユーザー向けの動作を変更した場合は、そちらも変更してください。
変更履歴
npm に公開されたバージョンと、Docker Hub にのみ公開されたバージョンを含むリリース履歴は、CHANGELOG.md にあります。
プライバシー
このサーバーは、指定された MySQL 以外には何も送信しません。テレメトリーも、分析データもなく、ディスクへの書き込みも、終了後に保持されるデータもありません。お使いのマシンから外部に送信されるのは、アシスタントが読み取る内容です。クエリ結果は会話の内容になるためです。PRIVACY.md では、この両面について説明しています。
ライセンス
MIT © Md. Shibbir Ahmed
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。