mcp-dbutils 0.16.1__py3-none-any.whl → 0.18.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.
@@ -1,6 +1,7 @@
1
1
  """SQLite connection handler implementation"""
2
2
 
3
3
  import sqlite3
4
+ import time
4
5
 
5
6
  import mcp.types as types
6
7
 
@@ -78,28 +79,51 @@ class SQLiteHandler(ConnectionHandler):
78
79
  async def _execute_query(self, sql: str) -> str:
79
80
  """Execute SQL query"""
80
81
  try:
81
- # Only allow SELECT statements
82
- if not sql.strip().upper().startswith("SELECT"):
83
- raise ConnectionHandlerError("cannot execute DELETE statement")
84
-
85
- with sqlite3.connect(self.config.path) as conn:
86
- conn.row_factory = sqlite3.Row
87
- cur = conn.cursor()
88
- self.log("debug", f"Executing query: {sql}")
89
-
82
+ # Check if the query is a DDL statement
83
+ sql_upper = sql.strip().upper()
84
+ is_ddl = sql_upper.startswith(("CREATE", "DROP", "ALTER", "TRUNCATE"))
85
+ is_dml = sql_upper.startswith(("INSERT", "UPDATE", "DELETE"))
86
+ is_select = sql_upper.startswith("SELECT")
87
+
88
+ if not (is_select or is_ddl or is_dml):
89
+ raise ConnectionHandlerError("Only SELECT, DDL, and DML statements are allowed")
90
+
91
+ conn = sqlite3.connect(self.config.path)
92
+ cur = conn.cursor()
93
+
94
+ try:
95
+ start_time = time.time()
90
96
  cur.execute(sql)
91
- results = cur.fetchall()
92
- rows = [dict(row) for row in results]
93
-
94
- result_text = str({
95
- 'type': self.db_type,
96
- 'columns': list(rows[0].keys()) if rows else [],
97
- 'rows': rows,
98
- 'row_count': len(rows)
99
- })
97
+ conn.commit()
98
+ end_time = time.time()
99
+ elapsed_ms = (end_time - start_time) * 1000
100
+ self.log("debug", f"Query executed in {elapsed_ms:.2f}ms")
101
+
102
+ if is_select:
103
+ # Get column names
104
+ columns = [description[0] for description in cur.description]
105
+ # Fetch results and convert to dictionaries
106
+ results = []
107
+ for row in cur.fetchall():
108
+ # Convert each row to a dictionary
109
+ row_dict = {}
110
+ for i, col_name in enumerate(columns):
111
+ row_dict[col_name] = row[i]
112
+ results.append(row_dict)
100
113
 
101
- self.log("debug", f"Query completed, returned {len(rows)} rows")
102
- return result_text
114
+ return str({
115
+ "columns": columns,
116
+ "rows": results
117
+ })
118
+ else:
119
+ # For DDL/DML statements
120
+ return "Query executed successfully"
121
+ except sqlite3.Error as e:
122
+ self.log("error", f"Query error: {str(e)}")
123
+ raise ConnectionHandlerError(str(e))
124
+ finally:
125
+ cur.close()
126
+ conn.close()
103
127
  except sqlite3.Error as e:
104
128
  error_msg = f"[{self.db_type}] Query execution failed: {str(e)}"
105
129
  raise ConnectionHandlerError(error_msg)
@@ -112,13 +136,13 @@ class SQLiteHandler(ConnectionHandler):
112
136
  # 获取表信息
113
137
  cur.execute(f"PRAGMA table_info({table_name})")
114
138
  columns = cur.fetchall()
115
-
139
+
116
140
  # SQLite不支持表级注释,但我们可以获取表的详细信息
117
141
  description = [
118
142
  f"Table: {table_name}\n",
119
143
  COLUMNS_HEADER
120
144
  ]
121
-
145
+
122
146
  for col in columns:
123
147
  col_info = [
124
148
  f" {col[1]} ({col[2]})",
@@ -128,9 +152,9 @@ class SQLiteHandler(ConnectionHandler):
128
152
  ]
129
153
  description.extend(col_info)
130
154
  description.append("") # Empty line between columns
131
-
155
+
132
156
  return "\n".join(description)
133
-
157
+
134
158
  except sqlite3.Error as e:
135
159
  error_msg = f"Failed to get table description: {str(e)}"
136
160
  self.stats.record_error(e.__class__.__name__)
@@ -144,25 +168,25 @@ class SQLiteHandler(ConnectionHandler):
144
168
  # SQLite provides the complete CREATE statement
145
169
  cur.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
146
170
  result = cur.fetchone()
147
-
171
+
148
172
  if not result:
149
173
  return f"Table {table_name} not found"
150
-
174
+
151
175
  ddl = result[0]
152
-
176
+
153
177
  # Get indexes
154
178
  cur.execute("SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name=?", (table_name,))
155
179
  indexes = cur.fetchall()
156
-
180
+
157
181
  # Add index definitions
158
182
  if indexes:
159
183
  ddl = ddl + "\n\n-- Indexes:"
160
184
  for idx in indexes:
161
185
  if idx[0]: # Some internal indexes might have NULL sql
162
186
  ddl = ddl + "\n" + idx[0] + ";"
163
-
187
+
164
188
  return ddl
165
-
189
+
166
190
  except sqlite3.Error as e:
167
191
  error_msg = f"Failed to get table DDL: {str(e)}"
168
192
  self.stats.record_error(e.__class__.__name__)
@@ -173,43 +197,49 @@ class SQLiteHandler(ConnectionHandler):
173
197
  try:
174
198
  with sqlite3.connect(self.config.path) as conn:
175
199
  cur = conn.cursor()
200
+
201
+ # Check if table exists
202
+ cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
203
+ if not cur.fetchone():
204
+ raise ConnectionHandlerError(f"Table '{table_name}' doesn't exist")
205
+
176
206
  # 获取索引列表
177
207
  cur.execute(f"PRAGMA index_list({table_name})")
178
208
  indexes = cur.fetchall()
179
-
209
+
180
210
  if not indexes:
181
211
  return f"No indexes found on table {table_name}"
182
-
212
+
183
213
  formatted_indexes = [f"Indexes for {table_name}:"]
184
-
214
+
185
215
  for idx in indexes:
186
216
  # 获取索引详细信息
187
217
  cur.execute(f"PRAGMA index_info({idx[1]})")
188
218
  index_info = cur.fetchall()
189
-
219
+
190
220
  # 获取索引的SQL定义
191
221
  cur.execute("SELECT sql FROM sqlite_master WHERE type='index' AND name=?", (idx[1],))
192
222
  sql = cur.fetchone()
193
-
223
+
194
224
  index_details = [
195
225
  f"\nIndex: {idx[1]}",
196
226
  f"Type: {'UNIQUE' if idx[2] else 'INDEX'}",
197
227
  COLUMNS_HEADER
198
228
  ]
199
-
229
+
200
230
  for col in index_info:
201
231
  index_details.append(f" - {col[2]}")
202
-
232
+
203
233
  if sql and sql[0]:
204
234
  index_details.extend([
205
235
  "Definition:",
206
236
  f" {sql[0]}"
207
237
  ])
208
-
238
+
209
239
  formatted_indexes.extend(index_details)
210
-
240
+
211
241
  return "\n".join(formatted_indexes)
212
-
242
+
213
243
  except sqlite3.Error as e:
214
244
  error_msg = f"Failed to get index information: {str(e)}"
215
245
  self.stats.record_error(e.__class__.__name__)
@@ -220,28 +250,33 @@ class SQLiteHandler(ConnectionHandler):
220
250
  try:
221
251
  with sqlite3.connect(self.config.path) as conn:
222
252
  cur = conn.cursor()
223
-
253
+
254
+ # Check if table exists
255
+ cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
256
+ if not cur.fetchone():
257
+ raise ConnectionHandlerError(f"Table '{table_name}' doesn't exist")
258
+
224
259
  # Get basic table information
225
260
  cur.execute(f"PRAGMA table_info({table_name})")
226
261
  columns = cur.fetchall()
227
-
262
+
228
263
  # Count rows
229
264
  cur.execute(f"SELECT COUNT(*) FROM {table_name}")
230
265
  row_count = cur.fetchone()[0]
231
-
266
+
232
267
  # Get index information
233
268
  cur.execute(f"PRAGMA index_list({table_name})")
234
269
  indexes = cur.fetchall()
235
-
270
+
236
271
  # Get page count and size
237
272
  cur.execute("PRAGMA page_count")
238
273
  page_count = cur.fetchone()[0]
239
274
  cur.execute("PRAGMA page_size")
240
275
  page_size = cur.fetchone()[0]
241
-
276
+
242
277
  # Calculate total size
243
278
  total_size = page_count * page_size
244
-
279
+
245
280
  # Format size in human readable format
246
281
  def format_size(size):
247
282
  for unit in ['B', 'KB', 'MB', 'GB']:
@@ -249,7 +284,7 @@ class SQLiteHandler(ConnectionHandler):
249
284
  return f"{size:.2f} {unit}"
250
285
  size /= 1024
251
286
  return f"{size:.2f} TB"
252
-
287
+
253
288
  # Get column statistics
254
289
  column_stats = []
255
290
  for col in columns:
@@ -260,7 +295,7 @@ class SQLiteHandler(ConnectionHandler):
260
295
  # Get distinct value count
261
296
  cur.execute(f"SELECT COUNT(DISTINCT {col_name}) FROM {table_name}")
262
297
  distinct_count = cur.fetchone()[0]
263
-
298
+
264
299
  column_stats.append({
265
300
  'name': col_name,
266
301
  'type': col[2],
@@ -268,7 +303,7 @@ class SQLiteHandler(ConnectionHandler):
268
303
  'null_percent': (null_count / row_count * 100) if row_count > 0 else 0,
269
304
  'distinct_count': distinct_count
270
305
  })
271
-
306
+
272
307
  # Format output
273
308
  output = [
274
309
  f"Table Statistics for {table_name}:",
@@ -279,7 +314,7 @@ class SQLiteHandler(ConnectionHandler):
279
314
  f" Index Count: {len(indexes)}\n",
280
315
  "Column Statistics:"
281
316
  ]
282
-
317
+
283
318
  for stat in column_stats:
284
319
  col_info = [
285
320
  f" {stat['name']} ({stat['type']}):",
@@ -288,7 +323,7 @@ class SQLiteHandler(ConnectionHandler):
288
323
  ]
289
324
  output.extend(col_info)
290
325
  output.append("") # Empty line between columns
291
-
326
+
292
327
  return "\n".join(output)
293
328
 
294
329
  except sqlite3.Error as e:
@@ -301,21 +336,21 @@ class SQLiteHandler(ConnectionHandler):
301
336
  try:
302
337
  with sqlite3.connect(self.config.path) as conn:
303
338
  cur = conn.cursor()
304
-
339
+
305
340
  # Get table info (includes PRIMARY KEY)
306
341
  cur.execute(f"PRAGMA table_info({table_name})")
307
342
  columns = cur.fetchall()
308
-
343
+
309
344
  # Get foreign keys
310
345
  cur.execute(f"PRAGMA foreign_key_list({table_name})")
311
346
  foreign_keys = cur.fetchall()
312
-
347
+
313
348
  # Get indexes (for UNIQUE constraints)
314
349
  cur.execute(f"PRAGMA index_list({table_name})")
315
350
  indexes = cur.fetchall()
316
-
351
+
317
352
  output = [f"Constraints for {table_name}:"]
318
-
353
+
319
354
  # Primary Key constraints
320
355
  pk_columns = [col[1] for col in columns if col[5]] # col[5] is pk flag
321
356
  if pk_columns:
@@ -323,13 +358,13 @@ class SQLiteHandler(ConnectionHandler):
323
358
  "\nPrimary Key Constraints:",
324
359
  f" PRIMARY KEY ({', '.join(pk_columns)})"
325
360
  ])
326
-
361
+
327
362
  # Foreign Key constraints
328
363
  if foreign_keys:
329
364
  output.append("\nForeign Key Constraints:")
330
365
  current_fk = None
331
366
  fk_columns = []
332
-
367
+
333
368
  for fk in foreign_keys:
334
369
  # SQLite foreign_key_list format:
335
370
  # id, seq, table, from, to, on_update, on_delete, match
@@ -345,10 +380,10 @@ class SQLiteHandler(ConnectionHandler):
345
380
  output.append(f" ON UPDATE: {fk[5]}")
346
381
  if fk[6]: # on_delete
347
382
  output.append(f" ON DELETE: {fk[6]}")
348
-
383
+
349
384
  if fk_columns:
350
385
  output.append(f" ({', '.join(fk_columns)})")
351
-
386
+
352
387
  # Unique constraints (from indexes)
353
388
  unique_indexes = [idx for idx in indexes if idx[2]] # idx[2] is unique flag
354
389
  if unique_indexes:
@@ -359,7 +394,7 @@ class SQLiteHandler(ConnectionHandler):
359
394
  index_info = cur.fetchall()
360
395
  columns = [info[2] for info in index_info] # info[2] is column name
361
396
  output.append(f" UNIQUE ({', '.join(columns)})")
362
-
397
+
363
398
  # Check constraints
364
399
  # Note: SQLite doesn't provide direct access to CHECK constraints through PRAGMA
365
400
  # We need to parse the table creation SQL
@@ -368,7 +403,7 @@ class SQLiteHandler(ConnectionHandler):
368
403
  if "CHECK" in create_sql.upper():
369
404
  output.append("\nCheck Constraints:")
370
405
  output.append(" See table DDL for CHECK constraints")
371
-
406
+
372
407
  return "\n".join(output)
373
408
 
374
409
  except sqlite3.Error as e:
@@ -381,11 +416,18 @@ class SQLiteHandler(ConnectionHandler):
381
416
  try:
382
417
  with sqlite3.connect(self.config.path) as conn:
383
418
  cur = conn.cursor()
384
-
419
+
420
+ # Check if the query is valid by preparing it
421
+ try:
422
+ # Use prepare to validate the query without executing it
423
+ conn.execute(f"EXPLAIN {sql}")
424
+ except sqlite3.Error as e:
425
+ raise ConnectionHandlerError(f"Failed to explain query: {str(e)}")
426
+
385
427
  # Get EXPLAIN output
386
428
  cur.execute(f"EXPLAIN QUERY PLAN {sql}")
387
429
  plan = cur.fetchall()
388
-
430
+
389
431
  # Format the output
390
432
  output = [
391
433
  "Query Execution Plan:",
@@ -393,20 +435,20 @@ class SQLiteHandler(ConnectionHandler):
393
435
  "Details:",
394
436
  "--------"
395
437
  ]
396
-
438
+
397
439
  for step in plan:
398
440
  # EXPLAIN QUERY PLAN format:
399
441
  # id | parent | notused | detail
400
442
  indent = " " * (step[0] - step[1] if step[1] >= 0 else step[0])
401
443
  output.append(f"{indent}{step[3]}")
402
-
444
+
403
445
  # Add query statistics
404
446
  output.extend([
405
447
  "\nNote: SQLite's EXPLAIN QUERY PLAN provides a high-level overview.",
406
448
  "For detailed execution statistics, consider using EXPLAIN (not QUERY PLAN)",
407
449
  "which shows the virtual machine instructions."
408
450
  ])
409
-
451
+
410
452
  return "\n".join(output)
411
453
 
412
454
  except sqlite3.Error as e:
@@ -414,6 +456,21 @@ class SQLiteHandler(ConnectionHandler):
414
456
  self.stats.record_error(e.__class__.__name__)
415
457
  raise ConnectionHandlerError(error_msg)
416
458
 
459
+ async def test_connection(self) -> bool:
460
+ """Test database connection
461
+
462
+ Returns:
463
+ bool: True if connection is successful, False otherwise
464
+ """
465
+ try:
466
+ with sqlite3.connect(self.config.path) as conn:
467
+ cur = conn.cursor()
468
+ cur.execute("SELECT 1")
469
+ return True
470
+ except sqlite3.Error as e:
471
+ self.log("error", f"Connection test failed: {str(e)}")
472
+ return False
473
+
417
474
  async def cleanup(self):
418
475
  """Cleanup resources"""
419
476
  # Log final stats
@@ -0,0 +1,138 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-dbutils
3
+ Version: 0.18.0
4
+ Summary: MCP Database Utilities Service
5
+ Author: Dong Hao
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: mcp>=1.2.1
10
+ Requires-Dist: mysql-connector-python>=8.2.0
11
+ Requires-Dist: psycopg2-binary>=2.9.10
12
+ Requires-Dist: python-dotenv>=1.0.1
13
+ Requires-Dist: pyyaml>=6.0.2
14
+ Provides-Extra: test
15
+ Requires-Dist: aiosqlite>=0.19.0; extra == 'test'
16
+ Requires-Dist: docker>=7.0.0; extra == 'test'
17
+ Requires-Dist: pre-commit>=3.6.0; extra == 'test'
18
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
19
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'test'
20
+ Requires-Dist: pytest-docker>=2.0.0; extra == 'test'
21
+ Requires-Dist: pytest>=7.0.0; extra == 'test'
22
+ Requires-Dist: ruff>=0.3.0; extra == 'test'
23
+ Requires-Dist: testcontainers>=3.7.0; extra == 'test'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # MCP 数据库工具
27
+
28
+ <!-- 项目状态徽章 -->
29
+ [![构建状态](https://img.shields.io/github/workflow/status/donghao1393/mcp-dbutils/Quality%20Assurance?label=tests)](https://github.com/donghao1393/mcp-dbutils/actions)
30
+ [![覆盖率](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/donghao1393/bdd0a63ec2a816539ff8c136ceb41e48/raw/coverage.json)](https://github.com/donghao1393/mcp-dbutils/actions)
31
+ [![质量门禁状态](https://sonarcloud.io/api/project_badges/measure?project=donghao1393_mcp-dbutils&metric=alert_status)](https://sonarcloud.io/dashboard?id=donghao1393_mcp-dbutils)
32
+
33
+ <!-- 版本和安装徽章 -->
34
+ [![PyPI 版本](https://img.shields.io/pypi/v/mcp-dbutils)](https://pypi.org/project/mcp-dbutils/)
35
+ [![PyPI 下载量](https://img.shields.io/pypi/dm/mcp-dbutils)](https://pypi.org/project/mcp-dbutils/)
36
+ [![Smithery](https://smithery.ai/badge/@donghao1393/mcp-dbutils)](https://smithery.ai/server/@donghao1393/mcp-dbutils)
37
+
38
+ <!-- 技术规格徽章 -->
39
+ [![Python](https://img.shields.io/badge/Python-3.10%2B-blue)](https://www.python.org/)
40
+ [![许可证](https://img.shields.io/github/license/donghao1393/mcp-dbutils)](LICENSE)
41
+ [![GitHub 星标](https://img.shields.io/github/stars/donghao1393/mcp-dbutils?style=social)](https://github.com/donghao1393/mcp-dbutils/stargazers)
42
+
43
+ [English](README_EN.md) | [文档导航](#文档导航)
44
+
45
+ ![Image](https://github.com/user-attachments/assets/26c4f1a1-7b19-4bdd-b9fd-34ad198b0ce3)
46
+
47
+ ## 简介
48
+
49
+ MCP Database Utilities 是一个多功能的 MCP 服务,它使您的 AI 能够通过统一的连接配置安全地访问各种类型的数据库(SQLite、MySQL、PostgreSQL 等)进行数据分析。
50
+
51
+ 您可以将其视为 AI 系统和数据库之间的安全桥梁,允许 AI 在不直接访问数据库或冒数据修改风险的情况下读取和分析您的数据。
52
+
53
+ ### 核心特性
54
+
55
+ - **安全优先**:严格只读操作,无直接数据库访问,隔离连接,按需连接,自动超时
56
+ - **隐私保障**:本地处理,最小数据暴露,凭证保护,敏感数据屏蔽
57
+ - **多数据库支持**:使用相同的接口连接 SQLite、MySQL、PostgreSQL
58
+ - **简单配置**:所有数据库连接使用单个 YAML 文件
59
+ - **高级功能**:表格浏览、架构分析和查询执行
60
+
61
+ > 🔒 **安全说明**:MCP 数据库工具采用安全优先的架构设计,非常适合注重数据保护的企业、初创公司和个人用户。详细了解我们的[安全架构](docs/zh/technical/security.md)。
62
+
63
+ ## 快速入门
64
+
65
+ 我们提供了多种安装方式,包括 uvx、Docker 和 Smithery。详细的安装和配置步骤请参阅[安装指南](docs/zh/installation.md)。
66
+
67
+ ### 基本步骤
68
+
69
+ 1. **安装**:选择适合您的安装方式([详细说明](docs/zh/installation.md))
70
+ 2. **配置**:创建包含数据库连接信息的 YAML 文件([配置指南](docs/zh/configuration.md))
71
+ 3. **连接**:将配置添加到您的 AI 客户端
72
+ 4. **使用**:开始与您的数据库交互([使用指南](docs/zh/usage.md))
73
+
74
+ ### 示例交互
75
+
76
+ **您**:"能否列出我的数据库中的所有表?"
77
+
78
+ **AI**:"以下是您的数据库中的表:
79
+ - customers(客户)
80
+ - products(产品)
81
+ - orders(订单)
82
+ - inventory(库存)"
83
+
84
+ **您**:"customers 表的结构是什么样的?"
85
+
86
+ **AI**:"customers 表有以下结构:
87
+ - id(整数,主键)
88
+ - name(文本)
89
+ - email(文本)
90
+ - registration_date(日期)"
91
+
92
+ ## 文档导航
93
+
94
+ ### 入门指南
95
+ - [安装指南](docs/zh/installation.md) - 详细的安装步骤和配置说明
96
+ - [平台特定安装指南](docs/zh/installation-platform-specific.md) - 针对不同操作系统的安装说明
97
+ - [配置指南](docs/zh/configuration.md) - 数据库连接配置示例和最佳实践
98
+ - [使用指南](docs/zh/usage.md) - 基本操作流程和常见使用场景
99
+
100
+ ### 技术文档
101
+ - [架构设计](docs/zh/technical/architecture.md) - 系统架构和组件说明
102
+ - [安全架构](docs/zh/technical/security.md) - 安全特性和保护机制
103
+ - [开发指南](docs/zh/technical/development.md) - 代码质量和开发流程
104
+ - [测试指南](docs/zh/technical/testing.md) - 测试框架和最佳实践
105
+ - [SonarCloud 集成](docs/zh/technical/sonarcloud-integration.md) - SonarCloud 与 AI 集成指南
106
+
107
+ ### 示例文档
108
+ - [SQLite 示例](docs/zh/examples/sqlite-examples.md) - SQLite 数据库操作示例
109
+ - [PostgreSQL 示例](docs/zh/examples/postgresql-examples.md) - PostgreSQL 数据库操作示例
110
+ - [MySQL 示例](docs/zh/examples/mysql-examples.md) - MySQL 数据库操作示例
111
+ - [高级 LLM 交互示例](docs/zh/examples/advanced-llm-interactions.md) - 与各类 LLM 的高级交互示例
112
+
113
+ ### 支持与反馈
114
+ - [GitHub Issues](https://github.com/donghao1393/mcp-dbutils/issues) - 报告问题或请求功能
115
+ - [Smithery](https://smithery.ai/server/@donghao1393/mcp-dbutils) - 简化安装和更新
116
+
117
+ ## 可用工具
118
+
119
+ MCP 数据库工具提供了多种工具,使 AI 能够与您的数据库交互:
120
+
121
+ - **dbutils-list-connections**:列出配置中的所有可用数据库连接
122
+ - **dbutils-list-tables**:列出数据库中的所有表
123
+ - **dbutils-run-query**:执行 SQL 查询(仅 SELECT)
124
+ - **dbutils-get-stats**:获取表统计信息
125
+ - **dbutils-list-constraints**:列出表约束
126
+ - **dbutils-explain-query**:获取查询执行计划
127
+ - **dbutils-get-performance**:获取数据库性能指标
128
+ - **dbutils-analyze-query**:分析查询以进行优化
129
+
130
+ 有关这些工具的详细说明和使用示例,请参阅[使用指南](docs/zh/usage.md)。
131
+
132
+ ## 星标历史
133
+
134
+ [![星标历史图表](https://starchart.cc/donghao1393/mcp-dbutils.svg?variant=adaptive)](https://starchart.cc/donghao1393/mcp-dbutils)
135
+
136
+ ## 许可证
137
+
138
+ 本项目采用 MIT 许可证 - 有关详细信息,请参阅 [LICENSE](LICENSE) 文件。
@@ -1,22 +1,22 @@
1
1
  mcp_dbutils/__init__.py,sha256=6LLccQv7je2L4IpY_I3OzSJZcK32VUDJv2IY31y6eYg,1900
2
- mcp_dbutils/base.py,sha256=9UDhPFpw6YdkUMhAqgGyy6vuXA2C78LIGrWiW0zeB8s,30839
2
+ mcp_dbutils/base.py,sha256=ptQfJwzYKteGokOrrkIcG3QIlz-f1Afk_XxyEHYcFLI,35957
3
3
  mcp_dbutils/config.py,sha256=bmXpOd1fyYfoyUS75I035ChT6t3wP5AyEnJ06e2ZS2o,1848
4
4
  mcp_dbutils/log.py,sha256=mqxi6I_IL-MF1F_pxBtnYZQKOHbGBJ74gsvZHVelr1w,823
5
5
  mcp_dbutils/stats.py,sha256=wMqWPfGnEOg9v5YBtTsARV-1YsFUMM_pKdzitzSU9x4,7137
6
6
  mcp_dbutils/mysql/__init__.py,sha256=gNhoHaxK1qhvMAH5AVl1vfV1rUpcbV9KZWUQb41aaQk,129
7
7
  mcp_dbutils/mysql/config.py,sha256=BTPPFqlhoTp7EBFIeLJZh8x6bCn3q9NivHYz9yZHziw,9820
8
- mcp_dbutils/mysql/handler.py,sha256=knBoFVYmdse5hsjr4GPi4fZhEaYOPRBPGR2d3w8qqzw,19837
8
+ mcp_dbutils/mysql/handler.py,sha256=L2COmWts8WNQaI7wZSPtQ-BqKnpYL7DRnNk7Yw3UsxU,22285
9
9
  mcp_dbutils/mysql/server.py,sha256=1bWAu7qHYXVeTZu4wdEpS6gSVB0RoXKI3Smy_ix-y8A,8586
10
10
  mcp_dbutils/postgres/__init__.py,sha256=-2zYuEJEQ2AMvmGhH5Z_umerSvt7S4xOa_XV4wgvGfI,154
11
11
  mcp_dbutils/postgres/config.py,sha256=NyQOVhkXJ1S-JD0w-ePNjTKI1Ja-aZQkDUdHi6U7Vl4,7752
12
- mcp_dbutils/postgres/handler.py,sha256=ppltSKtSk-BlPpp3iEVJlmoyl4AmqKcQHx_0zHlz03Y,24403
12
+ mcp_dbutils/postgres/handler.py,sha256=JKnh3QsF5oQ4nS-OdhMC1nWtTp235PArFncFN4pmDZ8,24670
13
13
  mcp_dbutils/postgres/server.py,sha256=_CiJC9PitpI1NB99Q1Bcs5TYADNgDpYMwv88fRHQunE,8640
14
14
  mcp_dbutils/sqlite/__init__.py,sha256=fK_3-WylCBYpBAzwuopi8hlwoIGJm2TPAlwcPWG46I0,134
15
15
  mcp_dbutils/sqlite/config.py,sha256=j67TJ8mQJ2D886MthSa-zYMtvUUYyyxYLMlNxkYoqZE,4509
16
- mcp_dbutils/sqlite/handler.py,sha256=25zqoQpMhRNKeO3MH2a0E5dO3-4A8sPb7q87cn7Cs0E,17746
16
+ mcp_dbutils/sqlite/handler.py,sha256=nCgBeBp3zjpE2HNohMbh3Jpz5tNeMczt5K87JOhVWzY,19320
17
17
  mcp_dbutils/sqlite/server.py,sha256=jqpE8d9vJETMs5xYGB7P0tvNDPes6Yn5ZM_iCCF7Tv4,7181
18
- mcp_dbutils-0.16.1.dist-info/METADATA,sha256=mjE8oqdIRiIdiRlyWufFfxWGoHT8aGIoSaVTEuFlkJs,16714
19
- mcp_dbutils-0.16.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
- mcp_dbutils-0.16.1.dist-info/entry_points.txt,sha256=XTjt0QmYRgKOJQT6skR9bp1EMUfIrgpHeZJPZ3CJffs,49
21
- mcp_dbutils-0.16.1.dist-info/licenses/LICENSE,sha256=1A_CwpWVlbjrKdVEYO77vYfnXlW7oxcilZ8FpA_BzCI,1065
22
- mcp_dbutils-0.16.1.dist-info/RECORD,,
18
+ mcp_dbutils-0.18.0.dist-info/METADATA,sha256=z41vH5CzRdtLPcbzjYJ8HukwkFCPDY4ifAmmfrTh1no,6695
19
+ mcp_dbutils-0.18.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
+ mcp_dbutils-0.18.0.dist-info/entry_points.txt,sha256=XTjt0QmYRgKOJQT6skR9bp1EMUfIrgpHeZJPZ3CJffs,49
21
+ mcp_dbutils-0.18.0.dist-info/licenses/LICENSE,sha256=1A_CwpWVlbjrKdVEYO77vYfnXlW7oxcilZ8FpA_BzCI,1065
22
+ mcp_dbutils-0.18.0.dist-info/RECORD,,