Skip to content

Commit 9966700

Browse files
feat: add charset and collation settings for compatibility with older MySQL versions (#56)
Co-authored-by: azrilaiman03 <[email protected]>
1 parent c647e40 commit 9966700

File tree

2 files changed

+66
-20
lines changed

2 files changed

+66
-20
lines changed

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,26 @@ MYSQL_PORT=3306 # Optional: Database port (defaults to 3306 if not speci
3333
MYSQL_USER=your_username
3434
MYSQL_PASSWORD=your_password
3535
MYSQL_DATABASE=your_database
36+
37+
# Optional: Charset and collation settings for compatibility with older MySQL versions
38+
MYSQL_CHARSET=utf8mb4 # Optional: Character set (defaults to utf8mb4)
39+
MYSQL_COLLATION=utf8mb4_unicode_ci # Optional: Collation (defaults to utf8mb4_unicode_ci)
40+
MYSQL_SQL_MODE=TRADITIONAL # Optional: SQL mode (defaults to TRADITIONAL)
41+
```
42+
43+
### Troubleshooting Collation Issues
44+
If you encounter the error "Unknown collation: 'utf8mb4_0900_ai_ci'", this typically means you're connecting to an older MySQL version (5.7 or earlier) that doesn't support the newer collation. The server now automatically uses compatible settings, but you can override them:
45+
46+
For MySQL 5.7 and earlier:
47+
```bash
48+
MYSQL_CHARSET=utf8mb4
49+
MYSQL_COLLATION=utf8mb4_unicode_ci
50+
```
51+
52+
For very old MySQL versions (5.6 and earlier):
53+
```bash
54+
MYSQL_CHARSET=utf8
55+
MYSQL_COLLATION=utf8_unicode_ci
3656
```
3757

3858
## Usage
@@ -44,7 +64,7 @@ Add this to your `claude_desktop_config.json`:
4464
"mysql": {
4565
"command": "uv",
4666
"args": [
47-
"--directory",
67+
"--directory",
4868
"path/to/mysql_mcp_server",
4969
"run",
5070
"mysql_mcp_server"
@@ -55,6 +75,9 @@ Add this to your `claude_desktop_config.json`:
5575
"MYSQL_USER": "your_username",
5676
"MYSQL_PASSWORD": "your_password",
5777
"MYSQL_DATABASE": "your_database"
78+
// Optional: Add these if you encounter collation issues
79+
// "MYSQL_CHARSET": "utf8mb4",
80+
// "MYSQL_COLLATION": "utf8mb4_unicode_ci"
5881
}
5982
}
6083
}
@@ -80,6 +103,9 @@ Add this to your `mcp.json`:
80103
"MYSQL_USER": "your_username",
81104
"MYSQL_PASSWORD": "your_password",
82105
"MYSQL_DATABASE": "your_database"
106+
// Optional: Add these if you encounter collation issues
107+
// "MYSQL_CHARSET": "utf8mb4",
108+
// "MYSQL_COLLATION": "utf8mb4_unicode_ci"
83109
}
84110
}
85111
}

src/mysql_mcp_server/server.py

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,25 @@ def get_db_config():
2121
"port": int(os.getenv("MYSQL_PORT", "3306")),
2222
"user": os.getenv("MYSQL_USER"),
2323
"password": os.getenv("MYSQL_PASSWORD"),
24-
"database": os.getenv("MYSQL_DATABASE")
24+
"database": os.getenv("MYSQL_DATABASE"),
25+
# Add charset and collation to avoid utf8mb4_0900_ai_ci issues with older MySQL versions
26+
# These can be overridden via environment variables for specific MySQL versions
27+
"charset": os.getenv("MYSQL_CHARSET", "utf8mb4"),
28+
"collation": os.getenv("MYSQL_COLLATION", "utf8mb4_unicode_ci"),
29+
# Disable autocommit for better transaction control
30+
"autocommit": True,
31+
# Set SQL mode for better compatibility - can be overridden
32+
"sql_mode": os.getenv("MYSQL_SQL_MODE", "TRADITIONAL")
2533
}
26-
27-
if not all([config["user"], config["password"], config["database"]]):
34+
35+
# Remove None values to let MySQL connector use defaults if not specified
36+
config = {k: v for k, v in config.items() if v is not None}
37+
38+
if not all([config.get("user"), config.get("password"), config.get("database")]):
2839
logger.error("Missing required database configuration. Please check environment variables:")
2940
logger.error("MYSQL_USER, MYSQL_PASSWORD, and MYSQL_DATABASE are required")
3041
raise ValueError("Missing required database configuration")
31-
42+
3243
return config
3344

3445
# Initialize server
@@ -39,12 +50,14 @@ async def list_resources() -> list[Resource]:
3950
"""List MySQL tables as resources."""
4051
config = get_db_config()
4152
try:
53+
logger.info(f"Connecting to MySQL with charset: {config.get('charset')}, collation: {config.get('collation')}")
4254
with connect(**config) as conn:
55+
logger.info(f"Successfully connected to MySQL server version: {conn.get_server_info()}")
4356
with conn.cursor() as cursor:
4457
cursor.execute("SHOW TABLES")
4558
tables = cursor.fetchall()
4659
logger.info(f"Found tables: {tables}")
47-
60+
4861
resources = []
4962
for table in tables:
5063
resources.append(
@@ -58,6 +71,7 @@ async def list_resources() -> list[Resource]:
5871
return resources
5972
except Error as e:
6073
logger.error(f"Failed to list resources: {str(e)}")
74+
logger.error(f"Error code: {e.errno}, SQL state: {e.sqlstate}")
6175
return []
6276

6377
@app.read_resource()
@@ -66,24 +80,27 @@ async def read_resource(uri: AnyUrl) -> str:
6680
config = get_db_config()
6781
uri_str = str(uri)
6882
logger.info(f"Reading resource: {uri_str}")
69-
83+
7084
if not uri_str.startswith("mysql://"):
7185
raise ValueError(f"Invalid URI scheme: {uri_str}")
72-
86+
7387
parts = uri_str[8:].split('/')
7488
table = parts[0]
75-
89+
7690
try:
91+
logger.info(f"Connecting to MySQL with charset: {config.get('charset')}, collation: {config.get('collation')}")
7792
with connect(**config) as conn:
93+
logger.info(f"Successfully connected to MySQL server version: {conn.get_server_info()}")
7894
with conn.cursor() as cursor:
7995
cursor.execute(f"SELECT * FROM {table} LIMIT 100")
8096
columns = [desc[0] for desc in cursor.description]
8197
rows = cursor.fetchall()
8298
result = [",".join(map(str, row)) for row in rows]
8399
return "\n".join([",".join(columns)] + result)
84-
100+
85101
except Error as e:
86102
logger.error(f"Database error reading resource {uri}: {str(e)}")
103+
logger.error(f"Error code: {e.errno}, SQL state: {e.sqlstate}")
87104
raise RuntimeError(f"Database error: {str(e)}")
88105

89106
@app.list_tools()
@@ -112,26 +129,28 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
112129
"""Execute SQL commands."""
113130
config = get_db_config()
114131
logger.info(f"Calling tool: {name} with arguments: {arguments}")
115-
132+
116133
if name != "execute_sql":
117134
raise ValueError(f"Unknown tool: {name}")
118-
135+
119136
query = arguments.get("query")
120137
if not query:
121138
raise ValueError("Query is required")
122-
139+
123140
try:
141+
logger.info(f"Connecting to MySQL with charset: {config.get('charset')}, collation: {config.get('collation')}")
124142
with connect(**config) as conn:
143+
logger.info(f"Successfully connected to MySQL server version: {conn.get_server_info()}")
125144
with conn.cursor() as cursor:
126145
cursor.execute(query)
127-
146+
128147
# Special handling for SHOW TABLES
129148
if query.strip().upper().startswith("SHOW TABLES"):
130149
tables = cursor.fetchall()
131150
result = ["Tables_in_" + config["database"]] # Header
132151
result.extend([table[0] for table in tables])
133152
return [TextContent(type="text", text="\n".join(result))]
134-
153+
135154
# Handle all other queries that return result sets (SELECT, SHOW, DESCRIBE etc.)
136155
elif cursor.description is not None:
137156
columns = [desc[0] for desc in cursor.description]
@@ -142,31 +161,32 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
142161
except Error as e:
143162
logger.warning(f"Error fetching results: {str(e)}")
144163
return [TextContent(type="text", text=f"Query executed but error fetching results: {str(e)}")]
145-
164+
146165
# Non-SELECT queries
147166
else:
148167
conn.commit()
149168
return [TextContent(type="text", text=f"Query executed successfully. Rows affected: {cursor.rowcount}")]
150-
169+
151170
except Error as e:
152171
logger.error(f"Error executing SQL '{query}': {e}")
172+
logger.error(f"Error code: {e.errno}, SQL state: {e.sqlstate}")
153173
return [TextContent(type="text", text=f"Error executing query: {str(e)}")]
154174

155175
async def main():
156176
"""Main entry point to run the MCP server."""
157177
from mcp.server.stdio import stdio_server
158-
178+
159179
# Add additional debug output
160180
print("Starting MySQL MCP server with config:", file=sys.stderr)
161181
config = get_db_config()
162182
print(f"Host: {config['host']}", file=sys.stderr)
163183
print(f"Port: {config['port']}", file=sys.stderr)
164184
print(f"User: {config['user']}", file=sys.stderr)
165185
print(f"Database: {config['database']}", file=sys.stderr)
166-
186+
167187
logger.info("Starting MySQL MCP server...")
168188
logger.info(f"Database config: {config['host']}/{config['database']} as {config['user']}")
169-
189+
170190
async with stdio_server() as (read_stream, write_stream):
171191
try:
172192
await app.run(

0 commit comments

Comments
 (0)