multimymcp 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
multimymcp/__init__.py ADDED
@@ -0,0 +1,24 @@
1
+ """
2
+ MultiMyMCP - 生产级 MySQL 多数据源 MCP 工具
3
+
4
+ 版本: 1.0.0
5
+ 作者: TraeMCP Team
6
+ 适配环境: TRAE CN IDE
7
+
8
+ 核心功能:
9
+ - 增强型连接池管理
10
+ - 数据源配置加密存储
11
+ - SQL 执行超时控制
12
+ - 连接池状态监控
13
+ - SQL 白名单/黑名单机制
14
+ """
15
+
16
+ __version__ = "1.0.0"
17
+ __author__ = "TraeMCP Team"
18
+ __all__ = ["MultiMyMCP", "DataSourceConfig", "ConnectionPool", "SQLExecutor", "Monitor"]
19
+
20
+ from multimymcp.core import MultiMyMCP
21
+ from multimymcp.config import DataSourceConfig
22
+ from multimymcp.pool import ConnectionPool
23
+ from multimymcp.executor import SQLExecutor
24
+ from multimymcp.monitor import Monitor
multimymcp/cli.py ADDED
@@ -0,0 +1,138 @@
1
+ """
2
+ 命令行接口模块
3
+
4
+ 提供命令行工具,用于管理数据源、查看状态等
5
+ """
6
+
7
+ import argparse
8
+ import json
9
+ from multimymcp import MultiMyMCP
10
+ from multimymcp.mcp_config_loader import MCPConfigLoader
11
+
12
+
13
+ def resolve_config_path(config_path: str = None) -> str:
14
+ """
15
+ 解析 CLI 应使用的配置文件路径。
16
+
17
+ Args:
18
+ config_path: 命令行显式传入的配置文件路径
19
+
20
+ Returns:
21
+ str: 可用的配置文件路径;若未找到则返回原始入参
22
+ """
23
+ if config_path:
24
+ return config_path
25
+
26
+ loader = MCPConfigLoader()
27
+ default_config = loader.get_default_config_path()
28
+
29
+ # 保持与 MCP Server 一致的默认查找顺序。
30
+ if default_config.exists():
31
+ return str(default_config)
32
+
33
+ if loader.multimymcp_config_file.exists():
34
+ return str(loader.multimymcp_config_file)
35
+
36
+ return config_path
37
+
38
+
39
+ def main():
40
+ """
41
+ 主函数
42
+ """
43
+ parser = argparse.ArgumentParser(description='MultiMyMCP 命令行工具')
44
+
45
+ # 全局参数
46
+ parser.add_argument('--config', '-c', type=str, help='配置文件路径')
47
+ parser.add_argument('--key', '-k', type=str, help='加密密钥')
48
+
49
+ # 子命令
50
+ subparsers = parser.add_subparsers(dest='command', help='子命令')
51
+
52
+ # 连接数据源
53
+ connect_parser = subparsers.add_parser('connect', help='连接数据源')
54
+ connect_parser.add_argument('datasource', type=str, help='数据源名称')
55
+
56
+ # 执行SQL
57
+ execute_parser = subparsers.add_parser('execute', help='执行SQL')
58
+ execute_parser.add_argument('sql', type=str, help='SQL语句')
59
+ execute_parser.add_argument('--datasource', '-d', type=str, default='default', help='数据源名称')
60
+ execute_parser.add_argument('--params', '-p', type=str, help='参数(JSON格式)')
61
+
62
+ # 查看状态
63
+ status_parser = subparsers.add_parser('status', help='查看连接池状态')
64
+ status_parser.add_argument('--datasource', '-d', type=str, default='default', help='数据源名称')
65
+
66
+ # 健康检查
67
+ health_parser = subparsers.add_parser('health', help='健康检查')
68
+ health_parser.add_argument('--datasource', '-d', type=str, default='default', help='数据源名称')
69
+
70
+ # 性能报告
71
+ perf_parser = subparsers.add_parser('performance', help='性能报告')
72
+ perf_parser.add_argument('--datasource', '-d', type=str, default='default', help='数据源名称')
73
+
74
+ # 列出数据源
75
+ list_parser = subparsers.add_parser('list', help='列出所有数据源')
76
+
77
+ # 调整连接池大小
78
+ resize_parser = subparsers.add_parser('resize', help='调整连接池大小')
79
+ resize_parser.add_argument('min_size', type=int, help='最小连接数')
80
+ resize_parser.add_argument('max_size', type=int, help='最大连接数')
81
+ resize_parser.add_argument('--datasource', '-d', type=str, default='default', help='数据源名称')
82
+
83
+ # 保存配置
84
+ save_parser = subparsers.add_parser('save', help='保存配置')
85
+ save_parser.add_argument('file', type=str, help='保存文件路径')
86
+
87
+ # 加载配置
88
+ load_parser = subparsers.add_parser('load', help='加载配置')
89
+ load_parser.add_argument('file', type=str, help='配置文件路径')
90
+
91
+ args = parser.parse_args()
92
+ resolved_config_path = resolve_config_path(args.config)
93
+
94
+ with MultiMyMCP(resolved_config_path, args.key) as mcp:
95
+ if args.command == 'connect':
96
+ success = mcp.connect(args.datasource)
97
+ print(f"连接成功: {success}")
98
+
99
+ elif args.command == 'execute':
100
+ params = None
101
+ if args.params:
102
+ params = json.loads(args.params)
103
+ result = mcp.execute(args.sql, params, args.datasource)
104
+ print(json.dumps(result, indent=2, ensure_ascii=False))
105
+
106
+ elif args.command == 'status':
107
+ status = mcp.get_pool_status(args.datasource)
108
+ print(json.dumps(status, indent=2, ensure_ascii=False))
109
+
110
+ elif args.command == 'health':
111
+ health = mcp.get_health_status(args.datasource)
112
+ print(json.dumps(health, indent=2, ensure_ascii=False))
113
+
114
+ elif args.command == 'performance':
115
+ report = mcp.get_performance_report(args.datasource)
116
+ print(json.dumps(report, indent=2, ensure_ascii=False))
117
+
118
+ elif args.command == 'list':
119
+ datasources = mcp.list_data_sources()
120
+ print("数据源列表:")
121
+ for ds in datasources:
122
+ print(f" - {ds}")
123
+
124
+ elif args.command == 'resize':
125
+ mcp.resize_pool(args.min_size, args.max_size, args.datasource)
126
+ print(f"连接池大小已调整: 最小={args.min_size}, 最大={args.max_size}")
127
+
128
+ elif args.command == 'save':
129
+ mcp.save_config(args.file)
130
+ print(f"配置已保存到: {args.file}")
131
+
132
+ elif args.command == 'load':
133
+ mcp.load_config(args.file)
134
+ print(f"配置已加载: {args.file}")
135
+
136
+
137
+ if __name__ == '__main__':
138
+ main()
multimymcp/config.py ADDED
@@ -0,0 +1,300 @@
1
+ """
2
+ 配置管理模块
3
+
4
+ 支持多数据源配置、环境变量配置、JSON5配置文件
5
+ 配置加密存储,运行时解密
6
+ """
7
+
8
+ import os
9
+ import json5
10
+ from typing import Dict, Optional, Any
11
+ from dataclasses import dataclass, field
12
+ from pathlib import Path
13
+ from dotenv import load_dotenv
14
+ from multimymcp.encryption import EncryptionUtil
15
+ from multimymcp.exceptions import ConfigurationError
16
+
17
+
18
+ @dataclass
19
+ class DataSourceConfig:
20
+ """
21
+ 数据源配置类
22
+
23
+ Attributes:
24
+ name: 数据源名称
25
+ host: 数据库主机地址
26
+ port: 数据库端口
27
+ user: 数据库用户名
28
+ password: 数据库密码(加密存储)
29
+ database: 数据库名称
30
+ pool_min_size: 连接池最小连接数
31
+ pool_max_size: 连接池最大连接数
32
+ pool_timeout: 连接池超时时间(秒)
33
+ sql_timeout: SQL执行超时时间(秒)
34
+ charset: 字符集
35
+ autocommit: 是否自动提交
36
+ encrypted: 密码是否已加密
37
+ """
38
+ name: str
39
+ host: str
40
+ port: int
41
+ user: str
42
+ password: str
43
+ database: str
44
+ pool_min_size: int = 2
45
+ pool_max_size: int = 10
46
+ pool_timeout: int = 30
47
+ sql_timeout: int = 60
48
+ charset: str = "utf8mb4"
49
+ autocommit: bool = False
50
+ encrypted: bool = False
51
+
52
+ def to_dict(self) -> Dict[str, Any]:
53
+ """
54
+ 转换为字典格式
55
+
56
+ Returns:
57
+ Dict[str, Any]: 配置字典
58
+ """
59
+ return {
60
+ "name": self.name,
61
+ "host": self.host,
62
+ "port": self.port,
63
+ "user": self.user,
64
+ "password": self.password,
65
+ "database": self.database,
66
+ "pool_min_size": self.pool_min_size,
67
+ "pool_max_size": self.pool_max_size,
68
+ "pool_timeout": self.pool_timeout,
69
+ "sql_timeout": self.sql_timeout,
70
+ "charset": self.charset,
71
+ "autocommit": self.autocommit,
72
+ "encrypted": self.encrypted,
73
+ }
74
+
75
+ @classmethod
76
+ def from_dict(cls, data: Dict[str, Any]) -> "DataSourceConfig":
77
+ """
78
+ 从字典创建配置对象
79
+
80
+ Args:
81
+ data: 配置字典
82
+
83
+ Returns:
84
+ DataSourceConfig: 配置对象
85
+ """
86
+ return cls(**data)
87
+
88
+
89
+ @dataclass
90
+ class SecurityConfig:
91
+ """
92
+ 安全配置类
93
+
94
+ Attributes:
95
+ whitelist_enabled: 是否启用白名单
96
+ blacklist_enabled: 是否启用黑名单
97
+ whitelist: SQL白名单列表
98
+ blacklist: SQL黑名单列表
99
+ """
100
+ whitelist_enabled: bool = False
101
+ blacklist_enabled: bool = True
102
+ whitelist: list = field(default_factory=list)
103
+ blacklist: list = field(default_factory=lambda: ["DROP", "TRUNCATE", "ALTER", "CREATE"])
104
+
105
+ def to_dict(self) -> Dict[str, Any]:
106
+ """
107
+ 转换为字典格式
108
+
109
+ Returns:
110
+ Dict[str, Any]: 配置字典
111
+ """
112
+ return {
113
+ "whitelist_enabled": self.whitelist_enabled,
114
+ "blacklist_enabled": self.blacklist_enabled,
115
+ "whitelist": self.whitelist,
116
+ "blacklist": self.blacklist,
117
+ }
118
+
119
+
120
+ class ConfigManager:
121
+ """
122
+ 配置管理器
123
+
124
+ 管理多数据源配置、安全配置、加密配置等
125
+ 支持从环境变量、JSON5文件加载配置
126
+ """
127
+
128
+ def __init__(self, encryption_key: Optional[str] = None):
129
+ """
130
+ 初始化配置管理器
131
+
132
+ Args:
133
+ encryption_key: 加密密钥(可选,默认从环境变量读取)
134
+ """
135
+ load_dotenv()
136
+
137
+ self.encryption_key = encryption_key or os.getenv("MYSQL_ENCRYPTION_KEY")
138
+ if not self.encryption_key:
139
+ self.encryption_key = EncryptionUtil.generate_key()
140
+
141
+ self.encryption = EncryptionUtil(self.encryption_key)
142
+ self.data_sources: Dict[str, DataSourceConfig] = {}
143
+ self.security_config = SecurityConfig()
144
+ self._load_from_env()
145
+
146
+ def _load_from_env(self):
147
+ """
148
+ 从环境变量加载配置
149
+ """
150
+ host = os.getenv("MYSQL_HOST")
151
+ if host:
152
+ config = DataSourceConfig(
153
+ name="default",
154
+ host=host,
155
+ port=int(os.getenv("MYSQL_PORT", "3306")),
156
+ user=os.getenv("MYSQL_USER", "root"),
157
+ password=os.getenv("MYSQL_PASSWORD", ""),
158
+ database=os.getenv("MYSQL_DATABASE", ""),
159
+ pool_min_size=int(os.getenv("MYSQL_POOL_MIN_SIZE", "2")),
160
+ pool_max_size=int(os.getenv("MYSQL_POOL_MAX_SIZE", "10")),
161
+ pool_timeout=int(os.getenv("MYSQL_POOL_TIMEOUT", "30")),
162
+ sql_timeout=int(os.getenv("MYSQL_SQL_TIMEOUT", "60")),
163
+ )
164
+ self.add_data_source(config)
165
+
166
+ self.security_config.whitelist_enabled = (
167
+ os.getenv("MYSQL_WHITELIST_ENABLED", "false").lower() == "true"
168
+ )
169
+ self.security_config.blacklist_enabled = (
170
+ os.getenv("MYSQL_BLACKLIST_ENABLED", "true").lower() == "true"
171
+ )
172
+
173
+ blacklist_str = os.getenv("MYSQL_BLACKLIST", "")
174
+ if blacklist_str:
175
+ self.security_config.blacklist = blacklist_str.split(",")
176
+
177
+ def load_from_file(self, file_path: str):
178
+ """
179
+ 从JSON5文件加载配置
180
+
181
+ Args:
182
+ file_path: 配置文件路径
183
+
184
+ Raises:
185
+ ConfigurationError: 配置文件不存在或格式错误
186
+ """
187
+ path = Path(file_path)
188
+ if not path.exists():
189
+ raise ConfigurationError(f"配置文件不存在: {file_path}")
190
+
191
+ try:
192
+ with open(path, "r", encoding="utf-8") as f:
193
+ config_data = json5.load(f)
194
+
195
+ if "datasources" in config_data:
196
+ for ds_name, ds_config in config_data["datasources"].items():
197
+ ds_config["name"] = ds_name
198
+ config = DataSourceConfig.from_dict(ds_config)
199
+ self.add_data_source(config)
200
+
201
+ if "security" in config_data:
202
+ security_data = config_data["security"]
203
+ self.security_config = SecurityConfig(**security_data)
204
+
205
+ except Exception as e:
206
+ raise ConfigurationError(f"配置文件解析失败: {str(e)}")
207
+
208
+ def add_data_source(self, config: DataSourceConfig, encrypt: bool = True):
209
+ """
210
+ 添加数据源配置
211
+
212
+ Args:
213
+ config: 数据源配置对象
214
+ encrypt: 是否加密密码
215
+ """
216
+ if encrypt and not config.encrypted:
217
+ config.password = self.encryption.encrypt(config.password)
218
+ config.encrypted = True
219
+
220
+ self.data_sources[config.name] = config
221
+
222
+ def get_data_source(self, name: str) -> Optional[DataSourceConfig]:
223
+ """
224
+ 获取数据源配置
225
+
226
+ Args:
227
+ name: 数据源名称
228
+
229
+ Returns:
230
+ Optional[DataSourceConfig]: 数据源配置对象
231
+ """
232
+ config = self.data_sources.get(name)
233
+ if config and config.encrypted:
234
+ config.password = self.encryption.decrypt(config.password)
235
+ config.encrypted = False
236
+ return config
237
+
238
+ def remove_data_source(self, name: str) -> bool:
239
+ """
240
+ 移除数据源配置
241
+
242
+ Args:
243
+ name: 数据源名称
244
+
245
+ Returns:
246
+ bool: 是否成功移除
247
+ """
248
+ if name in self.data_sources:
249
+ del self.data_sources[name]
250
+ return True
251
+ return False
252
+
253
+ def list_data_sources(self) -> list:
254
+ """
255
+ 列出所有数据源名称
256
+
257
+ Returns:
258
+ list: 数据源名称列表
259
+ """
260
+ return list(self.data_sources.keys())
261
+
262
+ def save_to_file(self, file_path: str):
263
+ """
264
+ 保存配置到JSON5文件
265
+
266
+ Args:
267
+ file_path: 配置文件路径
268
+ """
269
+ config_data = {
270
+ "datasources": {
271
+ name: config.to_dict() for name, config in self.datasources.items()
272
+ },
273
+ "security": self.security_config.to_dict(),
274
+ }
275
+
276
+ path = Path(file_path)
277
+ path.parent.mkdir(parents=True, exist_ok=True)
278
+
279
+ with open(path, "w", encoding="utf-8") as f:
280
+ json5.dump(config_data, f, indent=2, ensure_ascii=False)
281
+
282
+ def get_security_config(self) -> SecurityConfig:
283
+ """
284
+ 获取安全配置
285
+
286
+ Returns:
287
+ SecurityConfig: 安全配置对象
288
+ """
289
+ return self.security_config
290
+
291
+ def update_security_config(self, **kwargs):
292
+ """
293
+ 更新安全配置
294
+
295
+ Args:
296
+ **kwargs: 安全配置参数
297
+ """
298
+ for key, value in kwargs.items():
299
+ if hasattr(self.security_config, key):
300
+ setattr(self.security_config, key, value)