# Directory Structure
```
├── .env.example
├── .gitignore
├── .python-version
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── pyproject.toml
├── README.md
├── requirements.txt
├── smithery.yaml
├── src
│ └── mysqldb_mcp_server
│ ├── __init__.py
│ └── server.py
└── uv.lock
```
# Files
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
```
1 | 3.11
2 |
```
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
```
1 | # MySQL Connection Settings
2 | MYSQL_HOST=localhost
3 | MYSQL_USER=root
4 | MYSQL_PASSWORD=your_password
5 | MYSQL_DATABASE=optional_database_name
6 | MYSQL_READONLY=true
7 |
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Python-generated files
2 | __pycache__/
3 | *.py[oc]
4 | build/
5 | dist/
6 | wheels/
7 | *.egg-info
8 |
9 | # Virtual environments
10 | .venv
11 |
12 | # MacOS
13 | .DS_Store
14 |
15 | # Environment variables
16 | .env
17 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # mysqldb-mcp-server MCP server
2 | [](https://smithery.ai/server/@burakdirin/mysqldb-mcp-server)
3 |
4 | A MySQL database MCP server project.
5 |
6 | ## Installation
7 |
8 | You can install the package using `uv`:
9 |
10 | ```bash
11 | uv pip install mysqldb-mcp-server
12 | ```
13 |
14 | Or using `pip`:
15 |
16 | ```bash
17 | pip install mysqldb-mcp-server
18 | ```
19 |
20 | ## Components
21 |
22 | ### Tools
23 |
24 | The server provides two tools:
25 | - `connect_database`: Connects to a specific MySQL database
26 | - `database` parameter: Name of the database to connect to (string)
27 | - Returns a confirmation message when connection is successful
28 |
29 | - `execute_query`: Executes MySQL queries
30 | - `query` parameter: SQL query/queries to execute (string)
31 | - Returns query results in JSON format
32 | - Multiple queries can be sent separated by semicolons
33 |
34 | ## Configuration
35 |
36 | The server uses the following environment variables:
37 |
38 | - `MYSQL_HOST`: MySQL server address (default: "localhost")
39 | - `MYSQL_USER`: MySQL username (default: "root")
40 | - `MYSQL_PASSWORD`: MySQL password (default: "")
41 | - `MYSQL_DATABASE`: Initial database (optional)
42 | - `MYSQL_READONLY`: Read-only mode (set to 1/true to enable, default: false)
43 |
44 | ## Quickstart
45 |
46 | ### Installation
47 |
48 | #### Claude Desktop
49 |
50 | MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
51 |
52 | Windows: `%APPDATA%/Claude/claude_desktop_config.json`
53 |
54 | <details>
55 | <summary>Development/Unpublished Server Configuration</summary>
56 |
57 | ```json
58 | {
59 | "mcpServers": {
60 | "mysqldb-mcp-server": {
61 | "command": "uv",
62 | "args": [
63 | "--directory",
64 | "/Users/burakdirin/Projects/mysqldb-mcp-server",
65 | "run",
66 | "mysqldb-mcp-server"
67 | ],
68 | "env": {
69 | "MYSQL_HOST": "localhost",
70 | "MYSQL_USER": "root",
71 | "MYSQL_PASSWORD": "password",
72 | "MYSQL_DATABASE": "[optional]",
73 | "MYSQL_READONLY": "true"
74 | }
75 | }
76 | }
77 | }
78 | ```
79 | </details>
80 |
81 | <details>
82 | <summary>Published Server Configuration</summary>
83 |
84 | ```json
85 | {
86 | "mcpServers": {
87 | "mysqldb-mcp-server": {
88 | "command": "uvx",
89 | "args": [
90 | "mysqldb-mcp-server"
91 | ],
92 | "env": {
93 | "MYSQL_HOST": "localhost",
94 | "MYSQL_USER": "root",
95 | "MYSQL_PASSWORD": "password",
96 | "MYSQL_DATABASE": "[optional]",
97 | "MYSQL_READONLY": "true"
98 | }
99 | }
100 | }
101 | }
102 | ```
103 | </details>
104 |
105 | ### Installing via Smithery
106 |
107 | To install MySQL Database Integration Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@burakdirin/mysqldb-mcp-server):
108 |
109 | ```bash
110 | npx -y @smithery/cli install @burakdirin/mysqldb-mcp-server --client claude
111 | ```
112 |
113 | ## Development
114 |
115 | ### Building and Publishing
116 |
117 | To prepare the package for distribution:
118 |
119 | 1. Sync dependencies and update lockfile:
120 | ```bash
121 | uv sync
122 | ```
123 |
124 | 2. Build package distributions:
125 | ```bash
126 | uv build
127 | ```
128 |
129 | This will create source and wheel distributions in the `dist/` directory.
130 |
131 | 3. Publish to PyPI:
132 | ```bash
133 | uv publish
134 | ```
135 |
136 | Note: You'll need to set PyPI credentials via environment variables or command flags:
137 | - Token: `--token` or `UV_PUBLISH_TOKEN`
138 | - Or username/password: `--username`/`UV_PUBLISH_USERNAME` and `--password`/`UV_PUBLISH_PASSWORD`
139 |
140 | ### Debugging
141 |
142 | Since MCP servers run over stdio, debugging can be challenging. For the best debugging
143 | experience, we strongly recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector).
144 |
145 | You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command:
146 |
147 | ```bash
148 | npx @modelcontextprotocol/inspector uv --directory /Users/burakdirin/Projects/mysqldb-mcp-server run mysqldb-mcp-server
149 | ```
150 |
151 | Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.
152 |
```
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
```
1 | mcp>=1.4.1
2 | python-dotenv>=1.0.1
3 | mysql-connector-python>=9.2.0
4 |
```
--------------------------------------------------------------------------------
/src/mysqldb_mcp_server/__init__.py:
--------------------------------------------------------------------------------
```python
1 | """
2 | MySQL MCP Server
3 |
4 | A server for interacting with MySQL databases through MCP.
5 | """
6 |
7 | from .server import mcp
8 |
9 |
10 | def main() -> None:
11 | """Run the MySQL MCP server"""
12 | mcp.run()
13 |
14 |
15 | __all__ = ['mcp', 'main']
16 |
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
2 | FROM python:3.11-slim
3 |
4 | # Set working directory
5 | WORKDIR /app
6 |
7 | # Copy necessary project files
8 | COPY pyproject.toml ./
9 | COPY README.md ./
10 | COPY src/ ./src/
11 |
12 | # Upgrade pip and install build dependencies
13 | RUN pip install --upgrade pip \
14 | && pip install hatchling
15 |
16 | # Install the package (skip prepare scripts if any)
17 | RUN pip install --ignore-installed --no-cache-dir .
18 |
19 | # MCP server runs over stdio so no ports are exposed
20 |
21 | # Start the MCP server using the entrypoint installed by setup
22 | CMD ["mysqldb-mcp-server"]
23 |
```
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
```toml
1 | [project]
2 | name = "mysqldb-mcp-server"
3 | version = "0.1.2"
4 | description = "An MCP server implementation for MySQL database integration"
5 | readme = "README.md"
6 | requires-python = ">=3.11"
7 | license = "MIT"
8 | dependencies = [
9 | "mcp>=1.4.1",
10 | "mysql-connector-python>=9.2.0",
11 | "python-dotenv>=1.0.1",
12 | ]
13 |
14 | [project.urls]
15 | Homepage = "https://github.com/burakdirin/mysqldb-mcp-server"
16 | Repository = "https://github.com/burakdirin/mysqldb-mcp-server"
17 | "Bug Tracker" = "https://github.com/burakdirin/mysqldb-mcp-server/issues"
18 |
19 | [[project.authors]]
20 | name = "Burak Dirin"
21 | email = "[email protected]"
22 |
23 | [build-system]
24 | requires = [ "hatchling",]
25 | build-backend = "hatchling.build"
26 |
27 | [project.scripts]
28 | mysqldb-mcp-server = "mysqldb_mcp_server:main"
29 |
30 | [tool.hatch.build.targets.wheel]
31 | packages = ["src/mysqldb_mcp_server"]
32 |
```
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
```markdown
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [0.1.2] - 2025-03-17
9 |
10 | ### Changed
11 | - Implemented lazy database initialization to improve deployment reliability
12 | - Modified startup behavior to prevent immediate database connection attempts
13 |
14 | ## [0.1.1] - 2025-03-17
15 |
16 | ### Added
17 | - GitHub repository information to package metadata
18 |
19 | ## [0.1.0] - 2025-03-17
20 |
21 | ### Added
22 | - Initial MCP server implementation for MySQL database integration
23 | - Python 3.11+ support
24 | - MySQL Connector integration
25 |
26 | [0.1.2]: https://github.com/burakdirin/mysqldb-mcp-server/releases/tag/v0.1.2
27 | [0.1.1]: https://github.com/burakdirin/mysqldb-mcp-server/releases/tag/v0.1.1
28 | [0.1.0]: https://github.com/burakdirin/mysqldb-mcp-server/releases/tag/v0.1.0
29 |
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
2 |
3 | startCommand:
4 | type: stdio
5 | configSchema:
6 | # JSON Schema defining the configuration options for the MCP.
7 | type: object
8 | properties:
9 | MYSQL_HOST:
10 | type: string
11 | default: localhost
12 | description: MySQL server address
13 | MYSQL_USER:
14 | type: string
15 | default: root
16 | description: MySQL username
17 | MYSQL_PASSWORD:
18 | type: string
19 | default: ""
20 | description: MySQL password
21 | MYSQL_DATABASE:
22 | type: string
23 | default: ""
24 | description: Initial database (optional)
25 | MYSQL_READONLY:
26 | type: boolean
27 | default: false
28 | description: Read-only mode, set true to enable
29 | commandFunction:
30 | # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
31 | |-
32 | (config) => ({
33 | command: 'mysqldb-mcp-server',
34 | args: [],
35 | env: {
36 | MYSQL_HOST: config.MYSQL_HOST,
37 | MYSQL_USER: config.MYSQL_USER,
38 | MYSQL_PASSWORD: config.MYSQL_PASSWORD,
39 | MYSQL_DATABASE: config.MYSQL_DATABASE,
40 | MYSQL_READONLY: config.MYSQL_READONLY.toString()
41 | }
42 | })
43 | exampleConfig:
44 | MYSQL_HOST: localhost
45 | MYSQL_USER: root
46 | MYSQL_PASSWORD: password
47 | MYSQL_DATABASE: ""
48 | MYSQL_READONLY: false
49 |
```
--------------------------------------------------------------------------------
/src/mysqldb_mcp_server/server.py:
--------------------------------------------------------------------------------
```python
1 | import json
2 | import os
3 | from contextlib import asynccontextmanager
4 | from dataclasses import dataclass
5 | from enum import Enum
6 | from typing import Any, AsyncIterator, Dict, List, Optional, Union
7 |
8 | import mysql.connector
9 | from dotenv import load_dotenv
10 | from mcp.server.fastmcp import Context, FastMCP
11 | from mysql.connector import Error as MySQLError
12 | from mysql.connector.cursor import MySQLCursor
13 |
14 |
15 | class MySQLServerError(Exception):
16 | """Base exception for MySQL server errors"""
17 | pass
18 |
19 |
20 | class ConnectionError(MySQLServerError):
21 | """Raised when there's an issue with the database connection"""
22 | pass
23 |
24 |
25 | class QueryError(MySQLServerError):
26 | """Raised when there's an issue executing a query"""
27 | pass
28 |
29 |
30 | class QueryType(Enum):
31 | """Enum for different types of SQL queries"""
32 | SELECT = "SELECT"
33 | INSERT = "INSERT"
34 | UPDATE = "UPDATE"
35 | DELETE = "DELETE"
36 | CREATE = "CREATE"
37 | DROP = "DROP"
38 | ALTER = "ALTER"
39 | TRUNCATE = "TRUNCATE"
40 | USE = "USE"
41 | SHOW = "SHOW"
42 | DESCRIBE = "DESCRIBE"
43 |
44 | @classmethod
45 | def is_write_operation(cls, query_type: str) -> bool:
46 | """Check if the query type is a write operation"""
47 | write_operations = {
48 | cls.INSERT, cls.UPDATE, cls.DELETE,
49 | cls.CREATE, cls.DROP, cls.ALTER, cls.TRUNCATE
50 | }
51 | try:
52 | return cls(query_type.upper()) in write_operations
53 | except ValueError:
54 | return False
55 |
56 |
57 | @dataclass
58 | class MySQLContext:
59 | """Context for MySQL connection"""
60 | host: str
61 | user: str
62 | password: str
63 | database: Optional[str]
64 | readonly: bool
65 | connection: Optional[mysql.connector.MySQLConnection] = None
66 |
67 | def ensure_connected(self) -> None:
68 | """Ensure database connection is available, connecting lazily if needed"""
69 | if not self.connection or not self.connection.is_connected():
70 | config = {
71 | "host": self.host,
72 | "user": self.user,
73 | "password": self.password,
74 | }
75 | if self.database:
76 | config["database"] = self.database
77 |
78 | try:
79 | self.connection = mysql.connector.connect(**config)
80 | except MySQLError as e:
81 | raise ConnectionError(
82 | f"Failed to connect to database: {str(e)}")
83 |
84 |
85 | class QueryExecutor:
86 | """Handles MySQL query execution and result processing"""
87 |
88 | def __init__(self, context: MySQLContext):
89 | self.context = context
90 |
91 | def _format_datetime(self, value: Any) -> Any:
92 | """Format datetime values to string"""
93 | return value.strftime('%Y-%m-%d %H:%M:%S') if hasattr(value, 'strftime') else value
94 |
95 | def _process_row(self, row: Dict[str, Any]) -> Dict[str, Any]:
96 | """Process a single row of results"""
97 | return {key: self._format_datetime(value) for key, value in row.items()}
98 |
99 | def _process_results(self, cursor: MySQLCursor) -> Union[List[Dict[str, Any]], Dict[str, int]]:
100 | """Process query results"""
101 | if cursor.with_rows:
102 | results = cursor.fetchall()
103 | return [self._process_row(row) for row in results]
104 | return {"affected_rows": cursor.rowcount}
105 |
106 | def execute_single_query(self, query: str) -> Dict[str, Any]:
107 | """Execute a single query and return results"""
108 | self.context.ensure_connected()
109 | cursor = None
110 |
111 | try:
112 | cursor = self.context.connection.cursor(dictionary=True)
113 | query_type = QueryType(query.strip().upper().split()[0])
114 |
115 | # Handle readonly mode
116 | if self.context.readonly and QueryType.is_write_operation(query_type.value):
117 | raise QueryError(
118 | "Server is in read-only mode. Write operations are not allowed.")
119 |
120 | # Handle USE statements
121 | if query_type == QueryType.USE:
122 | db_name = query.strip().split()[-1].strip('`').strip()
123 | self.context.database = db_name
124 | cursor.execute(query)
125 | return {"message": f"Switched to database: {db_name}"}
126 |
127 | # Execute query
128 | cursor.execute(query)
129 | results = self._process_results(cursor)
130 |
131 | if not self.context.readonly:
132 | self.context.connection.commit()
133 |
134 | return results
135 |
136 | except MySQLError as e:
137 | raise QueryError(f"Error executing query: {str(e)}")
138 | finally:
139 | if cursor:
140 | cursor.close()
141 |
142 | def execute_multiple_queries(self, query: str) -> List[Dict[str, Any]]:
143 | """Execute multiple queries and return results"""
144 | queries = [q.strip() for q in query.split(';') if q.strip()]
145 | results = []
146 |
147 | for single_query in queries:
148 | try:
149 | result = self.execute_single_query(single_query)
150 | results.append(result)
151 | except QueryError as e:
152 | results.append({"error": str(e)})
153 |
154 | return results
155 |
156 |
157 | def get_env_vars() -> tuple[str, str, str, Optional[str], bool]:
158 | """Get MySQL connection settings from environment variables
159 |
160 | Returns:
161 | Tuple of (host, user, password, database, readonly)
162 | """
163 | load_dotenv()
164 |
165 | host = os.getenv("MYSQL_HOST", "localhost")
166 | user = os.getenv("MYSQL_USER", "root")
167 | password = os.getenv("MYSQL_PASSWORD", "")
168 | database = os.getenv("MYSQL_DATABASE") # Optional
169 | readonly = os.getenv("MYSQL_READONLY", "0") in ("1", "true", "True")
170 |
171 | return host, user, password, database, readonly
172 |
173 |
174 | @asynccontextmanager
175 | async def mysql_lifespan(server: FastMCP) -> AsyncIterator[MySQLContext]:
176 | """MySQL connection lifecycle manager"""
177 | # Get connection settings from environment variables
178 | host, user, password, database, readonly = get_env_vars()
179 |
180 | # Initialize context without connecting
181 | ctx = MySQLContext(
182 | host=host,
183 | user=user,
184 | password=password,
185 | database=database,
186 | readonly=readonly,
187 | connection=None # Don't connect immediately
188 | )
189 |
190 | try:
191 | yield ctx
192 | finally:
193 | if ctx.connection and ctx.connection.is_connected():
194 | ctx.connection.close()
195 |
196 |
197 | # Create MCP server instance
198 | mcp = FastMCP("MySQL Explorer", lifespan=mysql_lifespan)
199 |
200 |
201 | def _get_executor(ctx: Context) -> QueryExecutor:
202 | """Helper function to get QueryExecutor from context"""
203 | mysql_ctx = ctx.request_context.lifespan_context
204 | return QueryExecutor(mysql_ctx)
205 |
206 |
207 | @mcp.tool()
208 | def connect_database(database: str, ctx: Context) -> str:
209 | """Connect to a specific MySQL database"""
210 | try:
211 | executor = _get_executor(ctx)
212 | result = executor.execute_single_query(f"USE `{database}`")
213 | return json.dumps(result, indent=2)
214 | except (ConnectionError, QueryError) as e:
215 | return str(e)
216 |
217 |
218 | @mcp.tool()
219 | def execute_query(query: str, ctx: Context) -> str:
220 | """Execute MySQL queries"""
221 | try:
222 | executor = _get_executor(ctx)
223 | results = executor.execute_multiple_queries(query)
224 |
225 | if len(results) == 1:
226 | return json.dumps(results[0], indent=2)
227 | return json.dumps(results, indent=2)
228 | except (ConnectionError, QueryError) as e:
229 | return str(e)
230 |
231 |
232 | if __name__ == "__main__":
233 | mcp.run()
234 |
```