mcp-dbutils 0.7.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/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
- self.estimated_memory = sys.getsizeof(obj)
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.7.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
- databases:
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 database name
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("my_db") as 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
- #### list_tables
301
+ #### dbutils-list-tables
302
302
  Lists all tables in the specified database.
303
303
  - Parameters:
304
- * database: Database configuration name
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
- * database: Database configuration name
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=aawU0mY78Ga8YhSYvzOaBi1V8qSYTWyAuZzmXTc37Rg,11232
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.7.0.dist-info/METADATA,sha256=smgCaufFbIkvOFhDGqrajHYC9WxeQcDCWRerr_bxHp8,10840
15
- mcp_dbutils-0.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- mcp_dbutils-0.7.0.dist-info/entry_points.txt,sha256=XTjt0QmYRgKOJQT6skR9bp1EMUfIrgpHeZJPZ3CJffs,49
17
- mcp_dbutils-0.7.0.dist-info/licenses/LICENSE,sha256=1A_CwpWVlbjrKdVEYO77vYfnXlW7oxcilZ8FpA_BzCI,1065
18
- mcp_dbutils-0.7.0.dist-info/RECORD,,