mcp-dbutils 0.8.0__py3-none-any.whl → 0.9.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.
- mcp_dbutils/__init__.py +9 -9
- mcp_dbutils/base.py +383 -58
- mcp_dbutils/config.py +12 -12
- mcp_dbutils/postgres/__init__.py +3 -3
- mcp_dbutils/postgres/config.py +16 -16
- mcp_dbutils/postgres/handler.py +446 -14
- mcp_dbutils/postgres/server.py +16 -16
- mcp_dbutils/sqlite/__init__.py +3 -3
- mcp_dbutils/sqlite/config.py +12 -12
- mcp_dbutils/sqlite/handler.py +361 -77
- mcp_dbutils/sqlite/server.py +21 -21
- mcp_dbutils/stats.py +112 -3
- {mcp_dbutils-0.8.0.dist-info → mcp_dbutils-0.9.0.dist-info}/METADATA +42 -8
- mcp_dbutils-0.9.0.dist-info/RECORD +18 -0
- mcp_dbutils-0.8.0.dist-info/RECORD +0 -18
- {mcp_dbutils-0.8.0.dist-info → mcp_dbutils-0.9.0.dist-info}/WHEEL +0 -0
- {mcp_dbutils-0.8.0.dist-info → mcp_dbutils-0.9.0.dist-info}/entry_points.txt +0 -0
- {mcp_dbutils-0.8.0.dist-info → mcp_dbutils-0.9.0.dist-info}/licenses/LICENSE +0 -0
mcp_dbutils/stats.py
CHANGED
@@ -2,8 +2,9 @@
|
|
2
2
|
|
3
3
|
from dataclasses import dataclass
|
4
4
|
from datetime import datetime
|
5
|
-
from typing import Optional
|
5
|
+
from typing import Optional, List, Tuple
|
6
6
|
import sys
|
7
|
+
import statistics
|
7
8
|
|
8
9
|
@dataclass
|
9
10
|
class ResourceStats:
|
@@ -24,11 +25,23 @@ class ResourceStats:
|
|
24
25
|
|
25
26
|
# Resource stats
|
26
27
|
estimated_memory: int = 0
|
28
|
+
|
29
|
+
# Performance monitoring
|
30
|
+
query_durations: List[float] = None # 查询执行时间列表 (秒)
|
31
|
+
query_types: dict[str, int] = None # 查询类型统计 (SELECT, EXPLAIN等)
|
32
|
+
slow_queries: List[Tuple[str, float]] = None # 慢查询记录 (SQL, 时间)
|
33
|
+
peak_memory: int = 0 # 峰值内存使用
|
27
34
|
|
28
35
|
def __post_init__(self):
|
29
36
|
"""Initialize mutable defaults"""
|
30
37
|
if self.error_types is None:
|
31
38
|
self.error_types = {}
|
39
|
+
if self.query_durations is None:
|
40
|
+
self.query_durations = []
|
41
|
+
if self.query_types is None:
|
42
|
+
self.query_types = {}
|
43
|
+
if self.slow_queries is None:
|
44
|
+
self.slow_queries = []
|
32
45
|
|
33
46
|
def record_connection_start(self):
|
34
47
|
"""Record new connection start"""
|
@@ -45,6 +58,26 @@ class ResourceStats:
|
|
45
58
|
self.query_count += 1
|
46
59
|
self.last_query_time = datetime.now()
|
47
60
|
|
61
|
+
def record_query_duration(self, sql: str, duration: float):
|
62
|
+
"""Record query execution time and type
|
63
|
+
|
64
|
+
Args:
|
65
|
+
sql: SQL query text
|
66
|
+
duration: Execution time in seconds
|
67
|
+
"""
|
68
|
+
self.query_durations.append(duration)
|
69
|
+
|
70
|
+
# Record query type
|
71
|
+
query_type = sql.strip().split()[0].upper()
|
72
|
+
self.query_types[query_type] = self.query_types.get(query_type, 0) + 1
|
73
|
+
|
74
|
+
# Record slow queries (over 100ms)
|
75
|
+
if duration > 0.1: # 100ms
|
76
|
+
# Keep at most 10 slow queries
|
77
|
+
if len(self.slow_queries) >= 10:
|
78
|
+
self.slow_queries.pop(0)
|
79
|
+
self.slow_queries.append((sql[:100], duration)) # Truncate SQL to avoid excessive length
|
80
|
+
|
48
81
|
def record_error(self, error_type: str):
|
49
82
|
"""Record error occurrence
|
50
83
|
|
@@ -61,7 +94,73 @@ class ResourceStats:
|
|
61
94
|
Args:
|
62
95
|
obj: Object to estimate size for
|
63
96
|
"""
|
64
|
-
|
97
|
+
current_memory = sys.getsizeof(obj)
|
98
|
+
self.estimated_memory = current_memory
|
99
|
+
self.peak_memory = max(self.peak_memory, current_memory)
|
100
|
+
|
101
|
+
def get_query_time_stats(self) -> dict:
|
102
|
+
"""Get query time statistics
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
Dictionary with min, max, avg, median query times
|
106
|
+
"""
|
107
|
+
if not self.query_durations:
|
108
|
+
return {
|
109
|
+
"min": 0,
|
110
|
+
"max": 0,
|
111
|
+
"avg": 0,
|
112
|
+
"median": 0
|
113
|
+
}
|
114
|
+
|
115
|
+
return {
|
116
|
+
"min": min(self.query_durations),
|
117
|
+
"max": max(self.query_durations),
|
118
|
+
"avg": sum(self.query_durations) / len(self.query_durations),
|
119
|
+
"median": statistics.median(self.query_durations) if len(self.query_durations) > 0 else 0
|
120
|
+
}
|
121
|
+
|
122
|
+
def get_performance_stats(self) -> str:
|
123
|
+
"""Get formatted performance statistics
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
Formatted string with performance statistics
|
127
|
+
"""
|
128
|
+
stats = []
|
129
|
+
stats.append(f"Database Performance Statistics")
|
130
|
+
stats.append(f"-----------------------------")
|
131
|
+
stats.append(f"Query Count: {self.query_count}")
|
132
|
+
|
133
|
+
# Query time statistics
|
134
|
+
if self.query_durations:
|
135
|
+
time_stats = self.get_query_time_stats()
|
136
|
+
stats.append(f"Query Times: avg={time_stats['avg']*1000:.2f}ms, min={time_stats['min']*1000:.2f}ms, max={time_stats['max']*1000:.2f}ms, median={time_stats['median']*1000:.2f}ms")
|
137
|
+
|
138
|
+
# Query type distribution
|
139
|
+
if self.query_types:
|
140
|
+
stats.append(f"Query Types:")
|
141
|
+
for qtype, count in self.query_types.items():
|
142
|
+
percentage = (count / self.query_count) * 100 if self.query_count else 0
|
143
|
+
stats.append(f" - {qtype}: {count} ({percentage:.1f}%)")
|
144
|
+
|
145
|
+
# Slow queries
|
146
|
+
if self.slow_queries:
|
147
|
+
stats.append(f"Slow Queries:")
|
148
|
+
for sql, duration in self.slow_queries:
|
149
|
+
stats.append(f" - {duration*1000:.2f}ms: {sql}...")
|
150
|
+
|
151
|
+
# Error statistics
|
152
|
+
if self.error_count > 0:
|
153
|
+
error_rate = (self.error_count / self.query_count) * 100 if self.query_count else 0
|
154
|
+
stats.append(f"Error Rate: {error_rate:.2f}% ({self.error_count} errors)")
|
155
|
+
stats.append(f"Error Types:")
|
156
|
+
for etype, count in self.error_types.items():
|
157
|
+
stats.append(f" - {etype}: {count}")
|
158
|
+
|
159
|
+
# Resource usage
|
160
|
+
stats.append(f"Memory Usage: current={self.estimated_memory/1024:.2f}KB, peak={self.peak_memory/1024:.2f}KB")
|
161
|
+
stats.append(f"Connections: active={self.active_connections}, total={self.total_connections}")
|
162
|
+
|
163
|
+
return "\n".join(stats)
|
65
164
|
|
66
165
|
def to_dict(self) -> dict:
|
67
166
|
"""Convert stats to dictionary for logging
|
@@ -74,12 +173,22 @@ class ResourceStats:
|
|
74
173
|
if self.connection_start_time:
|
75
174
|
connection_duration = (now - self.connection_start_time).total_seconds()
|
76
175
|
|
176
|
+
time_stats = self.get_query_time_stats()
|
177
|
+
|
77
178
|
return {
|
78
179
|
"active_connections": self.active_connections,
|
79
180
|
"total_connections": self.total_connections,
|
80
181
|
"connection_duration": connection_duration,
|
81
182
|
"query_count": self.query_count,
|
183
|
+
"query_times_ms": {
|
184
|
+
"min": time_stats["min"] * 1000 if self.query_durations else 0,
|
185
|
+
"max": time_stats["max"] * 1000 if self.query_durations else 0,
|
186
|
+
"avg": time_stats["avg"] * 1000 if self.query_durations else 0,
|
187
|
+
"median": time_stats["median"] * 1000 if self.query_durations else 0
|
188
|
+
},
|
189
|
+
"query_types": self.query_types,
|
82
190
|
"error_count": self.error_count,
|
83
191
|
"error_types": self.error_types,
|
84
|
-
"estimated_memory_bytes": self.estimated_memory
|
192
|
+
"estimated_memory_bytes": self.estimated_memory,
|
193
|
+
"peak_memory_bytes": self.peak_memory
|
85
194
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcp-dbutils
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.9.0
|
4
4
|
Summary: MCP Database Utilities Service
|
5
5
|
Author: Dong Hao
|
6
6
|
License-Expression: MIT
|
@@ -148,7 +148,7 @@ Add to Claude configuration:
|
|
148
148
|
The project requires a YAML configuration file, specified via the `--config` parameter. Configuration example:
|
149
149
|
|
150
150
|
```yaml
|
151
|
-
|
151
|
+
connections:
|
152
152
|
# Standard PostgreSQL configuration example
|
153
153
|
my_postgres:
|
154
154
|
type: postgres
|
@@ -257,7 +257,7 @@ The abstraction layer design is the core architectural concept in MCP Database U
|
|
257
257
|
|
258
258
|
### Basic Query
|
259
259
|
```python
|
260
|
-
# Access through
|
260
|
+
# Access through connection name
|
261
261
|
async with server.get_handler("my_postgres") as handler:
|
262
262
|
# Execute SQL query
|
263
263
|
result = await handler.execute_query("SELECT * FROM users")
|
@@ -275,7 +275,7 @@ schema = await handler.get_schema("users")
|
|
275
275
|
### Error Handling
|
276
276
|
```python
|
277
277
|
try:
|
278
|
-
async with server.get_handler("
|
278
|
+
async with server.get_handler("my_connection") as handler:
|
279
279
|
result = await handler.execute_query("SELECT * FROM users")
|
280
280
|
except ValueError as e:
|
281
281
|
print(f"Configuration error: {e}")
|
@@ -298,19 +298,53 @@ Core server class providing:
|
|
298
298
|
|
299
299
|
### MCP Tools
|
300
300
|
|
301
|
-
####
|
301
|
+
#### dbutils-list-tables
|
302
302
|
Lists all tables in the specified database.
|
303
303
|
- Parameters:
|
304
|
-
*
|
304
|
+
* connection: Database connection name
|
305
305
|
- Returns: Text content with a list of table names
|
306
306
|
|
307
|
-
#### query
|
307
|
+
#### dbutils-run-query
|
308
308
|
Executes a SQL query on the specified database.
|
309
309
|
- Parameters:
|
310
|
-
*
|
310
|
+
* connection: Database connection name
|
311
311
|
* sql: SQL query to execute (SELECT only)
|
312
312
|
- Returns: Query results in a formatted text
|
313
313
|
|
314
|
+
#### dbutils-get-stats
|
315
|
+
Get table statistics information.
|
316
|
+
- Parameters:
|
317
|
+
* connection: Database connection name
|
318
|
+
* table: Table name
|
319
|
+
- Returns: Statistics including row count, size, column stats
|
320
|
+
|
321
|
+
#### dbutils-list-constraints
|
322
|
+
List table constraints (primary key, foreign keys, etc).
|
323
|
+
- Parameters:
|
324
|
+
* connection: Database connection name
|
325
|
+
* table: Table name
|
326
|
+
- Returns: Detailed constraint information
|
327
|
+
|
328
|
+
#### dbutils-explain-query
|
329
|
+
Get query execution plan with cost estimates.
|
330
|
+
- Parameters:
|
331
|
+
* connection: Database connection name
|
332
|
+
* sql: SQL query to explain
|
333
|
+
- Returns: Formatted execution plan
|
334
|
+
|
335
|
+
#### dbutils-get-performance
|
336
|
+
Get database performance statistics.
|
337
|
+
- Parameters:
|
338
|
+
* connection: Database connection name
|
339
|
+
- Returns: Detailed performance statistics including query times, query types, error rates, and resource usage
|
340
|
+
|
341
|
+
#### dbutils-analyze-query
|
342
|
+
Analyze a SQL query for performance and provide optimization suggestions.
|
343
|
+
- Parameters:
|
344
|
+
* connection: Database connection name
|
345
|
+
* sql: SQL query to analyze
|
346
|
+
- Returns: Query analysis with execution plan, timing information, and optimization suggestions
|
347
|
+
|
314
348
|
### DatabaseHandler
|
315
349
|
Abstract base class defining interfaces:
|
316
350
|
- get_tables(): Get table resource list
|
@@ -0,0 +1,18 @@
|
|
1
|
+
mcp_dbutils/__init__.py,sha256=9dqT2BdVvwforQVWOvJq2M0zCHxFamWIYWC4-6aY70k,1905
|
2
|
+
mcp_dbutils/base.py,sha256=2fLSPL-BxdgKIOT_eq73wQGla6MZB03jUrWqBRSWVBQ,25474
|
3
|
+
mcp_dbutils/config.py,sha256=uFI4Haw4en5gxHfCM9zugUqNCQFikdlHJZU_NTht7gQ,1905
|
4
|
+
mcp_dbutils/log.py,sha256=fibVIwsb1HVU5zriGrDZTMEirKjgIuxuN_B_YTdAJ7I,996
|
5
|
+
mcp_dbutils/stats.py,sha256=WYD9NAKHH2bFKmUSTOg18-SuIXergpznPq8AtIjtxdI,7101
|
6
|
+
mcp_dbutils/postgres/__init__.py,sha256=XanCXw-kVE6ayqFqjuLJ9swWPcCVlcKZXB2Et2Wjl9w,154
|
7
|
+
mcp_dbutils/postgres/config.py,sha256=Kqq0ZCa7PhtvMaBX3sfdEscGosHR1dWuiP8IkI5lzDo,5495
|
8
|
+
mcp_dbutils/postgres/handler.py,sha256=O45K0MOosNQ68SJ8piXXH3aZ74RVQ981hudk6faj28Y,24397
|
9
|
+
mcp_dbutils/postgres/server.py,sha256=FROogvjr3xlKAZcAvJMNHGgyHqT_LaMK9-vYbA0Dbm0,8716
|
10
|
+
mcp_dbutils/sqlite/__init__.py,sha256=lTUOkSfSWNw_BohJ_KNxYKMCANdtpGa3JK_ZyJsg_Ls,134
|
11
|
+
mcp_dbutils/sqlite/config.py,sha256=2ekTp89rcCu4qQN1O3sZjIcOwjhzWi4tisD20T2WAik,4533
|
12
|
+
mcp_dbutils/sqlite/handler.py,sha256=VxBVbwpNSQHyiudQXYFYpcdoScvbK2IGzYMbkcpvFcI,17731
|
13
|
+
mcp_dbutils/sqlite/server.py,sha256=49ug43nyXZApclMXo0t5dKCN4wjDyDUZ-vmf746ac8M,7709
|
14
|
+
mcp_dbutils-0.9.0.dist-info/METADATA,sha256=I52Q7IV8un9A5tmTTmEOFKZiW_UYCrWwBDhDq1tzFyE,11986
|
15
|
+
mcp_dbutils-0.9.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
16
|
+
mcp_dbutils-0.9.0.dist-info/entry_points.txt,sha256=XTjt0QmYRgKOJQT6skR9bp1EMUfIrgpHeZJPZ3CJffs,49
|
17
|
+
mcp_dbutils-0.9.0.dist-info/licenses/LICENSE,sha256=1A_CwpWVlbjrKdVEYO77vYfnXlW7oxcilZ8FpA_BzCI,1065
|
18
|
+
mcp_dbutils-0.9.0.dist-info/RECORD,,
|
@@ -1,18 +0,0 @@
|
|
1
|
-
mcp_dbutils/__init__.py,sha256=xcfE1spAaONAoxBYB1ZyDX8tw7nxV1PMqo_RwxLDp0A,1892
|
2
|
-
mcp_dbutils/base.py,sha256=a6wBdSGsbPQq7fKb_H-relSKbDZov-d75G_Q3S_aYP4,11284
|
3
|
-
mcp_dbutils/config.py,sha256=EwnPNuQVCBKd5WOXQfROyDTM-YpM_Odp0GhCPRg8YwE,1863
|
4
|
-
mcp_dbutils/log.py,sha256=fibVIwsb1HVU5zriGrDZTMEirKjgIuxuN_B_YTdAJ7I,996
|
5
|
-
mcp_dbutils/stats.py,sha256=2hiKi_M8V4xhVHlH5FS-Df5GuMEpuzif12C8ik06Khs,2538
|
6
|
-
mcp_dbutils/postgres/__init__.py,sha256=Y6v_RsI79pqAfpKM3SrT1T1I9r5yWuKT0GUUNmHD3DE,146
|
7
|
-
mcp_dbutils/postgres/config.py,sha256=Np0GS5iUaUuWpYI8QfnyjUDy7v-vResQEAexR4W92-o,5447
|
8
|
-
mcp_dbutils/postgres/handler.py,sha256=JC_Qyw1s6qjVR0tdH7FLAEAs9EPl_-wgFQLryQyMq5c,6089
|
9
|
-
mcp_dbutils/postgres/server.py,sha256=_S3HF1KooxPB9gX1FedoOOGn93tHlIevCab6vjCt2TU,8715
|
10
|
-
mcp_dbutils/sqlite/__init__.py,sha256=QV4th2ywzUmCCa3GHCcBf8blJ_E8OYy0dJ2fSf1TfSU,134
|
11
|
-
mcp_dbutils/sqlite/config.py,sha256=RTHT2Xx--g-osD73CpT8DrCk0VHpHfPil3D6YUzXD-g,4519
|
12
|
-
mcp_dbutils/sqlite/handler.py,sha256=bf_k93rCcJn09zc7tsqrlbiTGUg3FspimfWKxK_JQTs,4970
|
13
|
-
mcp_dbutils/sqlite/server.py,sha256=7Bbq9l7Ca_4dzkAbbdRcXxvHoO_NFLzZHwlhKB0HIJc,7724
|
14
|
-
mcp_dbutils-0.8.0.dist-info/METADATA,sha256=403i2poAoHqP0HCb_w0PL7Dj7e59aewKZaaS_KEiPYA,10840
|
15
|
-
mcp_dbutils-0.8.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
16
|
-
mcp_dbutils-0.8.0.dist-info/entry_points.txt,sha256=XTjt0QmYRgKOJQT6skR9bp1EMUfIrgpHeZJPZ3CJffs,49
|
17
|
-
mcp_dbutils-0.8.0.dist-info/licenses/LICENSE,sha256=1A_CwpWVlbjrKdVEYO77vYfnXlW7oxcilZ8FpA_BzCI,1065
|
18
|
-
mcp_dbutils-0.8.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|