#
tokens: 5416/50000 11/11 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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 | [![smithery badge](https://smithery.ai/badge/@burakdirin/mysqldb-mcp-server)](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 | 
```