├── .gitignore ├── Dockerfile ├── LICENSE ├── README-zh.md ├── README.md ├── docker-compose.yml ├── pyproject.toml ├── requirements.txt └── src └── mysql_mcp_server_pro ├── __init__.py ├── cli.py ├── config ├── .env ├── __init__.py ├── dbconfig.py └── event_store.py ├── handles ├── __init__.py ├── base.py ├── execute_sql.py ├── get_chinese_initials.py ├── get_db_health_index_usage.py ├── get_db_health_running.py ├── get_table_desc.py ├── get_table_index.py ├── get_table_lock.py └── get_table_name.py ├── prompts ├── AnalysisMySqlIssues.py ├── BasePrompt.py ├── QueryTableData.py └── __init__.py └── server.py /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile 2 | # Use the official Python image from the Docker Hub 3 | FROM python:3.11-slim 4 | 5 | # Set the working directory in the container 6 | WORKDIR /app 7 | 8 | # Copy the requirements file into the container 9 | COPY requirements.txt . 10 | 11 | # Install the dependencies specified in the requirements file 12 | RUN pip install --no-cache-dir -r requirements.txt 13 | 14 | # Copy the current directory contents into the container at /app 15 | COPY src/ /app/src 16 | 17 | # Set environment variables for MySQL (these can be overwritten with `docker run -e`) 18 | ENV MYSQL_HOST=localhost 19 | ENV MYSQL_PORT=3306 20 | ENV MYSQL_USER=root 21 | ENV MYSQL_PASSWORD=123456 22 | ENV MYSQL_DATABASE=a_llm 23 | ENV MYSQL_ROLE=admin 24 | ENV PYTHONPATH=/app/src 25 | 26 | # Command to run the server 27 | CMD ["python", "-m", "mysql_mcp_server_pro.server"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 tumf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | [![简体中文](https://img.shields.io/badge/简体中文-点击查看-orange)](README-zh.md) 2 | [![English](https://img.shields.io/badge/English-Click-yellow)](README.md) 3 | 4 | # mcp_mysql_server_pro 5 | #### 帮忙点个赞啊,朋友们。 6 | ## 介绍 7 | mcp_mysql_server_pro 不仅止于mysql的增删改查功能,还包含了数据库异常分析能力,且便于开发者们进行个性化的工具扩展 8 | 9 | - 支持 Model Context Protocol (MCP) 所有传输模式(STDIO、SSE、Streamable Http) 10 | - 支持 支持多sql执行,以";"分隔。 11 | - 支持 根据表注释可以查询出对于的数据库表名,表字段 12 | - 支持 sql执行计划分析 13 | - 支持 中文字段转拼音. 14 | - 支持 锁表分析 15 | - 支持 运行健康状态分析 16 | - 支持权限控制,只读(readonly)、读写(writer)、管理员(admin) 17 | ``` 18 | "readonly": ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"], # 只读权限 19 | "writer": ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN", "INSERT", "UPDATE", "DELETE"], # 读写权限 20 | "admin": ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN", "INSERT", "UPDATE", "DELETE", 21 | "CREATE", "ALTER", "DROP", "TRUNCATE"] # 管理员权限 22 | ``` 23 | - 支持 prompt 模版调用 24 | 25 | ## 工具列表 26 | | 工具名称 | 描述 | 27 | |-----------------------|------------------------------------------------------------------------------------------------------------------------------------| 28 | | execute_sql | sql执行工具,根据权限配置可执行["SELECT", "SHOW", "DESCRIBE", "EXPLAIN", "INSERT", "UPDATE", "DELETE", "CREATE", "ALTER", "DROP", "TRUNCATE"] 命令 | 29 | | get_chinese_initials | 将中文字段名转换为拼音首字母字段 | 30 | | get_db_health_running | 分析mysql的健康状态(连接情况、事务情况、运行情况、锁情况检测) | 31 | | get_table_desc | 根据表名搜索数据库中对应的表结构,支持多表查询 | 32 | | get_table_index | 根据表名搜索数据库中对应的表索引,支持多表查询 | 33 | | get_table_lock | 查询当前mysql服务器是否存在行级锁、表级锁情况 | 34 | | get_table_name | 根据表注释、表描述搜索数据库中对应的表名 | 35 | | get_db_health_index_usage | 获取当前连接的mysql库的索引使用情况,包含冗余索引情况、性能较差的索引情况、未使用索引且查询时间大于30秒top5情况| 36 | 37 | ## prompt 列表 38 | | prompt名称 | 描述 | 39 | |----------------------------|------------------------------------------------------------------------------------------------------------------------------------| 40 | | analyzing-mysql-prompt | 这是分析mysql相关问题的提示词 | 41 | | query-table-data-prompt | 这是通过调用工具查询表数据的提示词,描述可以为空,空时则会初始化为mysql数据库数据查询助手 | 42 | 43 | ## 使用说明 44 | 45 | ### pip安装和配置 46 | 1. 安装包 47 | ```bash 48 | pip install mysql_mcp_server_pro 49 | ``` 50 | 51 | 2. 配置环境变量 52 | 创建一个 `.env` 文件,内容如下: 53 | ```bash 54 | # MySQL数据库配置 55 | MYSQL_HOST=localhost 56 | MYSQL_PORT=3306 57 | MYSQL_USER=your_username 58 | MYSQL_PASSWORD=your_password 59 | MYSQL_DATABASE=your_database 60 | # 可选值: readonly, writer, admin,默认为 readonly 61 | MYSQL_ROLE=readonly 62 | ``` 63 | 64 | 3. 运行服务 65 | ```bash 66 | # SSE 模式 67 | mysql_mcp_server_sse 68 | 69 | ## Streamable Http 模式 70 | mysql_mcp_server_streamable_http 71 | ``` 72 | 73 | 4. 在mcp 客户端配置上。详细看下方的sse启动 74 | 75 | 76 | 77 | 注意: 78 | - `.env` 文件应该放在运行命令的目录下 79 | - 也可以直接在环境中设置这些变量 80 | - 确保数据库配置正确且可以连接 81 | 82 | 83 | ### 使用 uvx 运行,客户端配置 84 | - 该方式直接在支持mcp 客户端上配置即可,无需下载源码。如通义千问插件,trae编辑工具等 85 | ``` 86 | { 87 | "mcpServers": { 88 | "mysql": { 89 | "command": "uvx", 90 | "args": [ 91 | "--from", 92 | "mysql_mcp_server_pro", 93 | "mysql_mcp_server_pro" 94 | ], 95 | "env": { 96 | "MYSQL_HOST": "192.168.x.xxx", 97 | "MYSQL_PORT": "3306", 98 | "MYSQL_USER": "root", 99 | "MYSQL_PASSWORD": "root", 100 | "MYSQL_DATABASE": "a_llm", 101 | "MYSQL_ROLE": "admin" 102 | } 103 | } 104 | } 105 | } 106 | 107 | ``` 108 | 109 | ### 本地开发 Streamable Http 方式 110 | 111 | - 使用 uv 启动服务 112 | 113 | 将以下内容添加到你的 mcp client 工具中,例如cursor、cline等 114 | 115 | mcp json 如下 116 | ```` 117 | { 118 | "mcpServers": { 119 | "mysql_mcp_server_pro": { 120 | "name": "mysql_mcp_server_pro", 121 | "type": "streamableHttp", 122 | "description": "", 123 | "isActive": true, 124 | "baseUrl": "http://localhost:3000/mcp/" 125 | } 126 | } 127 | } 128 | ```` 129 | 130 | 修改.env 文件内容,将数据库连接信息修改为你的数据库连接信息 131 | ``` 132 | # MySQL数据库配置 133 | MYSQL_HOST=192.168.xxx.xxx 134 | MYSQL_PORT=3306 135 | MYSQL_USER=root 136 | MYSQL_PASSWORD=root 137 | MYSQL_DATABASE=a_llm 138 | MYSQL_ROLE=admin 139 | ``` 140 | 141 | 启动命令 142 | ``` 143 | # 下载依赖 144 | uv sync 145 | 146 | # 启动 147 | uv run -m mysql_mcp_server_pro.server 148 | ``` 149 | 150 | ### 本地开发 SSE 方式 151 | 152 | - 使用 uv 启动服务 153 | 154 | 将以下内容添加到你的 mcp client 工具中,例如cursor、cline等 155 | 156 | mcp json 如下 157 | ```` 158 | { 159 | "mcpServers": { 160 | "mysql_mcp_server_pro": { 161 | "name": "mysql_mcp_server_pro", 162 | "description": "", 163 | "isActive": true, 164 | "baseUrl": "http://localhost:9000/sse" 165 | } 166 | } 167 | } 168 | ```` 169 | 170 | 修改.env 文件内容,将数据库连接信息修改为你的数据库连接信息 171 | ``` 172 | # MySQL数据库配置 173 | MYSQL_HOST=192.168.xxx.xxx 174 | MYSQL_PORT=3306 175 | MYSQL_USER=root 176 | MYSQL_PASSWORD=root 177 | MYSQL_DATABASE=a_llm 178 | MYSQL_ROLE=admin 179 | ``` 180 | 181 | 启动命令 182 | ``` 183 | # 下载依赖 184 | uv sync 185 | 186 | # 启动 187 | uv run -m mysql_mcp_server_pro.server --sse 188 | ``` 189 | 190 | ### 本地开发 STDIO 方式 191 | 192 | 将以下内容添加到你的 mcp client 工具中,例如cursor、cline等 193 | 194 | mcp json 如下 195 | ``` 196 | { 197 | "mcpServers": { 198 | "operateMysql": { 199 | "isActive": true, 200 | "name": "operateMysql", 201 | "command": "uv", 202 | "args": [ 203 | "--directory", 204 | "/Volumes/mysql_mcp_server_pro/src/mysql_mcp_server_pro", # 这里需要替换为你的项目路径 205 | "run", 206 | "-m", 207 | "mysql_mcp_server_pro.server", 208 | "--stdio" 209 | ], 210 | "env": { 211 | "MYSQL_HOST": "localhost", 212 | "MYSQL_PORT": "3306", 213 | "MYSQL_USER": "root", 214 | "MYSQL_PASSWORD": "123456", 215 | "MYSQL_DATABASE": "a_llm", 216 | "MYSQL_ROLE": "admin" 217 | } 218 | } 219 | } 220 | } 221 | ``` 222 | 223 | ## 个性扩展工具 224 | 1. 在handles包中新增工具类,继承BaseHandler,实现get_tool_description、run_tool方法 225 | 226 | 2. 在__init__.py中引入新工具即可在server中调用 227 | 228 | 229 | ## 工具调用示例 230 | 1. 创建新表以及插入数据 prompt格式如下 231 | ``` 232 | # 任务 233 | 创建一张组织架构表,表结构如下:部门名称,部门编号,父部门,是否有效。 234 | # 要求 235 | - 表名用t_admin_rms_zzjg, 236 | - 字段要求:字符串类型使用'varchar(255)',整数类型使用'int',浮点数类型使用'float',日期和时间类型使用'datetime',布尔类型使用'boolean',文本类型使用'text',大文本类型使用'longtext',大整数类型使用'bigint',大浮点数类型使用'double。 237 | - 表头需要加入主键字段,序号 XH varchar(255) 238 | - 表最后需加入固定字段:创建人-CJR varchar(50),创建时间-CJSJ datetime,修改人-XGR varchar(50),修改时间-XGSJ datetime。 239 | - 字段命名使用工具返回内容作为字段命名 240 | - 常用字段需要添加索引 241 | - 每个字段需要添加注释,表注释也需要 242 | - 创建完成后生成5条真实数据 243 | ``` 244 | ![image](https://github.com/user-attachments/assets/8a07007f-7375-4fb3-b69e-7ef9ffd68044) 245 | 246 | 247 | 2. 根据表注释查询数据 prompt如下 248 | ``` 249 | 查询用户信息表中张三的数据 250 | ``` 251 | ![image](https://github.com/user-attachments/assets/fb57aae3-1e50-4bc8-98d9-36e377cc9722) 252 | 253 | 254 | 3. 分析慢sql prompt如下 255 | ``` 256 | select * from t_jcsjzx_hjkq_cd_xsz_sk xsz 257 | left join t_jcsjzx_hjkq_jcd jcd on jcd.cddm = xsz.cddm 258 | 根据当前的索引情况,查看执行计划提出优化意见,以markdown格式输出,sql相关的表索引情况、执行情况,优化意见 259 | ``` 260 | 261 | 4. 分析sql卡死问题 prompt如下 262 | ``` 263 | update t_admin_rms_zzjg set sfyx = '0' where xh = '1' 卡死了,请分析原因 264 | ``` 265 | ![image](https://github.com/user-attachments/assets/e99576f0-a50f-457c-ad48-9e9c45391b89) 266 | 267 | 268 | 5. 分析健康状态 prompt如下 269 | ``` 270 | 检测mysql当前的健康状态 271 | ``` 272 | ![image](https://github.com/user-attachments/assets/156d91be-3140-4cf2-9456-c24eb268a0f0) 273 | 274 | ## prompt 调用示例 275 | 1. mysql 分析prompt 调用示例 276 | - step1: 选择 analyzing-mysql-prompt 277 | 278 | ![image](https://github.com/user-attachments/assets/3be1ad59-2b1e-452b-bef4-564f0a754e74) 279 | 280 | - step2: 自动生成对应prompt 281 | 282 | ![image](https://github.com/user-attachments/assets/9438dba0-c003-4d14-bfe8-401f32f71b07) 283 | 284 | - step3: 开始问答 285 | 286 | ![image](https://github.com/user-attachments/assets/9eefbb82-794d-4cb2-82e1-4debe237d86a) 287 | 288 | 2. 表数据查询 prompt 调用示例 289 | - step1: 选择 query-table-data-prompt 290 | 291 | ![image](https://github.com/user-attachments/assets/768ff4cc-be89-42b0-802f-4e41f105db11) 292 | 293 | - step2: 输入问题描述(可选),不输入则会初始化为mysql数据查询助手 294 | 295 | ![image](https://github.com/user-attachments/assets/968e7cfd-4dfe-47b5-9fc3-49cbf07a7a78) 296 | 297 | ![image](https://github.com/user-attachments/assets/3e1f80c1-2cff-471a-997a-94b8104e1b9b) 298 | 299 | 300 | 301 | 302 | 303 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![简体中文](https://img.shields.io/badge/简体中文-点击查看-orange)](README-zh.md) 2 | [![English](https://img.shields.io/badge/English-Click-yellow)](README.md) 3 | 4 | # mcp_mysql_server_pro 5 | 6 | ## Introduction 7 | mcp_mysql_server_pro is not just about MySQL CRUD operations, but also includes database anomaly analysis capabilities and makes it easy for developers to extend with custom tools. 8 | 9 | - Supports all Model Context Protocol (MCP) transfer modes (STDIO, SSE, Streamable Http) 10 | - Supports multiple SQL execution, separated by ";" 11 | - Supports querying database table names and fields based on table comments 12 | - Supports SQL execution plan analysis 13 | - Supports Chinese field to pinyin conversion 14 | - Supports table lock analysis 15 | - Supports database health status analysis 16 | - Supports permission control with three roles: readonly, writer, and admin 17 | ``` 18 | "readonly": ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"], # Read-only permissions 19 | "writer": ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN", "INSERT", "UPDATE", "DELETE"], # Read-write permissions 20 | "admin": ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN", "INSERT", "UPDATE", "DELETE", 21 | "CREATE", "ALTER", "DROP", "TRUNCATE"] # Administrator permissions 22 | ``` 23 | - Supports prompt template invocation 24 | 25 | 26 | ## Tool List 27 | | Tool Name | Description | 28 | |----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 29 | | execute_sql | SQL execution tool that can execute ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN", "INSERT", "UPDATE", "DELETE", "CREATE", "ALTER", "DROP", "TRUNCATE"] commands based on permission configuration | 30 | | get_chinese_initials | Convert Chinese field names to pinyin initials | 31 | | get_db_health_running | Analyze MySQL health status (connection status, transaction status, running status, lock status detection) | 32 | | get_table_desc | Search for table structures in the database based on table names, supporting multi-table queries | 33 | | get_table_index | Search for table indexes in the database based on table names, supporting multi-table queries | 34 | | get_table_lock | Check if there are row-level locks or table-level locks in the current MySQL server | 35 | | get_table_name | Search for table names in the database based on table comments and descriptions | 36 | | get_db_health_index_usage | Get the index usage of the currently connected mysql database, including redundant index situations, poorly performing index situations, and the top 5 unused index situations with query times greater than 30 seconds | 37 | 38 | ## Prompt List 39 | | Prompt Name | Description | 40 | |---------------------------|---------------------------------------------------------------------------------------------------------------------------------------| 41 | | analyzing-mysql-prompt | This is a prompt for analyzing MySQL-related issues | 42 | | query-table-data-prompt | This is a prompt for querying table data using tools. If description is empty, it will be initialized as a MySQL database query assistant | 43 | 44 | ## Usage Instructions 45 | 46 | ### Installation and Configuration 47 | 1. Install Package 48 | ```bash 49 | pip install mysql_mcp_server_pro 50 | ``` 51 | 52 | 2. Configure Environment Variables 53 | Create a `.env` file with the following content: 54 | ```bash 55 | # MySQL Database Configuration 56 | MYSQL_HOST=localhost 57 | MYSQL_PORT=3306 58 | MYSQL_USER=your_username 59 | MYSQL_PASSWORD=your_password 60 | MYSQL_DATABASE=your_database 61 | # Optional, default is 'readonly'. Available values: readonly, writer, admin 62 | MYSQL_ROLE=readonly 63 | ``` 64 | 65 | 3. Run Service 66 | ```bash 67 | # SSE mode 68 | mysql_mcp_server_sse 69 | 70 | ## Streamable Http mode 71 | mysql_mcp_server_streamable_http 72 | ``` 73 | 74 | 4. mcp client 75 | 76 | go to see see "Use uv to start the service" 77 | ^_^ 78 | 79 | 80 | Note: 81 | - The `.env` file should be placed in the directory where you run the command 82 | - You can also set these variables directly in your environment 83 | - Make sure the database configuration is correct and can connect 84 | 85 | ### Run with uvx, Client Configuration 86 | - This method can be used directly in MCP-supported clients, no need to download the source code. For example, Tongyi Qianwen plugin, trae editor, etc. 87 | ```json 88 | { 89 | "mcpServers": { 90 | "mysql": { 91 | "command": "uvx", 92 | "args": [ 93 | "--from", 94 | "mysql_mcp_server_pro", 95 | "mysql_mcp_server_pro" 96 | ], 97 | "env": { 98 | "MYSQL_HOST": "192.168.x.xxx", 99 | "MYSQL_PORT": "3306", 100 | "MYSQL_USER": "root", 101 | "MYSQL_PASSWORD": "root", 102 | "MYSQL_DATABASE": "a_llm", 103 | "MYSQL_ROLE": "admin" 104 | } 105 | } 106 | } 107 | } 108 | ``` 109 | 110 | ### Local Development with Streamable Http mode 111 | 112 | - Use uv to start the service 113 | 114 | Add the following content to your mcp client tools, such as cursor, cline, etc. 115 | 116 | mcp json as follows: 117 | ``` 118 | { 119 | "mcpServers": { 120 | "mysql_mcp_server_pro": { 121 | "name": "mysql_mcp_server_pro", 122 | "type": "streamableHttp", 123 | "description": "", 124 | "isActive": true, 125 | "baseUrl": "http://localhost:3000/mcp/" 126 | } 127 | } 128 | } 129 | ``` 130 | 131 | Modify the .env file content to update the database connection information with your database details: 132 | ``` 133 | # MySQL Database Configuration 134 | MYSQL_HOST=192.168.xxx.xxx 135 | MYSQL_PORT=3306 136 | MYSQL_USER=root 137 | MYSQL_PASSWORD=root 138 | MYSQL_DATABASE=a_llm 139 | MYSQL_ROLE=admin 140 | ``` 141 | 142 | Start commands: 143 | ``` 144 | # Download dependencies 145 | uv sync 146 | 147 | # Start 148 | uv run -m mysql_mcp_server_pro.server 149 | ``` 150 | 151 | ### Local Development with SSE Mode 152 | 153 | - Use uv to start the service 154 | 155 | Add the following content to your mcp client tools, such as cursor, cline, etc. 156 | 157 | mcp json as follows: 158 | ``` 159 | { 160 | "mcpServers": { 161 | "mysql_mcp_server_pro": { 162 | "name": "mysql_mcp_server_pro", 163 | "description": "", 164 | "isActive": true, 165 | "baseUrl": "http://localhost:9000/sse" 166 | } 167 | } 168 | } 169 | ``` 170 | 171 | Modify the .env file content to update the database connection information with your database details: 172 | ``` 173 | # MySQL Database Configuration 174 | MYSQL_HOST=192.168.xxx.xxx 175 | MYSQL_PORT=3306 176 | MYSQL_USER=root 177 | MYSQL_PASSWORD=root 178 | MYSQL_DATABASE=a_llm 179 | MYSQL_ROLE=readonly # Optional, default is 'readonly'. Available values: readonly, writer, admin 180 | ``` 181 | 182 | Start commands: 183 | ``` 184 | # Download dependencies 185 | uv sync 186 | 187 | # Start 188 | uv run -m mysql_mcp_server_pro.server --sse 189 | ``` 190 | 191 | ### Local Development with STDIO Mode 192 | 193 | Add the following content to your mcp client tools, such as cursor, cline, etc. 194 | 195 | mcp json as follows: 196 | ``` 197 | { 198 | "mcpServers": { 199 | "operateMysql": { 200 | "isActive": true, 201 | "name": "operateMysql", 202 | "command": "uv", 203 | "args": [ 204 | "--directory", 205 | "/Volumes/mysql_mcp_server_pro/src/mysql_mcp_server_pro", # Replace this with your project path 206 | "run", 207 | "-m", 208 | "mysql_mcp_server_pro.server", 209 | "--stdio" 210 | ], 211 | "env": { 212 | "MYSQL_HOST": "localhost", 213 | "MYSQL_PORT": "3306", 214 | "MYSQL_USER": "root", 215 | "MYSQL_PASSWORD": "123456", 216 | "MYSQL_DATABASE": "a_llm", 217 | "MYSQL_ROLE": "admin" # Optional, default is 'readonly'. Available values: readonly, writer, admin 218 | 219 | } 220 | } 221 | } 222 | } 223 | ``` 224 | 225 | ## Custom Tool Extensions 226 | 1. Add a new tool class in the handles package, inherit from BaseHandler, and implement get_tool_description and run_tool methods 227 | 228 | 2. Import the new tool in __init__.py to make it available in the server 229 | 230 | ## Examples 231 | 1. Create a new table and insert data, prompt format as follows: 232 | ``` 233 | # Task 234 | Create an organizational structure table with the following structure: department name, department number, parent department, is valid. 235 | # Requirements 236 | - Table name: department 237 | - Common fields need indexes 238 | - Each field needs comments, table needs comment 239 | - Generate 5 real data records after creation 240 | ``` 241 | ![image](https://github.com/user-attachments/assets/34118993-2a4c-4804-92f8-7fba9df89190) 242 | ![image](https://github.com/user-attachments/assets/f8299f01-c321-4dbf-b5de-13ba06885cc1) 243 | 244 | 245 | 2. Query data based on table comments, prompt as follows: 246 | ``` 247 | Search for data with Department name 'Executive Office' in Department organizational structure table 248 | ``` 249 | ![image](https://github.com/user-attachments/assets/dcf96603-548e-42d9-9217-78e569be7a8d) 250 | 251 | 252 | 3. Analyze slow SQL, prompt as follows: 253 | ``` 254 | select * from t_jcsjzx_hjkq_cd_xsz_sk xsz 255 | left join t_jcsjzx_hjkq_jcd jcd on jcd.cddm = xsz.cddm 256 | Based on current index situation, review execution plan and provide optimization suggestions in markdown format, including table index status, execution details, and optimization recommendations 257 | ``` 258 | 259 | 4. Analyze SQL deadlock issues, prompt as follows: 260 | ``` 261 | update t_admin_rms_zzjg set sfyx = '0' where xh = '1' is stuck, please analyze the cause 262 | ``` 263 | ![image](https://github.com/user-attachments/assets/25bca1cd-854c-4591-ac6e-32d464b12066) 264 | 265 | 266 | 5. Analyze the health status prompt as follows 267 | ``` 268 | Check the current health status of MySQL 269 | ``` 270 | ![image](https://github.com/user-attachments/assets/1f221ab8-59bf-402c-a15a-ec3eba1eea59) 271 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | mysql-mcp-server: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | container_name: mysql-mcp-server 9 | ports: 10 | - "9000:9000" # 如果你的应用监听 9000 端口,请根据实际情况调整 11 | environment: 12 | MYSQL_HOST: ${MYSQL_HOST:-localhost} 13 | MYSQL_PORT: ${MYSQL_PORT:-3306} 14 | MYSQL_USER: ${MYSQL_USER:-root} 15 | MYSQL_PASSWORD: ${MYSQL_PASSWORD:-123456} 16 | MYSQL_DATABASE: ${MYSQL_DATABASE:-a_llm} 17 | MYSQL_ROLE: ${MYSQL_ROLE:-admin} 18 | PYTHONPATH: /app/src 19 | restart: unless-stopped 20 | command: ["python", "-m", "mysql_mcp_server_pro.server"] -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "mysql_mcp_server_pro" 3 | version = "0.0.7" 4 | license = { text = "MIT" } 5 | description = "Support for SSE, STDIO in MySQL MCP server, includes CRUD, database anomaly analysis capabilities .支持SSE,STDIO;不仅止于mysql的增删改查功能; 还包含了数据库异常分析能力;且便于开发者们进行个性化的工具扩展" 6 | readme = "README.md" 7 | requires-python = ">=3.11" 8 | dependencies = [ 9 | "mcp>=1.8.0", 10 | "mysql-connector-python>=9.2.0", 11 | "pypinyin>=0.54.0", 12 | "python-dotenv>=1.1.0", 13 | "starlette>=0.46.1", 14 | "uvicorn>=0.34.0", 15 | ] 16 | 17 | [[project.authors]] 18 | name = "wenb1n" 19 | 20 | [build-system] 21 | requires = ["hatchling"] 22 | build-backend = "hatchling.build" 23 | 24 | [project.scripts] 25 | mysql_mcp_server_pro = "mysql_mcp_server_pro.cli:stdio_entry" 26 | mysql_mcp_server_sse = "mysql_mcp_server_pro.cli:sse_entry" 27 | mysql_mcp_server_streamable_http = "mysql_mcp_server_pro.cli:streamable_http_entry" 28 | 29 | [tool.hatch.build] 30 | packages = ["src"] 31 | 32 | [tool.hatch.build.targets.wheel] 33 | packages = ["src/mysql_mcp_server_pro"] 34 | 35 | [project.urls] 36 | Homepage = "https://github.com/wenb1n-dev/mysql_mcp_server_pro" 37 | Documentation = "https://github.com/wenb1n-dev/mysql_mcp_server_pro/blob/main/README.md" 38 | Repository = "https://github.com/wenb1n-dev/mysql_mcp_server_pro.git" 39 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | mcp>=1.8.0 2 | mysql-connector-python>=9.1.0 3 | pypinyin>=0.48.0 4 | uvicorn>=0.24.0 5 | python-dotenv>=1.0.0 6 | starlette>=0.27.0 -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenb1n-dev/mysql_mcp_server_pro/bcae4cf14f0b56d66bfd0de6ab1d6f3f470220da/src/mysql_mcp_server_pro/__init__.py -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/cli.py: -------------------------------------------------------------------------------- 1 | from mysql_mcp_server_pro.server import main 2 | 3 | def stdio_entry(): 4 | """stdio 模式入口点""" 5 | main(mode="stdio") 6 | 7 | def sse_entry(): 8 | """SSE 模式入口点""" 9 | main(mode="sse") 10 | 11 | def streamable_http_entry(): 12 | """Streamable Http 入口点""" 13 | main(mode="streamable_http") -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/config/.env: -------------------------------------------------------------------------------- 1 | # MySQL数据库配置 2 | MYSQL_HOST=localhost 3 | MYSQL_PORT=3306 4 | MYSQL_USER=root 5 | MYSQL_PASSWORD=123456 6 | MYSQL_DATABASE=a_llm 7 | MYSQL_ROLE=admin 8 | -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/config/__init__.py: -------------------------------------------------------------------------------- 1 | from .dbconfig import get_db_config,get_role_permissions 2 | 3 | __all__ = [ 4 | "get_db_config", 5 | "get_role_permissions", 6 | ] -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/config/dbconfig.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | 4 | def get_db_config(): 5 | """从环境变量获取数据库配置信息 6 | 7 | 返回: 8 | dict: 包含数据库连接所需的配置信息 9 | - host: 数据库主机地址 10 | - port: 数据库端口 11 | - user: 数据库用户名 12 | - password: 数据库密码 13 | - database: 数据库名称 14 | - role: 数据库角色权限 15 | 16 | 异常: 17 | ValueError: 当必需的配置信息缺失时抛出 18 | """ 19 | # 加载.env文件 20 | load_dotenv() 21 | 22 | config = { 23 | "host": os.getenv("MYSQL_HOST", "localhost"), 24 | "port": int(os.getenv("MYSQL_PORT", "3306")), 25 | "user": os.getenv("MYSQL_USER"), 26 | "password": os.getenv("MYSQL_PASSWORD"), 27 | "database": os.getenv("MYSQL_DATABASE"), 28 | "role": os.getenv("MYSQL_ROLE", "readonly") # 默认为只读角色 29 | } 30 | 31 | if not all([config["user"], config["password"], config["database"]]): 32 | raise ValueError("缺少必需的数据库配置") 33 | 34 | return config 35 | 36 | # 定义角色权限 37 | ROLE_PERMISSIONS = { 38 | "readonly": ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"], # 只读权限 39 | "writer": ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN", "INSERT", "UPDATE", "DELETE"], # 读写权限 40 | "admin": ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN", "INSERT", "UPDATE", "DELETE", 41 | "CREATE", "ALTER", "DROP", "TRUNCATE"] # 管理员权限 42 | } 43 | 44 | def get_role_permissions(role: str) -> list: 45 | """获取指定角色的权限列表 46 | 47 | 参数: 48 | role (str): 角色名称 49 | 50 | 返回: 51 | list: 该角色允许执行的SQL操作列表 52 | """ 53 | return ROLE_PERMISSIONS.get(role, ROLE_PERMISSIONS["readonly"]) # 默认返回只读权限 -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/config/event_store.py: -------------------------------------------------------------------------------- 1 | """ 2 | In-memory event store for demonstrating resumability functionality. 3 | 4 | This is a simple implementation intended for examples and testing, 5 | not for production use where a persistent storage solution would be more appropriate. 6 | """ 7 | 8 | import logging 9 | from collections import deque 10 | from dataclasses import dataclass 11 | from uuid import uuid4 12 | 13 | from mcp.server.streamable_http import ( 14 | EventCallback, 15 | EventId, 16 | EventMessage, 17 | EventStore, 18 | StreamId, 19 | ) 20 | from mcp.types import JSONRPCMessage 21 | 22 | logger = logging.getLogger(__name__) 23 | 24 | 25 | @dataclass 26 | class EventEntry: 27 | """ 28 | Represents an event entry in the event store. 29 | """ 30 | 31 | event_id: EventId 32 | stream_id: StreamId 33 | message: JSONRPCMessage 34 | 35 | 36 | class InMemoryEventStore(EventStore): 37 | """ 38 | Simple in-memory implementation of the EventStore interface for resumability. 39 | This is primarily intended for examples and testing, not for production use 40 | where a persistent storage solution would be more appropriate. 41 | 42 | This implementation keeps only the last N events per stream for memory efficiency. 43 | """ 44 | 45 | def __init__(self, max_events_per_stream: int = 100): 46 | """Initialize the event store. 47 | 48 | Args: 49 | max_events_per_stream: Maximum number of events to keep per stream 50 | """ 51 | self.max_events_per_stream = max_events_per_stream 52 | # for maintaining last N events per stream 53 | self.streams: dict[StreamId, deque[EventEntry]] = {} 54 | # event_id -> EventEntry for quick lookup 55 | self.event_index: dict[EventId, EventEntry] = {} 56 | 57 | async def store_event( 58 | self, stream_id: StreamId, message: JSONRPCMessage 59 | ) -> EventId: 60 | """Stores an event with a generated event ID.""" 61 | event_id = str(uuid4()) 62 | event_entry = EventEntry( 63 | event_id=event_id, stream_id=stream_id, message=message 64 | ) 65 | 66 | # Get or create deque for this stream 67 | if stream_id not in self.streams: 68 | self.streams[stream_id] = deque(maxlen=self.max_events_per_stream) 69 | 70 | # If deque is full, the oldest event will be automatically removed 71 | # We need to remove it from the event_index as well 72 | if len(self.streams[stream_id]) == self.max_events_per_stream: 73 | oldest_event = self.streams[stream_id][0] 74 | self.event_index.pop(oldest_event.event_id, None) 75 | 76 | # Add new event 77 | self.streams[stream_id].append(event_entry) 78 | self.event_index[event_id] = event_entry 79 | 80 | return event_id 81 | 82 | async def replay_events_after( 83 | self, 84 | last_event_id: EventId, 85 | send_callback: EventCallback, 86 | ) -> StreamId | None: 87 | """Replays events that occurred after the specified event ID.""" 88 | if last_event_id not in self.event_index: 89 | logger.warning(f"Event ID {last_event_id} not found in store") 90 | return None 91 | 92 | # Get the stream and find events after the last one 93 | last_event = self.event_index[last_event_id] 94 | stream_id = last_event.stream_id 95 | stream_events = self.streams.get(last_event.stream_id, deque()) 96 | 97 | # Events in deque are already in chronological order 98 | found_last = False 99 | for event in stream_events: 100 | if found_last: 101 | await send_callback(EventMessage(event.message, event.event_id)) 102 | elif event.event_id == last_event_id: 103 | found_last = True 104 | 105 | return stream_id 106 | -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/handles/__init__.py: -------------------------------------------------------------------------------- 1 | from .execute_sql import ExecuteSQL 2 | from .get_chinese_initials import GetChineseInitials 3 | from .get_table_desc import GetTableDesc 4 | from .get_table_index import GetTableIndex 5 | from .get_table_lock import GetTableLock 6 | from .get_table_name import GetTableName 7 | from .get_db_health_running import GetDBHealthRunning 8 | from .get_db_health_index_usage import GetDBHealthIndexUsage 9 | 10 | __all__ = [ 11 | "ExecuteSQL", 12 | "GetChineseInitials", 13 | "GetTableDesc", 14 | "GetTableIndex", 15 | "GetTableLock", 16 | "GetTableName", 17 | "GetDBHealthRunning", 18 | "GetDBHealthIndexUsage" 19 | ] -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/handles/base.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, Sequence, Type, ClassVar 2 | 3 | from mcp.types import TextContent, Tool 4 | 5 | 6 | class ToolRegistry: 7 | """工具注册表,用于管理所有工具实例""" 8 | _tools: ClassVar[Dict[str, 'BaseHandler']] = {} 9 | 10 | @classmethod 11 | def register(cls, tool_class: Type['BaseHandler']) -> Type['BaseHandler']: 12 | """注册工具类 13 | 14 | Args: 15 | tool_class: 要注册的工具类 16 | 17 | Returns: 18 | 返回注册的工具类,方便作为装饰器使用 19 | """ 20 | tool = tool_class() 21 | cls._tools[tool.name] = tool 22 | return tool_class 23 | 24 | @classmethod 25 | def get_tool(cls, name: str) -> 'BaseHandler': 26 | """获取工具实例 27 | 28 | Args: 29 | name: 工具名称 30 | 31 | Returns: 32 | 工具实例 33 | 34 | Raises: 35 | ValueError: 当工具不存在时抛出 36 | """ 37 | if name not in cls._tools: 38 | raise ValueError(f"未知的工具: {name}") 39 | return cls._tools[name] 40 | 41 | @classmethod 42 | def get_all_tools(cls) -> list[Tool]: 43 | """获取所有工具的描述 44 | 45 | Returns: 46 | 所有工具的描述列表 47 | """ 48 | return [tool.get_tool_description() for tool in cls._tools.values()] 49 | 50 | 51 | class BaseHandler: 52 | """工具基类""" 53 | name: str = "" 54 | description: str = "" 55 | 56 | def __init_subclass__(cls, **kwargs): 57 | """子类初始化时自动注册到工具注册表""" 58 | super().__init_subclass__(**kwargs) 59 | if cls.name: # 只注册有名称的工具 60 | ToolRegistry.register(cls) 61 | 62 | def get_tool_description(self) -> Tool: 63 | raise NotImplementedError 64 | 65 | async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 66 | raise NotImplementedError 67 | 68 | -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/handles/execute_sql.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, Sequence 2 | 3 | from mcp import Tool 4 | from mcp.types import TextContent 5 | from mysql.connector import connect, Error 6 | 7 | from .base import BaseHandler 8 | from ..config import get_db_config, get_role_permissions 9 | 10 | 11 | class ExecuteSQL(BaseHandler): 12 | name = "execute_sql" 13 | description = ( 14 | "在MySQL数据库上执行SQL (multiple SQL execution, separated by ';')" 15 | ) 16 | 17 | def get_tool_description(self) -> Tool: 18 | return Tool( 19 | name=self.name, 20 | description=self.description, 21 | inputSchema={ 22 | "type": "object", 23 | "properties": { 24 | "query": { 25 | "type": "string", 26 | "description": "要执行的SQL语句" 27 | } 28 | }, 29 | "required": ["query"] 30 | } 31 | ) 32 | 33 | def check_sql_permission(self, sql: str, allowed_operations: list) -> bool: 34 | """检查SQL语句是否有执行权限 35 | 36 | 参数: 37 | sql (str): SQL语句 38 | allowed_operations (list): 允许的操作列表 39 | 40 | 返回: 41 | bool: 是否有权限执行 42 | """ 43 | # 提取SQL语句的操作类型 44 | operation = sql.strip().split()[0].upper() 45 | return operation in allowed_operations 46 | 47 | async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 48 | """执行SQL查询语句 49 | 50 | 参数: 51 | query (str): 要执行的SQL语句,支持多条语句以分号分隔 52 | 53 | 返回: 54 | list[TextContent]: 包含查询结果的TextContent列表 55 | - 对于SELECT查询:返回CSV格式的结果,包含列名和数据 56 | - 对于SHOW TABLES:返回数据库中的所有表名 57 | - 对于其他查询:返回执行状态和影响行数 58 | - 多条语句的结果以"---"分隔 59 | 60 | 异常: 61 | Error: 当数据库连接或查询执行失败时抛出 62 | """ 63 | config = get_db_config() 64 | try: 65 | if "query" not in arguments: 66 | raise ValueError("缺少查询语句") 67 | 68 | query = arguments["query"] 69 | 70 | # 获取角色权限 71 | allowed_operations = get_role_permissions(config["role"]) 72 | 73 | with connect(**{k: v for k, v in config.items() if k != "role"}) as conn: 74 | with conn.cursor() as cursor: 75 | statements = [stmt.strip() for stmt in query.split(';') if stmt.strip()] 76 | results = [] 77 | 78 | for statement in statements: 79 | try: 80 | # 检查权限 81 | if not self.check_sql_permission(statement, allowed_operations): 82 | results.append(f"权限不足: 当前角色 '{config['role']}' 无权执行该SQL操作") 83 | continue 84 | 85 | cursor.execute(statement) 86 | 87 | # 检查语句是否返回了结果集 (SELECT, SHOW, EXPLAIN, etc.) 88 | if cursor.description: 89 | columns = [desc[0] for desc in cursor.description] 90 | rows = cursor.fetchall() 91 | 92 | # 将每一行的数据转换为字符串,特殊处理None值 93 | formatted_rows = [] 94 | for row in rows: 95 | formatted_row = ["NULL" if value is None else str(value) for value in row] 96 | formatted_rows.append(",".join(formatted_row)) 97 | 98 | # 将列名和数据合并为CSV格式 99 | results.append("\n".join([",".join(columns)] + formatted_rows)) 100 | 101 | # 如果语句没有返回结果集 (INSERT, UPDATE, DELETE, etc.) 102 | else: 103 | conn.commit() # 只有在非查询语句时才提交 104 | results.append(f"查询执行成功。影响行数: {cursor.rowcount}") 105 | 106 | except Error as stmt_error: 107 | # 单条语句执行出错时,记录错误并继续执行 108 | results.append(f"执行语句 '{statement}' 出错: {str(stmt_error)}") 109 | # 可以在这里选择是否继续执行后续语句,目前是继续 110 | 111 | return [TextContent(type="text", text="\n---\n".join(results))] 112 | 113 | except Error as e: 114 | return [TextContent(type="text", text=f"执行查询时出错: {str(e)}")] 115 | 116 | -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/handles/get_chinese_initials.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, Sequence 2 | 3 | from mcp import Tool 4 | from mcp.types import TextContent 5 | from pypinyin import pinyin, Style 6 | from .base import BaseHandler 7 | 8 | 9 | class GetChineseInitials(BaseHandler): 10 | name = "get_chinese_initials" 11 | description = ( 12 | "创建表结构时,将中文字段名转换为拼音首字母字段" 13 | ) 14 | 15 | def get_tool_description(self) -> Tool: 16 | return Tool( 17 | name=self.name, 18 | description=self.description, 19 | inputSchema={ 20 | "type": "object", 21 | "properties": { 22 | "text": { 23 | "type": "string", 24 | "description": "要获取拼音首字母的汉字文本,以“,”分隔" 25 | } 26 | }, 27 | "required": ["text"] 28 | } 29 | ) 30 | 31 | async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 32 | """将中文文本转换为拼音首字母 33 | 34 | 参数: 35 | text (str): 要转换的中文文本,以中文逗号分隔 36 | 37 | 返回: 38 | list[TextContent]: 包含转换结果的TextContent列表 39 | - 每个词的首字母会被转换为大写 40 | - 多个词的结果以英文逗号连接 41 | 42 | 示例: 43 | get_chinese_initials("用户名,密码") 44 | [TextContent(type="text", text="YHM,MM")] 45 | """ 46 | try: 47 | if "text" not in arguments: 48 | raise ValueError("缺少查询语句") 49 | 50 | text = arguments["text"] 51 | 52 | # 将文本按逗号分割 53 | words = text.split(',') 54 | 55 | # 存储每个词的首字母 56 | initials = [] 57 | 58 | for word in words: 59 | # 获取每个字的拼音首字母 60 | word_pinyin = pinyin(word, style=Style.FIRST_LETTER) 61 | # 将每个字的首字母连接起来 62 | word_initials = ''.join([p[0].upper() for p in word_pinyin]) 63 | initials.append(word_initials) 64 | 65 | # 用逗号连接所有结果 66 | return [TextContent(type="text", text=','.join(initials))] 67 | 68 | except Exception as e: 69 | return [TextContent(type="text", text=f"执行查询时出错: {str(e)}")] -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/handles/get_db_health_index_usage.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, Sequence 2 | 3 | from mcp import Tool 4 | from mcp.types import TextContent 5 | 6 | from .base import BaseHandler 7 | from mysql_mcp_server_pro.config import get_db_config 8 | from mysql_mcp_server_pro.handles import ( 9 | ExecuteSQL 10 | ) 11 | 12 | execute_sql = ExecuteSQL() 13 | 14 | config = get_db_config() 15 | 16 | class GetDBHealthIndexUsage(BaseHandler): 17 | name = "get_db_health_index_usage" 18 | description = ( 19 | "获取当前连接的mysql库的索引使用情况,包含冗余索引情况、性能较差的索引情况、未使用索引且查询时间大于30秒top5情况" 20 | + "(Get the index usage of the currently connected mysql database, including redundant index situations, " 21 | + "poorly performing index situations, and the top 5 unused index situations with query times greater than 30 seconds)" 22 | ) 23 | 24 | def get_tool_description(self) -> Tool: 25 | return Tool( 26 | name=self.name, 27 | description=self.description, 28 | inputSchema={ 29 | "type": "object", 30 | "properties": { 31 | 32 | } 33 | } 34 | ) 35 | 36 | async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 37 | count_zero_result = await self.get_count_zero(arguments) 38 | max_time_result = await self.get_max_timer(arguments) 39 | not_used_index_result = await self.get_not_used_index(arguments) 40 | 41 | 42 | # 合并结果 43 | combined_result = [] 44 | combined_result.extend(count_zero_result) 45 | combined_result.extend(max_time_result) 46 | combined_result.extend(not_used_index_result) 47 | 48 | 49 | return combined_result 50 | 51 | """ 52 | 获取冗余索引情况 53 | """ 54 | async def get_count_zero(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 55 | try: 56 | sql = "SELECT object_name,index_name,count_star from performance_schema.table_io_waits_summary_by_index_usage " 57 | sql += f"WHERE object_schema = '{config['database']}' and count_star = 0 AND sum_timer_wait = 0 ;" 58 | 59 | return await execute_sql.run_tool({"query": sql}) 60 | except Exception as e: 61 | return [TextContent(type="text", text=f"执行查询时出错: {str(e)}")] 62 | 63 | 64 | """ 65 | 获取性能较差的索引情况 66 | """ 67 | async def get_max_timer(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 68 | try: 69 | sql = "SELECT object_schema,object_name,index_name,(max_timer_wait / 1000000000000) max_timer_wait " 70 | sql += f"FROM performance_schema.table_io_waits_summary_by_index_usage where object_schema = '{config['database']}' " 71 | sql += "and index_name is not null ORDER BY max_timer_wait DESC;" 72 | 73 | return await execute_sql.run_tool({"query": sql}) 74 | except Exception as e: 75 | return [TextContent(type="text", text=f"执行查询时出错: {str(e)}")] 76 | 77 | """ 78 | 获取未使用索引查询时间大于30秒的top5情况 79 | """ 80 | async def get_not_used_index(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 81 | try: 82 | sql = "SELECT object_schema,object_name, (max_timer_wait / 1000000000000) max_timer_wait " 83 | sql += f"FROM performance_schema.table_io_waits_summary_by_index_usage where object_schema = '{config['database']}' " 84 | sql += "and index_name IS null and max_timer_wait > 30000000000000 ORDER BY max_timer_wait DESC limit 5;" 85 | 86 | return await execute_sql.run_tool({"query": sql}) 87 | except Exception as e: 88 | return [TextContent(type="text", text=f"执行查询时出错: {str(e)}")] -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/handles/get_db_health_running.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, Sequence 2 | 3 | from mcp import Tool 4 | from mcp.types import TextContent 5 | 6 | from .base import BaseHandler 7 | 8 | from mysql_mcp_server_pro.handles import ( 9 | ExecuteSQL 10 | ) 11 | 12 | execute_sql = ExecuteSQL() 13 | 14 | class GetDBHealthRunning(BaseHandler): 15 | name = "get_db_health_running" 16 | description = ( 17 | "获取当前mysql的健康状态(Analyze MySQL health status )" 18 | ) 19 | 20 | def get_tool_description(self) -> Tool: 21 | return Tool( 22 | name=self.name, 23 | description=self.description, 24 | inputSchema={ 25 | "type": "object", 26 | "properties": { 27 | 28 | } 29 | } 30 | ) 31 | 32 | async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 33 | lock_result = await self.get_lock(arguments) 34 | processlist_result = await self.get_processlist(arguments) 35 | status_result = await self.get_status(arguments) 36 | trx_result = await self.get_trx(arguments) 37 | 38 | 39 | # 合并结果 40 | combined_result = [] 41 | combined_result.extend(processlist_result) 42 | combined_result.extend(lock_result) 43 | combined_result.extend(trx_result) 44 | combined_result.extend(status_result) 45 | 46 | return combined_result 47 | 48 | """ 49 | 获取连接情况 50 | """ 51 | async def get_processlist(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 52 | try: 53 | sql = "SHOW FULL PROCESSLIST;SHOW VARIABLES LIKE 'max_connections';" 54 | 55 | return await execute_sql.run_tool({"query": sql}) 56 | except Exception as e: 57 | return [TextContent(type="text", text=f"执行查询时出错: {str(e)}")] 58 | 59 | """ 60 | 获取运行情况 61 | """ 62 | async def get_status(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 63 | try: 64 | sql = "SHOW ENGINE INNODB STATUS;" 65 | 66 | return await execute_sql.run_tool({"query": sql}) 67 | except Exception as e: 68 | return [TextContent(type="text", text=f"执行查询时出错: {str(e)}")] 69 | 70 | """ 71 | 获取事务情况 72 | """ 73 | async def get_trx(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 74 | try: 75 | sql = "SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;" 76 | return await execute_sql.run_tool({"query": sql}) 77 | except Exception as e: 78 | return [TextContent(type="text", text=f"执行查询时出错: {str(e)}")] 79 | 80 | 81 | """ 82 | 获取锁情况 83 | """ 84 | async def get_lock(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 85 | try: 86 | sql = "SHOW OPEN TABLES WHERE In_use > 0;select * from information_schema.innodb_locks;select * from information_schema.innodb_lock_waits;" 87 | sql += "select * from performance_schema.data_lock_waits;" 88 | sql += "select * from performance_schema.data_locks;" 89 | 90 | 91 | return await execute_sql.run_tool({"query": sql}) 92 | except Exception as e: 93 | return [TextContent(type="text", text=f"执行查询时出错: {str(e)}")] -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/handles/get_table_desc.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Sequence, Any 2 | 3 | from mcp import Tool 4 | from mcp.types import TextContent 5 | 6 | from .base import BaseHandler 7 | from mysql_mcp_server_pro.config import get_db_config 8 | from mysql_mcp_server_pro.handles import ( 9 | ExecuteSQL 10 | ) 11 | 12 | 13 | class GetTableDesc(BaseHandler): 14 | name = "get_table_desc" 15 | description = ( 16 | "根据表名搜索数据库中对应的表字段,支持多表查询(Search for table structures in the database based on table names, supporting multi-table queries)" 17 | ) 18 | 19 | def get_tool_description(self) -> Tool: 20 | return Tool( 21 | name=self.name, 22 | description=self.description, 23 | inputSchema={ 24 | "type": "object", 25 | "properties": { 26 | "text": { 27 | "type": "string", 28 | "description": "要搜索的表名" 29 | } 30 | }, 31 | "required": ["text"] 32 | } 33 | ) 34 | 35 | async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 36 | """获取指定表的字段结构信息 37 | 38 | 参数: 39 | text (str): 要查询的表名,多个表名以逗号分隔 40 | 41 | 返回: 42 | list[TextContent]: 包含查询结果的TextContent列表 43 | - 返回表的字段名、字段注释等信息 44 | - 结果按表名和字段顺序排序 45 | - 结果以CSV格式返回,包含列名和数据 46 | """ 47 | try: 48 | if "text" not in arguments: 49 | raise ValueError("缺少查询语句") 50 | 51 | text = arguments["text"] 52 | 53 | config = get_db_config() 54 | execute_sql = ExecuteSQL() 55 | 56 | # 将输入的表名按逗号分割成列表 57 | table_names = [name.strip() for name in text.split(',')] 58 | # 构建IN条件 59 | table_condition = "','".join(table_names) 60 | sql = "SELECT TABLE_NAME, COLUMN_NAME, COLUMN_COMMENT " 61 | sql += f"FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '{config['database']}' " 62 | sql += f"AND TABLE_NAME IN ('{table_condition}') ORDER BY TABLE_NAME, ORDINAL_POSITION;" 63 | return await execute_sql.run_tool({"query":sql}) 64 | 65 | except Exception as e: 66 | return [TextContent(type="text", text=f"执行查询时出错: {str(e)}")] -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/handles/get_table_index.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, Sequence 2 | 3 | from mcp import Tool 4 | from mcp.types import TextContent 5 | 6 | from .base import BaseHandler 7 | from mysql_mcp_server_pro.config import get_db_config 8 | from mysql_mcp_server_pro.handles import ( 9 | ExecuteSQL 10 | ) 11 | 12 | class GetTableIndex(BaseHandler): 13 | name = "get_table_index" 14 | description = ( 15 | "根据表名搜索数据库中对应的表索引,支持多表查询(Search for table indexes in the database based on table names, supporting multi-table queries)" 16 | ) 17 | 18 | def get_tool_description(self) -> Tool: 19 | return Tool( 20 | name=self.name, 21 | description=self.description, 22 | inputSchema={ 23 | "type": "object", 24 | "properties": { 25 | "text": { 26 | "type": "string", 27 | "description": "要搜索的表名" 28 | } 29 | }, 30 | "required": ["text"] 31 | } 32 | ) 33 | 34 | async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 35 | """获取指定表的索引信息 36 | 37 | 参数: 38 | text (str): 要查询的表名,多个表名以逗号分隔 39 | 40 | 返回: 41 | list[TextContent]: 包含查询结果的TextContent列表 42 | - 返回表的索引名、索引字段、索引类型等信息 43 | - 结果按表名、索引名和索引顺序排序 44 | - 结果以CSV格式返回,包含列名和数据 45 | """ 46 | try: 47 | if "text" not in arguments: 48 | raise ValueError("缺少查询语句") 49 | 50 | text = arguments["text"] 51 | 52 | config = get_db_config() 53 | execute_sql = ExecuteSQL() 54 | 55 | # 将输入的表名按逗号分割成列表 56 | table_names = [name.strip() for name in text.split(',')] 57 | # 构建IN条件 58 | table_condition = "','".join(table_names) 59 | 60 | sql = "SELECT TABLE_NAME, INDEX_NAME, COLUMN_NAME, SEQ_IN_INDEX, NON_UNIQUE, INDEX_TYPE " 61 | sql += f"FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = '{config['database']}' " 62 | sql += f"AND TABLE_NAME IN ('{table_condition}') ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;" 63 | 64 | return await execute_sql.run_tool({"query": sql}) 65 | 66 | except Exception as e: 67 | return [TextContent(type="text", text=f"执行查询时出错: {str(e)}")] -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/handles/get_table_lock.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, Sequence 2 | 3 | from mcp import Tool 4 | from mcp.types import TextContent 5 | 6 | from .base import BaseHandler 7 | 8 | from mysql_mcp_server_pro.handles import ( 9 | ExecuteSQL 10 | ) 11 | 12 | execute_sql = ExecuteSQL() 13 | 14 | class GetTableLock(BaseHandler): 15 | name = "get_table_lock" 16 | description = ( 17 | "获取当前mysql服务器行级锁、表级锁情况(Check if there are row-level locks or table-level locks in the current MySQL server )" 18 | ) 19 | 20 | def get_tool_description(self) -> Tool: 21 | return Tool( 22 | name=self.name, 23 | description=self.description, 24 | inputSchema={ 25 | "type": "object", 26 | "properties": { 27 | 28 | } 29 | } 30 | ) 31 | 32 | async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 33 | use_result = await self.get_table_use(arguments) 34 | lock_result_5 = await self.get_table_lock_for_mysql5(arguments) 35 | lock_result_8 = await self.get_table_lock_for_mysql8(arguments) 36 | 37 | # 合并两个结果 38 | combined_result = [] 39 | combined_result.extend(use_result) 40 | combined_result.extend(lock_result_5) 41 | combined_result.extend(lock_result_8) 42 | 43 | return combined_result 44 | 45 | """ 46 | 获取表级锁情况 47 | """ 48 | async def get_table_use(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 49 | try: 50 | sql = "SHOW OPEN TABLES WHERE In_use > 0;" 51 | 52 | return await execute_sql.run_tool({"query": sql}) 53 | except Exception as e: 54 | return [TextContent(type="text", text=f"执行查询时出错: {str(e)}")] 55 | 56 | """ 57 | 获取行级锁情况--mysql5.6 58 | """ 59 | async def get_table_lock_for_mysql5(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 60 | try: 61 | sql = "SELECT p2.`HOST` 被阻塞方host, p2.`USER` 被阻塞方用户, r.trx_id 被阻塞方事务id, " 62 | sql += "r.trx_mysql_thread_id 被阻塞方线程号,TIMESTAMPDIFF(SECOND, r.trx_wait_started, CURRENT_TIMESTAMP) 等待时间, " 63 | sql += "r.trx_query 被阻塞的查询, l.lock_table 阻塞方锁住的表, m.`lock_mode` 被阻塞方的锁模式, " 64 | sql += "m.`lock_type` '被阻塞方的锁类型(表锁还是行锁)', m.`lock_index` 被阻塞方锁住的索引, " 65 | sql += "m.`lock_space` 被阻塞方锁对象的space_id, m.lock_page 被阻塞方事务锁定页的数量, " 66 | sql += "m.lock_rec 被阻塞方事务锁定记录的数量, m.lock_data 被阻塞方事务锁定记录的主键值, " 67 | sql += "p.`HOST` 阻塞方主机, p.`USER` 阻塞方用户, b.trx_id 阻塞方事务id,b.trx_mysql_thread_id 阻塞方线程号, " 68 | sql += "b.trx_query 阻塞方查询, l.`lock_mode` 阻塞方的锁模式, l.`lock_type` '阻塞方的锁类型(表锁还是行锁)'," 69 | sql += "l.`lock_index` 阻塞方锁住的索引,l.`lock_space` 阻塞方锁对象的space_id,l.lock_page 阻塞方事务锁定页的数量," 70 | sql += "l.lock_rec 阻塞方事务锁定行的数量, l.lock_data 阻塞方事务锁定记录的主键值," 71 | sql += "IF(p.COMMAND = 'Sleep', CONCAT(p.TIME, ' 秒'), 0) 阻塞方事务空闲的时间 " 72 | sql += "FROM information_schema.INNODB_LOCK_WAITS w " 73 | sql += "INNER JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id " 74 | sql += "INNER JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id " 75 | sql += "INNER JOIN information_schema.INNODB_LOCKS l ON w.blocking_lock_id = l.lock_id AND l.`lock_trx_id` = b.`trx_id` " 76 | sql += "INNER JOIN information_schema.INNODB_LOCKS m ON m.`lock_id` = w.`requested_lock_id` AND m.`lock_trx_id` = r.`trx_id` " 77 | sql += "INNER JOIN information_schema.PROCESSLIST p ON p.ID = b.trx_mysql_thread_id " 78 | sql += "INNER JOIN information_schema.PROCESSLIST p2 ON p2.ID = r.trx_mysql_thread_id " 79 | sql += "ORDER BY 等待时间 DESC;" 80 | 81 | return await execute_sql.run_tool({"query": sql}) 82 | except Exception as e: 83 | return [TextContent(type="text", text=f"执行查询时出错: {str(e)}")] 84 | 85 | """ 86 | 获取行级锁情况--mysql8 87 | """ 88 | async def get_table_lock_for_mysql8(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 89 | try: 90 | sql = "SELECT p2.HOST AS '被阻塞方host',p2.USER AS '被阻塞方用户',r.trx_id AS '被阻塞方事务id', " 91 | sql += "r.trx_mysql_thread_id AS '被阻塞方线程号',TIMESTAMPDIFF(SECOND, r.trx_wait_started, CURRENT_TIMESTAMP) AS '等待时间'," 92 | sql += "r.trx_query AS '被阻塞的查询',dlr.OBJECT_SCHEMA AS '被阻塞方锁库',dlr.OBJECT_NAME AS '被阻塞方锁表'," 93 | sql += "dlr.LOCK_MODE AS '被阻塞方锁模式', dlr.LOCK_TYPE AS '被阻塞方锁类型',dlr.INDEX_NAME AS '被阻塞方锁住的索引'," 94 | sql += "dlr.LOCK_DATA AS '被阻塞方锁定记录的主键值',p.HOST AS '阻塞方主机',p.USER AS '阻塞方用户',b.trx_id AS '阻塞方事务id'," 95 | sql += "b.trx_mysql_thread_id AS '阻塞方线程号',b.trx_query AS '阻塞方查询',dlb.LOCK_MODE AS '阻塞方锁模式'," 96 | sql += "dlb.LOCK_TYPE AS '阻塞方锁类型',dlb.INDEX_NAME AS '阻塞方锁住的索引',dlb.LOCK_DATA AS '阻塞方锁定记录的主键值'," 97 | sql += "IF(p.COMMAND = 'Sleep', CONCAT(p.TIME, ' 秒'), 0) AS '阻塞方事务空闲的时间' " 98 | sql += "FROM performance_schema.data_lock_waits w " 99 | sql += "JOIN performance_schema.data_locks dlr ON w.REQUESTING_ENGINE_LOCK_ID = dlr.ENGINE_LOCK_ID " 100 | sql += "JOIN performance_schema.data_locks dlb ON w.BLOCKING_ENGINE_LOCK_ID = dlb.ENGINE_LOCK_ID " 101 | sql += "JOIN information_schema.innodb_trx r ON w.REQUESTING_ENGINE_TRANSACTION_ID = r.trx_id " 102 | sql += "JOIN information_schema.innodb_trx b ON w.BLOCKING_ENGINE_TRANSACTION_ID = b.trx_id " 103 | sql += "JOIN information_schema.processlist p ON b.trx_mysql_thread_id = p.ID " 104 | sql += "JOIN information_schema.processlist p2 ON r.trx_mysql_thread_id = p2.ID " 105 | sql += "ORDER BY '等待时间' DESC;" 106 | 107 | return await execute_sql.run_tool({"query": sql}) 108 | except Exception as e: 109 | return [TextContent(type="text", text=f"执行查询时出错: {str(e)}")] -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/handles/get_table_name.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, Sequence 2 | 3 | from mcp import Tool 4 | from mcp.types import TextContent 5 | 6 | from .base import BaseHandler 7 | from mysql_mcp_server_pro.config import get_db_config 8 | from mysql_mcp_server_pro.handles import ( 9 | ExecuteSQL 10 | ) 11 | 12 | 13 | class GetTableName(BaseHandler): 14 | 15 | name = "get_table_name" 16 | description = ( 17 | "根据表中文名或表描述搜索数据库中对应的表名(Search for table names in the database based on table comments and descriptions )" 18 | ) 19 | 20 | def get_tool_description(self) -> Tool: 21 | return Tool( 22 | name=self.name, 23 | description=self.description, 24 | inputSchema={ 25 | "type": "object", 26 | "properties": { 27 | "text": { 28 | "type": "string", 29 | "description": "要搜索的表中文名、表描述,仅支持单个查询" 30 | } 31 | }, 32 | "required": ["text"] 33 | } 34 | ) 35 | 36 | async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]: 37 | """根据表的注释搜索数据库中的表名 38 | 39 | 参数: 40 | text (str): 要搜索的表中文注释关键词 41 | 42 | 返回: 43 | list[TextContent]: 包含查询结果的TextContent列表 44 | - 返回匹配的表名、数据库名和表注释信息 45 | - 结果以CSV格式返回,包含列名和数据 46 | """ 47 | try: 48 | if "text" not in arguments: 49 | raise ValueError("缺少查询语句") 50 | 51 | text = arguments["text"] 52 | 53 | config = get_db_config() 54 | execute_sql = ExecuteSQL() 55 | 56 | sql = "SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_COMMENT " 57 | sql += f"FROM information_schema.TABLES WHERE TABLE_SCHEMA = '{config['database']}' AND TABLE_COMMENT LIKE '%{text}%';" 58 | return await execute_sql.run_tool({"query":sql}) 59 | 60 | except Exception as e: 61 | return [TextContent(type="text", text=f"执行查询时出错: {str(e)}")] -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/prompts/AnalysisMySqlIssues.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any 2 | 3 | from mcp import GetPromptResult 4 | from mcp.types import Prompt, TextContent, PromptMessage 5 | 6 | from mysql_mcp_server_pro.prompts.BasePrompt import BasePrompt 7 | 8 | 9 | class AnalysisMySqlIssues(BasePrompt): 10 | name = "analyzing-mysql-prompt" 11 | description = ( 12 | "这是分析mysql相关问题的提示词" 13 | ) 14 | 15 | def get_prompt(self) -> Prompt: 16 | return Prompt( 17 | name= self.name, 18 | description= self.description, 19 | arguments=[ 20 | ], 21 | ) 22 | 23 | async def run_prompt(self, arguments: Dict[str, Any]) -> GetPromptResult: 24 | 25 | prompt = """- Role: 数据库运维专家和MySQL问题诊断工程师" 26 | - Background: 用户在使用MySQL数据库时遇到了问题,需要对问题进行深入分析,以便快速定位并解决,确保数据库的稳定运行和数据的完整性。" 27 | - Profile: 你是一位资深的数据库运维专家,对MySQL数据库的架构、性能优化、故障排查有着丰富的实战经验,熟悉Linux操作系统和SQL语言,能够运用专业的工具和方法进行问题诊断。 28 | - Skills: 你具备数据库性能监控、日志分析、SQL语句优化、备份与恢复等关键能力,能够熟练使用MySQL的命令行工具、配置文件以及第三方监控工具。 29 | - Goals: 快速定位MySQL数据库问题的根源,提供有效的解决方案,恢复数据库的正常运行,保障数据安全。 30 | - Constrains: 在解决问题的过程中,应遵循最小干预原则,避免对现有数据造成不必要的影响,确保解决方案的可操作性和安全性。 31 | - OutputFormat: 以问题分析报告的形式输出,包括问题描述、分析过程、解决方案和预防措施。 32 | - Workflow: 33 | 1. 收集问题相关信息,包括错误日志、系统配置、SQL语句执行情况等。 34 | 2. 分析问题产生的可能原因,从数据库性能、配置错误、SQL语句逻辑等方面进行排查。 35 | 3. 提出针对性的解决方案,并进行测试验证,确保问题得到解决。 36 | - Examples: 37 | - 例子1:数据库连接失败 38 | 问题描述:用户无法连接到MySQL数据库,提示“无法连接到服务器”。 39 | 分析过程:检查MySQL服务是否正常运行,查看错误日志是否有相关记录,确认网络连接是否正常,检查数据库的用户权限和连接限制。 40 | 解决方案:启动MySQL服务,修复网络问题,调整用户权限,增加连接限制。 41 | 预防措施:定期检查MySQL服务状态,监控网络连接,合理配置用户权限。 42 | - 例子2:查询性能低下 43 | 问题描述:执行SQL查询语句时响应时间过长。 44 | 分析过程:查看执行计划,检查表的索引是否合理,分析SQL语句是否存在优化空间,检查数据库的缓存配置。 45 | 解决方案:优化SQL语句,创建合适的索引,调整缓存参数。 46 | 预防措施:定期分析和优化SQL语句,监控数据库性能,及时调整索引和缓存配置。 47 | - 例子3:数据丢失 48 | 问题描述:部分数据丢失,无法恢复。 49 | 分析过程:检查备份策略是否正常执行,查看二进制日志是否完整,分析是否有误操作导致数据丢失。 50 | 解决方案:从备份中恢复数据,利用二进制日志进行数据恢复,分析误操作并采取补救措施。 51 | 预防措施:制定完善的备份策略,定期检查备份文件的完整性和可用性,加强用户操作权限管理。 52 | - Initialization: 在第一次对话中,请直接输出以下:您好,作为数据库运维专家,我将协助您分析和解决MySQL数据库的问题。请详细描述您遇到的问题,包括错误信息、操作步骤等,以便我更好地进行分析。 53 | """ 54 | 55 | return GetPromptResult( 56 | description="mysql prompt", 57 | messages=[ 58 | PromptMessage( 59 | role="user", 60 | content=TextContent(type="text", text=prompt), 61 | ) 62 | ], 63 | ) -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/prompts/BasePrompt.py: -------------------------------------------------------------------------------- 1 | from typing import ClassVar, Dict, Type, Any 2 | 3 | from mcp import GetPromptResult 4 | from mcp.types import Prompt 5 | 6 | class PromptRegistry: 7 | """prompt注册表,用于管理所有prompt实例""" 8 | _prompts: ClassVar[Dict[str, 'BasePrompt']] = {} 9 | 10 | @classmethod 11 | def register(cls, prompt_class: Type['BasePrompt']) -> Type['BasePrompt']: 12 | """注册prompt类 13 | 14 | Args: 15 | prompt_class: 要注册的工具类 16 | 17 | Returns: 18 | 返回注册的prompt类,方便作为装饰器使用 19 | """ 20 | prompt = prompt_class() 21 | cls._prompts[prompt.name] = prompt 22 | return prompt_class 23 | 24 | @classmethod 25 | def get_prompt(cls, name: str) -> 'BasePrompt': 26 | """获取prompt实例 27 | 28 | Args: 29 | name: prompt名称 30 | 31 | Returns: 32 | prompt实例 33 | 34 | Raises: 35 | ValueError: 当prompt不存在时抛出 36 | """ 37 | if name not in cls._prompts: 38 | raise ValueError(f"未知的prompt: {name}") 39 | return cls._prompts[name] 40 | 41 | @classmethod 42 | def get_all__prompts(cls) -> list[Prompt]: 43 | """获取所有prompt的描述 44 | 45 | Returns: 46 | 所有prompt的描述列表 47 | """ 48 | return [prompt.get_prompt() for prompt in cls._prompts.values()] 49 | 50 | 51 | class BasePrompt: 52 | name:str = "" 53 | description:str = "" 54 | 55 | def __init_subclass__(cls, **kwargs): 56 | """子类初始化时自动注册到prompt注册表""" 57 | super().__init_subclass__(**kwargs) 58 | if cls.name: # 只注册有名称的prompt 59 | PromptRegistry.register(cls) 60 | 61 | def get_prompt(self) -> Prompt: 62 | raise NotImplementedError() 63 | 64 | async def run_prompt(self, arguments: Dict[str, Any]) -> GetPromptResult: 65 | raise NotImplementedError() 66 | -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/prompts/QueryTableData.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any 2 | 3 | from mcp import GetPromptResult 4 | from mcp.types import Prompt, TextContent, PromptMessage, PromptArgument 5 | 6 | from mysql_mcp_server_pro.prompts.BasePrompt import BasePrompt 7 | 8 | 9 | class QueryTableData(BasePrompt): 10 | name = "query-table-data-prompt" 11 | description = ( 12 | "这是通过调用工具查询表数据的提示词" 13 | ) 14 | 15 | def get_prompt(self) -> Prompt: 16 | return Prompt( 17 | name= self.name, 18 | description= self.description, 19 | arguments=[ 20 | PromptArgument( 21 | name="desc", description="请输入需要查询的内容,为空时大模型会初始化为数据库助手" 22 | ) 23 | ], 24 | ) 25 | 26 | async def run_prompt(self, arguments: Dict[str, Any]) -> GetPromptResult: 27 | 28 | prompt = """ 29 | - Role: 数据库应用开发专家和自然语言处理工程师 30 | - Background: 目前有一个操作MySQL数据库的MCP Server,该服务器具备根据表描述获取数据库表名和根据表名获取表结构的功能。用户希望实现一个自然语言交互功能,通过用户输入的自然语言指令,如“查询用户表张三的数据”,自动调用相关工具完成任务。 31 | - Profile: 你是一位数据库应用开发专家,同时具备自然语言处理的技能,能够理解用户输入的自然语言指令,并将其转化为数据库操作的具体步骤。你熟悉MySQL数据库的操作,以及如何通过编程接口调用相关工具。 32 | - Skills: 你具备自然语言解析、数据库查询、API调用、逻辑推理等关键能力,能够将用户的自然语言指令转化为具体的数据库操作流程。 33 | - Goals: 根据用户输入的自然语言指令,自动调用“get_table_name”和“get_table_desc”的工具,最终实现用户期望的数据库操作。 34 | - Constrains: 该提示词应确保用户输入的自然语言指令能够被准确解析,调用的工具能够正确执行,且整个过程应具备良好的用户体验和错误处理机制。 35 | - OutputFormat: 以自然语言交互的形式输出,包括用户输入的解析结果、调用工具的中间结果以及最终的数据库查询结果。 36 | - Workflow: 37 | 1. 解析用户输入的自然语言指令,提取关键信息,如表描述和查询条件。 38 | 2. 调用“get_table_name”工具,获取对应的表名。 39 | 3. 调用“get_table_desc”工具,获取表的结构信息。 40 | 4. 根据表结构信息和用户输入的查询条件,生成SQL查询语句并执行,返回查询结果。 41 | - Examples: 42 | - 例子1:用户输入“查询用户表张三的数据” 43 | 解析结果:表描述为“用户表”,查询条件为“张三”。 44 | 调用工具1:根据“用户表”描述获取表名,假设返回表名为“user_table”。 45 | 调用工具2:根据“user_table”获取表结构,假设表结构包含字段“id”、“name”、“age”。 46 | 生成SQL查询语句:`SELECT * FROM user_table WHERE name = '张三';` 47 | 查询结果:返回张三的相关数据。 48 | - 例子2:用户输入“查询商品表价格大于100的商品” 49 | 解析结果:表描述为“商品表”,查询条件为“价格大于100”。 50 | 调用工具1:根据“商品表”描述获取表名,假设返回表名为“product_table”。 51 | 调用工具2:根据“product_table”获取表结构,假设表结构包含字段“id”、“name”、“price”。 52 | 生成SQL查询语句:`SELECT * FROM product_table WHERE price > 100;` 53 | 查询结果:返回价格大于100的商品数据。 54 | - 例子3:用户输入“查询订单表张三的订单金额” 55 | 解析结果:表描述为“订单表”,查询条件为“张三”。 56 | 调用工具1:根据“订单表”描述获取表名,假设返回表名为“order_table”。 57 | 调用工具2:根据“order_table”获取表结构,假设表结构包含字段“id”、“user_name”、“order_amount”。 58 | 生成SQL查询语句:`SELECT order_amount FROM order_table WHERE user_name = '张三';` 59 | 查询结果:返回张三的订单金额。 60 | """ 61 | 62 | if "desc" not in arguments: 63 | prompt += """- Initialization: 在第一次对话中,请直接输出以下:您好,作为数据库应用开发专家,我将协助您实现自然语言交互功能。 64 | 请按照以下格式输入您的需求:“查询[表描述][查询条件]的数据”,例如“查询用户表张三的数据”,我将为您自动调用相关工具并返回查询结果。""" 65 | else: 66 | desc = arguments["desc"] 67 | prompt += f"- task: {desc}。 " 68 | 69 | return GetPromptResult( 70 | description="mysql prompt", 71 | messages=[ 72 | PromptMessage( 73 | role="user", 74 | content=TextContent(type="text", text=prompt), 75 | ) 76 | ], 77 | ) -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/prompts/__init__.py: -------------------------------------------------------------------------------- 1 | from .AnalysisMySqlIssues import AnalysisMySqlIssues 2 | from .QueryTableData import QueryTableData 3 | 4 | __all__ = [ 5 | "AnalysisMySqlIssues", 6 | "QueryTableData", 7 | ] -------------------------------------------------------------------------------- /src/mysql_mcp_server_pro/server.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import contextlib 3 | 4 | from collections.abc import AsyncIterator 5 | 6 | import uvicorn 7 | 8 | from typing import Sequence, Dict, Any 9 | from mcp.server.sse import SseServerTransport 10 | 11 | from mcp.server.lowlevel import Server 12 | from mcp.server.streamable_http_manager import StreamableHTTPSessionManager 13 | from mcp.types import Tool, TextContent, Prompt, GetPromptResult 14 | 15 | from starlette.applications import Starlette 16 | from starlette.routing import Route, Mount 17 | from starlette.types import Scope, Receive, Send 18 | 19 | from .config.event_store import InMemoryEventStore 20 | from .handles.base import ToolRegistry 21 | from .prompts.BasePrompt import PromptRegistry 22 | 23 | 24 | # 初始化服务器 25 | app = Server("operateMysql") 26 | 27 | @app.list_prompts() 28 | async def handle_list_prompts() -> list[Prompt]: 29 | """获取所有可用的提示模板列表 30 | 31 | 返回: 32 | list[Prompt]: 返回系统中注册的所有提示模板列表 33 | """ 34 | return PromptRegistry.get_all__prompts() 35 | 36 | 37 | @app.get_prompt() 38 | async def handle_get_prompt(name: str, arguments: Dict[str, Any] | None) -> GetPromptResult: 39 | """获取并执行指定的提示模板 40 | 41 | 参数: 42 | name (str): 提示模板的名称 43 | arguments (dict[str, str] | None): 提示模板所需的参数字典,可以为空 44 | 45 | 返回: 46 | GetPromptResult: 提示模板执行的结果 47 | 48 | 说明: 49 | 1. 根据提供的模板名称从注册表中获取对应的提示模板 50 | 2. 使用提供的参数执行该模板 51 | 3. 返回执行结果 52 | """ 53 | prompt = PromptRegistry.get_prompt(name) 54 | return await prompt.run_prompt(arguments) 55 | 56 | 57 | @app.list_tools() 58 | async def list_tools() -> list[Tool]: 59 | """ 60 | 列出所有可用的MySQL操作工具 61 | """ 62 | return ToolRegistry.get_all_tools() 63 | 64 | @app.call_tool() 65 | async def call_tool(name: str, arguments: Dict[str, Any]) -> Sequence[TextContent]: 66 | """调用指定的工具执行操作 67 | 68 | Args: 69 | name (str): 工具名称 70 | arguments (dict): 工具参数 71 | 72 | Returns: 73 | Sequence[TextContent]: 工具执行结果 74 | 75 | Raises: 76 | ValueError: 当指定了未知的工具名称时抛出异常 77 | """ 78 | tool = ToolRegistry.get_tool(name) 79 | 80 | return await tool.run_tool(arguments) 81 | 82 | 83 | async def run_stdio(): 84 | """运行标准输入输出模式的服务器 85 | 86 | 使用标准输入输出流(stdio)运行服务器,主要用于命令行交互模式 87 | 88 | Raises: 89 | Exception: 当服务器运行出错时抛出异常 90 | """ 91 | from mcp.server.stdio import stdio_server 92 | 93 | async with stdio_server() as (read_stream, write_stream): 94 | try: 95 | await app.run( 96 | read_stream, 97 | write_stream, 98 | app.create_initialization_options() 99 | ) 100 | except Exception as e: 101 | print(f"服务器错误: {str(e)}") 102 | raise 103 | 104 | def run_sse(): 105 | """运行SSE(Server-Sent Events)模式的服务器 106 | 107 | 启动一个支持SSE的Web服务器,允许客户端通过HTTP长连接接收服务器推送的消息 108 | 服务器默认监听0.0.0.0:9000 109 | """ 110 | sse = SseServerTransport("/messages/") 111 | 112 | async def handle_sse(request): 113 | """处理SSE连接请求 114 | 115 | Args: 116 | request: HTTP请求对象 117 | """ 118 | async with sse.connect_sse( 119 | request.scope, request.receive, request._send 120 | ) as streams: 121 | await app.run(streams[0], streams[1], app.create_initialization_options()) 122 | 123 | starlette_app = Starlette( 124 | debug=True, 125 | routes=[ 126 | Route("/sse", endpoint=handle_sse), 127 | Mount("/messages/", app=sse.handle_post_message) 128 | ], 129 | ) 130 | uvicorn.run(starlette_app, host="0.0.0.0", port=9000) 131 | 132 | def run_streamable_http(json_response: bool): 133 | event_store = InMemoryEventStore() 134 | 135 | session_manager = StreamableHTTPSessionManager( 136 | app=app, 137 | event_store=event_store, 138 | json_response=json_response, 139 | ) 140 | 141 | async def handle_streamable_http( 142 | scope: Scope, receive: Receive, send: Send 143 | ) -> None: 144 | await session_manager.handle_request(scope, receive, send) 145 | 146 | @contextlib.asynccontextmanager 147 | async def lifespan(app: Starlette) -> AsyncIterator[None]: 148 | async with session_manager.run(): 149 | yield 150 | 151 | 152 | starlette = Starlette( 153 | debug=True, 154 | routes=[ 155 | Mount("/mcp", app=handle_streamable_http) 156 | ], 157 | lifespan=lifespan, 158 | ) 159 | uvicorn.run(starlette, host="0.0.0.0", port=3000) 160 | 161 | 162 | def main(mode="streamable_http"): 163 | """ 164 | 主入口函数,用于命令行启动 165 | 支持三种模式: 166 | 1. SSE 模式:mysql-mcp-server 167 | 2. stdio 模式:mysql-mcp-server --stdio 168 | 3. streamable http 模式(默认) 169 | 170 | Args: 171 | mode (str): 运行模式,可选值为 "sse" 或 "stdio" 172 | """ 173 | import sys 174 | 175 | # 命令行参数优先级高于默认参数 176 | if len(sys.argv) > 1 and sys.argv[1] == "--stdio": 177 | # 标准输入输出模式 178 | asyncio.run(run_stdio()) 179 | elif len(sys.argv) > 1 and sys.argv[1] == "--sse": 180 | # SSE 模式 181 | run_sse() 182 | elif len(sys.argv) > 1 and sys.argv[1] == "--streamable_http": 183 | # Streamable Http 模式 184 | run_streamable_http(False) 185 | else: 186 | # 使用传入的默认模式 187 | if mode == "stdio": 188 | asyncio.run(run_stdio()) 189 | elif mode == "sse": 190 | run_sse() 191 | else: 192 | run_streamable_http(False) 193 | 194 | if __name__ == "__main__": 195 | main() 196 | --------------------------------------------------------------------------------