mcp-dbutils 0.15.5__py3-none-any.whl → 0.16.1__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/base.py +441 -323
- mcp_dbutils/mysql/config.py +122 -54
- mcp_dbutils/mysql/handler.py +9 -9
- mcp_dbutils/mysql/server.py +3 -3
- mcp_dbutils/sqlite/handler.py +4 -4
- mcp_dbutils/sqlite/server.py +2 -2
- {mcp_dbutils-0.15.5.dist-info → mcp_dbutils-0.16.1.dist-info}/METADATA +1 -1
- {mcp_dbutils-0.15.5.dist-info → mcp_dbutils-0.16.1.dist-info}/RECORD +11 -11
- {mcp_dbutils-0.15.5.dist-info → mcp_dbutils-0.16.1.dist-info}/WHEEL +0 -0
- {mcp_dbutils-0.15.5.dist-info → mcp_dbutils-0.16.1.dist-info}/entry_points.txt +0 -0
- {mcp_dbutils-0.15.5.dist-info → mcp_dbutils-0.16.1.dist-info}/licenses/LICENSE +0 -0
mcp_dbutils/base.py
CHANGED
@@ -299,6 +299,64 @@ class ConnectionServer:
|
|
299
299
|
self.send_log(LOG_LEVEL_ERROR, f"Error in list_prompts: {str(e)}")
|
300
300
|
raise
|
301
301
|
|
302
|
+
def _get_config_or_raise(self, connection: str) -> dict:
|
303
|
+
"""读取配置文件并验证连接配置
|
304
|
+
|
305
|
+
Args:
|
306
|
+
connection: 连接名称
|
307
|
+
|
308
|
+
Returns:
|
309
|
+
dict: 连接配置
|
310
|
+
|
311
|
+
Raises:
|
312
|
+
ConfigurationError: 如果配置文件格式不正确或连接不存在
|
313
|
+
"""
|
314
|
+
with open(self.config_path, 'r') as f:
|
315
|
+
config = yaml.safe_load(f)
|
316
|
+
if not config or 'connections' not in config:
|
317
|
+
raise ConfigurationError("Configuration file must contain 'connections' section")
|
318
|
+
if connection not in config['connections']:
|
319
|
+
available_connections = list(config['connections'].keys())
|
320
|
+
raise ConfigurationError(f"Connection not found: {connection}. Available connections: {available_connections}")
|
321
|
+
|
322
|
+
db_config = config['connections'][connection]
|
323
|
+
|
324
|
+
if 'type' not in db_config:
|
325
|
+
raise ConfigurationError("Database configuration must include 'type' field")
|
326
|
+
|
327
|
+
return db_config
|
328
|
+
|
329
|
+
def _create_handler_for_type(self, db_type: str, connection: str) -> ConnectionHandler:
|
330
|
+
"""基于数据库类型创建相应的处理器
|
331
|
+
|
332
|
+
Args:
|
333
|
+
db_type: 数据库类型
|
334
|
+
connection: 连接名称
|
335
|
+
|
336
|
+
Returns:
|
337
|
+
ConnectionHandler: 数据库连接处理器
|
338
|
+
|
339
|
+
Raises:
|
340
|
+
ConfigurationError: 如果数据库类型不支持或导入失败
|
341
|
+
"""
|
342
|
+
self.send_log(LOG_LEVEL_DEBUG, f"Creating handler for database type: {db_type}")
|
343
|
+
|
344
|
+
try:
|
345
|
+
if db_type == 'sqlite':
|
346
|
+
from .sqlite.handler import SQLiteHandler
|
347
|
+
return SQLiteHandler(self.config_path, connection, self.debug)
|
348
|
+
elif db_type == 'postgres':
|
349
|
+
from .postgres.handler import PostgreSQLHandler
|
350
|
+
return PostgreSQLHandler(self.config_path, connection, self.debug)
|
351
|
+
elif db_type == 'mysql':
|
352
|
+
from .mysql.handler import MySQLHandler
|
353
|
+
return MySQLHandler(self.config_path, connection, self.debug)
|
354
|
+
else:
|
355
|
+
raise ConfigurationError(f"Unsupported database type: {db_type}")
|
356
|
+
except ImportError as e:
|
357
|
+
# 捕获导入错误并转换为ConfigurationError,以保持与现有测试兼容
|
358
|
+
raise ConfigurationError(f"Failed to import handler for {db_type}: {str(e)}")
|
359
|
+
|
302
360
|
@asynccontextmanager
|
303
361
|
async def get_handler(self, connection: str) -> AsyncContextManager[ConnectionHandler]:
|
304
362
|
"""Get connection handler
|
@@ -311,74 +369,385 @@ class ConnectionServer:
|
|
311
369
|
Returns:
|
312
370
|
AsyncContextManager[ConnectionHandler]: Context manager for connection handler
|
313
371
|
"""
|
314
|
-
# Read configuration file
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
372
|
+
# Read configuration file and validate connection
|
373
|
+
db_config = self._get_config_or_raise(connection)
|
374
|
+
|
375
|
+
# Create appropriate handler based on database type
|
376
|
+
handler = None
|
377
|
+
try:
|
378
|
+
db_type = db_config['type']
|
379
|
+
handler = self._create_handler_for_type(db_type, connection)
|
322
380
|
|
323
|
-
|
381
|
+
# Set session for MCP logging
|
382
|
+
if hasattr(self.server, 'session'):
|
383
|
+
handler._session = self.server.session
|
324
384
|
|
325
|
-
handler
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
self.send_log(LOG_LEVEL_DEBUG, f"
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
385
|
+
handler.stats.record_connection_start()
|
386
|
+
self.send_log(LOG_LEVEL_DEBUG, f"Handler created successfully for {connection}")
|
387
|
+
|
388
|
+
yield handler
|
389
|
+
finally:
|
390
|
+
if handler:
|
391
|
+
self.send_log(LOG_LEVEL_DEBUG, f"Cleaning up handler for {connection}")
|
392
|
+
handler.stats.record_connection_end()
|
393
|
+
|
394
|
+
if hasattr(handler, 'cleanup') and callable(handler.cleanup):
|
395
|
+
await handler.cleanup()
|
396
|
+
|
397
|
+
def _get_available_tools(self) -> list[types.Tool]:
|
398
|
+
"""返回所有可用的数据库工具列表
|
399
|
+
|
400
|
+
Returns:
|
401
|
+
list[types.Tool]: 工具列表
|
402
|
+
"""
|
403
|
+
return [
|
404
|
+
types.Tool(
|
405
|
+
name="dbutils-run-query",
|
406
|
+
description="Execute read-only SQL query on database connection",
|
407
|
+
inputSchema={
|
408
|
+
"type": "object",
|
409
|
+
"properties": {
|
410
|
+
"connection": {
|
411
|
+
"type": "string",
|
412
|
+
"description": DATABASE_CONNECTION_NAME
|
413
|
+
},
|
414
|
+
"sql": {
|
415
|
+
"type": "string",
|
416
|
+
"description": "SQL query (SELECT only)"
|
417
|
+
}
|
418
|
+
},
|
419
|
+
"required": ["connection", "sql"]
|
420
|
+
}
|
421
|
+
),
|
422
|
+
types.Tool(
|
423
|
+
name="dbutils-list-tables",
|
424
|
+
description="List all available tables in the specified database connection",
|
425
|
+
inputSchema={
|
426
|
+
"type": "object",
|
427
|
+
"properties": {
|
428
|
+
"connection": {
|
429
|
+
"type": "string",
|
430
|
+
"description": DATABASE_CONNECTION_NAME
|
431
|
+
}
|
432
|
+
},
|
433
|
+
"required": ["connection"]
|
434
|
+
}
|
435
|
+
),
|
436
|
+
types.Tool(
|
437
|
+
name="dbutils-describe-table",
|
438
|
+
description="Get detailed information about a table's structure",
|
439
|
+
inputSchema={
|
440
|
+
"type": "object",
|
441
|
+
"properties": {
|
442
|
+
"connection": {
|
443
|
+
"type": "string",
|
444
|
+
"description": DATABASE_CONNECTION_NAME
|
445
|
+
},
|
446
|
+
"table": {
|
447
|
+
"type": "string",
|
448
|
+
"description": "Table name to describe"
|
449
|
+
}
|
450
|
+
},
|
451
|
+
"required": ["connection", "table"]
|
452
|
+
}
|
453
|
+
),
|
454
|
+
types.Tool(
|
455
|
+
name="dbutils-get-ddl",
|
456
|
+
description="Get DDL statement for creating the table",
|
457
|
+
inputSchema={
|
458
|
+
"type": "object",
|
459
|
+
"properties": {
|
460
|
+
"connection": {
|
461
|
+
"type": "string",
|
462
|
+
"description": DATABASE_CONNECTION_NAME
|
463
|
+
},
|
464
|
+
"table": {
|
465
|
+
"type": "string",
|
466
|
+
"description": "Table name to get DDL for"
|
467
|
+
}
|
468
|
+
},
|
469
|
+
"required": ["connection", "table"]
|
470
|
+
}
|
471
|
+
),
|
472
|
+
types.Tool(
|
473
|
+
name="dbutils-list-indexes",
|
474
|
+
description="List all indexes on the specified table",
|
475
|
+
inputSchema={
|
476
|
+
"type": "object",
|
477
|
+
"properties": {
|
478
|
+
"connection": {
|
479
|
+
"type": "string",
|
480
|
+
"description": DATABASE_CONNECTION_NAME
|
481
|
+
},
|
482
|
+
"table": {
|
483
|
+
"type": "string",
|
484
|
+
"description": "Table name to list indexes for"
|
485
|
+
}
|
486
|
+
},
|
487
|
+
"required": ["connection", "table"]
|
488
|
+
}
|
489
|
+
),
|
490
|
+
types.Tool(
|
491
|
+
name="dbutils-get-stats",
|
492
|
+
description="Get table statistics like row count and size",
|
493
|
+
inputSchema={
|
494
|
+
"type": "object",
|
495
|
+
"properties": {
|
496
|
+
"connection": {
|
497
|
+
"type": "string",
|
498
|
+
"description": DATABASE_CONNECTION_NAME
|
499
|
+
},
|
500
|
+
"table": {
|
501
|
+
"type": "string",
|
502
|
+
"description": "Table name to get statistics for"
|
503
|
+
}
|
504
|
+
},
|
505
|
+
"required": ["connection", "table"]
|
506
|
+
}
|
507
|
+
),
|
508
|
+
types.Tool(
|
509
|
+
name="dbutils-list-constraints",
|
510
|
+
description="List all constraints (primary key, foreign keys, etc) on the table",
|
511
|
+
inputSchema={
|
512
|
+
"type": "object",
|
513
|
+
"properties": {
|
514
|
+
"connection": {
|
515
|
+
"type": "string",
|
516
|
+
"description": DATABASE_CONNECTION_NAME
|
517
|
+
},
|
518
|
+
"table": {
|
519
|
+
"type": "string",
|
520
|
+
"description": "Table name to list constraints for"
|
521
|
+
}
|
522
|
+
},
|
523
|
+
"required": ["connection", "table"]
|
524
|
+
}
|
525
|
+
),
|
526
|
+
types.Tool(
|
527
|
+
name="dbutils-explain-query",
|
528
|
+
description="Get execution plan for a SQL query",
|
529
|
+
inputSchema={
|
530
|
+
"type": "object",
|
531
|
+
"properties": {
|
532
|
+
"connection": {
|
533
|
+
"type": "string",
|
534
|
+
"description": DATABASE_CONNECTION_NAME
|
535
|
+
},
|
536
|
+
"sql": {
|
537
|
+
"type": "string",
|
538
|
+
"description": "SQL query to explain"
|
539
|
+
}
|
540
|
+
},
|
541
|
+
"required": ["connection", "sql"]
|
542
|
+
}
|
543
|
+
),
|
544
|
+
types.Tool(
|
545
|
+
name="dbutils-get-performance",
|
546
|
+
description="Get database performance statistics",
|
547
|
+
inputSchema={
|
548
|
+
"type": "object",
|
549
|
+
"properties": {
|
550
|
+
"connection": {
|
551
|
+
"type": "string",
|
552
|
+
"description": DATABASE_CONNECTION_NAME
|
553
|
+
}
|
554
|
+
},
|
555
|
+
"required": ["connection"]
|
556
|
+
}
|
557
|
+
),
|
558
|
+
types.Tool(
|
559
|
+
name="dbutils-analyze-query",
|
560
|
+
description="Analyze a SQL query for performance",
|
561
|
+
inputSchema={
|
562
|
+
"type": "object",
|
563
|
+
"properties": {
|
564
|
+
"connection": {
|
565
|
+
"type": "string",
|
566
|
+
"description": DATABASE_CONNECTION_NAME
|
567
|
+
},
|
568
|
+
"sql": {
|
569
|
+
"type": "string",
|
570
|
+
"description": "SQL query to analyze"
|
571
|
+
}
|
572
|
+
},
|
573
|
+
"required": ["connection", "sql"]
|
574
|
+
}
|
575
|
+
)
|
576
|
+
]
|
577
|
+
|
578
|
+
async def _handle_list_tables(self, connection: str) -> list[types.TextContent]:
|
579
|
+
"""处理列表表格工具调用
|
580
|
+
|
581
|
+
Args:
|
582
|
+
connection: 数据库连接名称
|
583
|
+
|
584
|
+
Returns:
|
585
|
+
list[types.TextContent]: 表格列表
|
586
|
+
"""
|
587
|
+
async with self.get_handler(connection) as handler:
|
588
|
+
tables = await handler.get_tables()
|
589
|
+
if not tables:
|
590
|
+
# 空表列表的情况也返回数据库类型
|
591
|
+
return [types.TextContent(type="text", text=f"[{handler.db_type}] No tables found")]
|
592
|
+
|
593
|
+
formatted_tables = "\n".join([
|
594
|
+
f"Table: {table.name}\n" +
|
595
|
+
f"URI: {table.uri}\n" +
|
596
|
+
(f"Description: {table.description}\n" if table.description else "") +
|
597
|
+
"---"
|
598
|
+
for table in tables
|
599
|
+
])
|
600
|
+
# 添加数据库类型前缀
|
601
|
+
return [types.TextContent(type="text", text=f"[{handler.db_type}]\n{formatted_tables}")]
|
602
|
+
|
603
|
+
async def _handle_run_query(self, connection: str, sql: str) -> list[types.TextContent]:
|
604
|
+
"""处理运行查询工具调用
|
605
|
+
|
606
|
+
Args:
|
607
|
+
connection: 数据库连接名称
|
608
|
+
sql: SQL查询语句
|
609
|
+
|
610
|
+
Returns:
|
611
|
+
list[types.TextContent]: 查询结果
|
612
|
+
|
613
|
+
Raises:
|
614
|
+
ConfigurationError: 如果SQL为空或非SELECT语句
|
615
|
+
"""
|
616
|
+
if not sql:
|
617
|
+
raise ConfigurationError(EMPTY_QUERY_ERROR)
|
618
|
+
|
619
|
+
# Only allow SELECT statements
|
620
|
+
if not sql.lower().startswith("select"):
|
621
|
+
raise ConfigurationError(SELECT_ONLY_ERROR)
|
622
|
+
|
623
|
+
async with self.get_handler(connection) as handler:
|
624
|
+
result = await handler.execute_query(sql)
|
625
|
+
return [types.TextContent(type="text", text=result)]
|
626
|
+
|
627
|
+
async def _handle_table_tools(self, name: str, connection: str, table: str) -> list[types.TextContent]:
|
628
|
+
"""处理表相关工具调用
|
629
|
+
|
630
|
+
Args:
|
631
|
+
name: 工具名称
|
632
|
+
connection: 数据库连接名称
|
633
|
+
table: 表名
|
634
|
+
|
635
|
+
Returns:
|
636
|
+
list[types.TextContent]: 工具执行结果
|
637
|
+
|
638
|
+
Raises:
|
639
|
+
ConfigurationError: 如果表名为空
|
640
|
+
"""
|
641
|
+
if not table:
|
642
|
+
raise ConfigurationError(EMPTY_TABLE_NAME_ERROR)
|
643
|
+
|
644
|
+
async with self.get_handler(connection) as handler:
|
645
|
+
result = await handler.execute_tool_query(name, table_name=table)
|
646
|
+
return [types.TextContent(type="text", text=result)]
|
647
|
+
|
648
|
+
async def _handle_explain_query(self, connection: str, sql: str) -> list[types.TextContent]:
|
649
|
+
"""处理解释查询工具调用
|
650
|
+
|
651
|
+
Args:
|
652
|
+
connection: 数据库连接名称
|
653
|
+
sql: SQL查询语句
|
654
|
+
|
655
|
+
Returns:
|
656
|
+
list[types.TextContent]: 查询解释
|
657
|
+
|
658
|
+
Raises:
|
659
|
+
ConfigurationError: 如果SQL为空
|
660
|
+
"""
|
661
|
+
if not sql:
|
662
|
+
raise ConfigurationError(EMPTY_QUERY_ERROR)
|
663
|
+
|
664
|
+
async with self.get_handler(connection) as handler:
|
665
|
+
result = await handler.execute_tool_query("dbutils-explain-query", sql=sql)
|
666
|
+
return [types.TextContent(type="text", text=result)]
|
667
|
+
|
668
|
+
async def _handle_performance(self, connection: str) -> list[types.TextContent]:
|
669
|
+
"""处理性能统计工具调用
|
670
|
+
|
671
|
+
Args:
|
672
|
+
connection: 数据库连接名称
|
673
|
+
|
674
|
+
Returns:
|
675
|
+
list[types.TextContent]: 性能统计
|
676
|
+
"""
|
677
|
+
async with self.get_handler(connection) as handler:
|
678
|
+
performance_stats = handler.stats.get_performance_stats()
|
679
|
+
return [types.TextContent(type="text", text=f"[{handler.db_type}]\n{performance_stats}")]
|
680
|
+
|
681
|
+
async def _handle_analyze_query(self, connection: str, sql: str) -> list[types.TextContent]:
|
682
|
+
"""处理查询分析工具调用
|
683
|
+
|
684
|
+
Args:
|
685
|
+
connection: 数据库连接名称
|
686
|
+
sql: SQL查询语句
|
687
|
+
|
688
|
+
Returns:
|
689
|
+
list[types.TextContent]: 查询分析结果
|
690
|
+
|
691
|
+
Raises:
|
692
|
+
ConfigurationError: 如果SQL为空
|
693
|
+
"""
|
694
|
+
if not sql:
|
695
|
+
raise ConfigurationError(EMPTY_QUERY_ERROR)
|
696
|
+
|
697
|
+
async with self.get_handler(connection) as handler:
|
698
|
+
# First get the execution plan
|
699
|
+
explain_result = await handler.explain_query(sql)
|
700
|
+
|
701
|
+
# Then execute the actual query to measure performance
|
702
|
+
start_time = datetime.now()
|
703
|
+
if sql.lower().startswith("select"):
|
351
704
|
try:
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
705
|
+
await handler.execute_query(sql)
|
706
|
+
except Exception as e:
|
707
|
+
# If query fails, we still provide the execution plan
|
708
|
+
self.send_log(LOG_LEVEL_ERROR, f"Query execution failed during analysis: {str(e)}")
|
709
|
+
duration = (datetime.now() - start_time).total_seconds()
|
710
|
+
|
711
|
+
# Combine analysis results
|
712
|
+
analysis = [
|
713
|
+
f"[{handler.db_type}] Query Analysis",
|
714
|
+
f"SQL: {sql}",
|
715
|
+
"",
|
716
|
+
f"Execution Time: {duration*1000:.2f}ms",
|
717
|
+
"",
|
718
|
+
"Execution Plan:",
|
719
|
+
explain_result
|
720
|
+
]
|
721
|
+
|
722
|
+
# Add optimization suggestions
|
723
|
+
suggestions = self._get_optimization_suggestions(explain_result, duration)
|
724
|
+
if suggestions:
|
725
|
+
analysis.append("\nOptimization Suggestions:")
|
726
|
+
analysis.extend(suggestions)
|
727
|
+
|
728
|
+
return [types.TextContent(type="text", text="\n".join(analysis))]
|
729
|
+
|
730
|
+
def _get_optimization_suggestions(self, explain_result: str, duration: float) -> list[str]:
|
731
|
+
"""根据执行计划和耗时获取优化建议
|
732
|
+
|
733
|
+
Args:
|
734
|
+
explain_result: 执行计划
|
735
|
+
duration: 查询耗时(秒)
|
736
|
+
|
737
|
+
Returns:
|
738
|
+
list[str]: 优化建议列表
|
739
|
+
"""
|
740
|
+
suggestions = []
|
741
|
+
if "seq scan" in explain_result.lower() and duration > 0.1:
|
742
|
+
suggestions.append("- Consider adding an index to avoid sequential scan")
|
743
|
+
if "hash join" in explain_result.lower() and duration > 0.5:
|
744
|
+
suggestions.append("- Consider optimizing join conditions")
|
745
|
+
if duration > 0.5: # 500ms
|
746
|
+
suggestions.append("- Query is slow, consider optimizing or adding caching")
|
747
|
+
if "temporary" in explain_result.lower():
|
748
|
+
suggestions.append("- Query creates temporary tables, consider restructuring")
|
749
|
+
|
750
|
+
return suggestions
|
382
751
|
|
383
752
|
def _setup_handlers(self):
|
384
753
|
"""Setup MCP handlers"""
|
@@ -409,180 +778,7 @@ class ConnectionServer:
|
|
409
778
|
|
410
779
|
@self.server.list_tools()
|
411
780
|
async def handle_list_tools() -> list[types.Tool]:
|
412
|
-
return
|
413
|
-
types.Tool(
|
414
|
-
name="dbutils-run-query",
|
415
|
-
description="Execute read-only SQL query on database connection",
|
416
|
-
inputSchema={
|
417
|
-
"type": "object",
|
418
|
-
"properties": {
|
419
|
-
"connection": {
|
420
|
-
"type": "string",
|
421
|
-
"description": DATABASE_CONNECTION_NAME
|
422
|
-
},
|
423
|
-
"sql": {
|
424
|
-
"type": "string",
|
425
|
-
"description": "SQL query (SELECT only)"
|
426
|
-
}
|
427
|
-
},
|
428
|
-
"required": ["connection", "sql"]
|
429
|
-
}
|
430
|
-
),
|
431
|
-
types.Tool(
|
432
|
-
name="dbutils-list-tables",
|
433
|
-
description="List all available tables in the specified database connection",
|
434
|
-
inputSchema={
|
435
|
-
"type": "object",
|
436
|
-
"properties": {
|
437
|
-
"connection": {
|
438
|
-
"type": "string",
|
439
|
-
"description": DATABASE_CONNECTION_NAME
|
440
|
-
}
|
441
|
-
},
|
442
|
-
"required": ["connection"]
|
443
|
-
}
|
444
|
-
),
|
445
|
-
types.Tool(
|
446
|
-
name="dbutils-describe-table",
|
447
|
-
description="Get detailed information about a table's structure",
|
448
|
-
inputSchema={
|
449
|
-
"type": "object",
|
450
|
-
"properties": {
|
451
|
-
"connection": {
|
452
|
-
"type": "string",
|
453
|
-
"description": DATABASE_CONNECTION_NAME
|
454
|
-
},
|
455
|
-
"table": {
|
456
|
-
"type": "string",
|
457
|
-
"description": "Table name to describe"
|
458
|
-
}
|
459
|
-
},
|
460
|
-
"required": ["connection", "table"]
|
461
|
-
}
|
462
|
-
),
|
463
|
-
types.Tool(
|
464
|
-
name="dbutils-get-ddl",
|
465
|
-
description="Get DDL statement for creating the table",
|
466
|
-
inputSchema={
|
467
|
-
"type": "object",
|
468
|
-
"properties": {
|
469
|
-
"connection": {
|
470
|
-
"type": "string",
|
471
|
-
"description": DATABASE_CONNECTION_NAME
|
472
|
-
},
|
473
|
-
"table": {
|
474
|
-
"type": "string",
|
475
|
-
"description": "Table name to get DDL for"
|
476
|
-
}
|
477
|
-
},
|
478
|
-
"required": ["connection", "table"]
|
479
|
-
}
|
480
|
-
),
|
481
|
-
types.Tool(
|
482
|
-
name="dbutils-list-indexes",
|
483
|
-
description="List all indexes on the specified table",
|
484
|
-
inputSchema={
|
485
|
-
"type": "object",
|
486
|
-
"properties": {
|
487
|
-
"connection": {
|
488
|
-
"type": "string",
|
489
|
-
"description": DATABASE_CONNECTION_NAME
|
490
|
-
},
|
491
|
-
"table": {
|
492
|
-
"type": "string",
|
493
|
-
"description": "Table name to list indexes for"
|
494
|
-
}
|
495
|
-
},
|
496
|
-
"required": ["connection", "table"]
|
497
|
-
}
|
498
|
-
),
|
499
|
-
types.Tool(
|
500
|
-
name="dbutils-get-stats",
|
501
|
-
description="Get table statistics like row count and size",
|
502
|
-
inputSchema={
|
503
|
-
"type": "object",
|
504
|
-
"properties": {
|
505
|
-
"connection": {
|
506
|
-
"type": "string",
|
507
|
-
"description": DATABASE_CONNECTION_NAME
|
508
|
-
},
|
509
|
-
"table": {
|
510
|
-
"type": "string",
|
511
|
-
"description": "Table name to get statistics for"
|
512
|
-
}
|
513
|
-
},
|
514
|
-
"required": ["connection", "table"]
|
515
|
-
}
|
516
|
-
),
|
517
|
-
types.Tool(
|
518
|
-
name="dbutils-list-constraints",
|
519
|
-
description="List all constraints (primary key, foreign keys, etc) on the table",
|
520
|
-
inputSchema={
|
521
|
-
"type": "object",
|
522
|
-
"properties": {
|
523
|
-
"connection": {
|
524
|
-
"type": "string",
|
525
|
-
"description": DATABASE_CONNECTION_NAME
|
526
|
-
},
|
527
|
-
"table": {
|
528
|
-
"type": "string",
|
529
|
-
"description": "Table name to list constraints for"
|
530
|
-
}
|
531
|
-
},
|
532
|
-
"required": ["connection", "table"]
|
533
|
-
}
|
534
|
-
),
|
535
|
-
types.Tool(
|
536
|
-
name="dbutils-explain-query",
|
537
|
-
description="Get execution plan for a SQL query",
|
538
|
-
inputSchema={
|
539
|
-
"type": "object",
|
540
|
-
"properties": {
|
541
|
-
"connection": {
|
542
|
-
"type": "string",
|
543
|
-
"description": DATABASE_CONNECTION_NAME
|
544
|
-
},
|
545
|
-
"sql": {
|
546
|
-
"type": "string",
|
547
|
-
"description": "SQL query to explain"
|
548
|
-
}
|
549
|
-
},
|
550
|
-
"required": ["connection", "sql"]
|
551
|
-
}
|
552
|
-
),
|
553
|
-
types.Tool(
|
554
|
-
name="dbutils-get-performance",
|
555
|
-
description="Get database performance statistics",
|
556
|
-
inputSchema={
|
557
|
-
"type": "object",
|
558
|
-
"properties": {
|
559
|
-
"connection": {
|
560
|
-
"type": "string",
|
561
|
-
"description": DATABASE_CONNECTION_NAME
|
562
|
-
}
|
563
|
-
},
|
564
|
-
"required": ["connection"]
|
565
|
-
}
|
566
|
-
),
|
567
|
-
types.Tool(
|
568
|
-
name="dbutils-analyze-query",
|
569
|
-
description="Analyze a SQL query for performance",
|
570
|
-
inputSchema={
|
571
|
-
"type": "object",
|
572
|
-
"properties": {
|
573
|
-
"connection": {
|
574
|
-
"type": "string",
|
575
|
-
"description": DATABASE_CONNECTION_NAME
|
576
|
-
},
|
577
|
-
"sql": {
|
578
|
-
"type": "string",
|
579
|
-
"description": "SQL query to analyze"
|
580
|
-
}
|
581
|
-
},
|
582
|
-
"required": ["connection", "sql"]
|
583
|
-
}
|
584
|
-
)
|
585
|
-
]
|
781
|
+
return self._get_available_tools()
|
586
782
|
|
587
783
|
@self.server.call_tool()
|
588
784
|
async def handle_call_tool(name: str, arguments: dict) -> list[types.TextContent]:
|
@@ -592,100 +788,22 @@ class ConnectionServer:
|
|
592
788
|
connection = arguments["connection"]
|
593
789
|
|
594
790
|
if name == "dbutils-list-tables":
|
595
|
-
|
596
|
-
tables = await handler.get_tables()
|
597
|
-
if not tables:
|
598
|
-
# 空表列表的情况也返回数据库类型
|
599
|
-
return [types.TextContent(type="text", text=f"[{handler.db_type}] No tables found")]
|
600
|
-
|
601
|
-
formatted_tables = "\n".join([
|
602
|
-
f"Table: {table.name}\n" +
|
603
|
-
f"URI: {table.uri}\n" +
|
604
|
-
(f"Description: {table.description}\n" if table.description else "") +
|
605
|
-
"---"
|
606
|
-
for table in tables
|
607
|
-
])
|
608
|
-
# 添加数据库类型前缀
|
609
|
-
return [types.TextContent(type="text", text=f"[{handler.db_type}]\n{formatted_tables}")]
|
791
|
+
return await self._handle_list_tables(connection)
|
610
792
|
elif name == "dbutils-run-query":
|
611
793
|
sql = arguments.get("sql", "").strip()
|
612
|
-
|
613
|
-
raise ConfigurationError(EMPTY_QUERY_ERROR)
|
614
|
-
|
615
|
-
# Only allow SELECT statements
|
616
|
-
if not sql.lower().startswith("select"):
|
617
|
-
raise ConfigurationError(SELECT_ONLY_ERROR)
|
618
|
-
|
619
|
-
async with self.get_handler(connection) as handler:
|
620
|
-
result = await handler.execute_query(sql)
|
621
|
-
return [types.TextContent(type="text", text=result)]
|
794
|
+
return await self._handle_run_query(connection, sql)
|
622
795
|
elif name in ["dbutils-describe-table", "dbutils-get-ddl", "dbutils-list-indexes",
|
623
796
|
"dbutils-get-stats", "dbutils-list-constraints"]:
|
624
797
|
table = arguments.get("table", "").strip()
|
625
|
-
|
626
|
-
raise ConfigurationError(EMPTY_TABLE_NAME_ERROR)
|
627
|
-
|
628
|
-
async with self.get_handler(connection) as handler:
|
629
|
-
result = await handler.execute_tool_query(name, table_name=table)
|
630
|
-
return [types.TextContent(type="text", text=result)]
|
798
|
+
return await self._handle_table_tools(name, connection, table)
|
631
799
|
elif name == "dbutils-explain-query":
|
632
800
|
sql = arguments.get("sql", "").strip()
|
633
|
-
|
634
|
-
raise ConfigurationError(EMPTY_QUERY_ERROR)
|
635
|
-
|
636
|
-
async with self.get_handler(connection) as handler:
|
637
|
-
result = await handler.execute_tool_query(name, sql=sql)
|
638
|
-
return [types.TextContent(type="text", text=result)]
|
801
|
+
return await self._handle_explain_query(connection, sql)
|
639
802
|
elif name == "dbutils-get-performance":
|
640
|
-
|
641
|
-
performance_stats = handler.stats.get_performance_stats()
|
642
|
-
return [types.TextContent(type="text", text=f"[{handler.db_type}]\n{performance_stats}")]
|
803
|
+
return await self._handle_performance(connection)
|
643
804
|
elif name == "dbutils-analyze-query":
|
644
805
|
sql = arguments.get("sql", "").strip()
|
645
|
-
|
646
|
-
raise ConfigurationError(EMPTY_QUERY_ERROR)
|
647
|
-
|
648
|
-
async with self.get_handler(connection) as handler:
|
649
|
-
# First get the execution plan
|
650
|
-
explain_result = await handler.explain_query(sql)
|
651
|
-
|
652
|
-
# Then execute the actual query to measure performance
|
653
|
-
start_time = datetime.now()
|
654
|
-
if sql.lower().startswith("select"):
|
655
|
-
try:
|
656
|
-
await handler.execute_query(sql)
|
657
|
-
except Exception as e:
|
658
|
-
# If query fails, we still provide the execution plan
|
659
|
-
self.send_log(LOG_LEVEL_ERROR, f"Query execution failed during analysis: {str(e)}")
|
660
|
-
duration = (datetime.now() - start_time).total_seconds()
|
661
|
-
|
662
|
-
# Combine analysis results
|
663
|
-
analysis = [
|
664
|
-
f"[{handler.db_type}] Query Analysis",
|
665
|
-
f"SQL: {sql}",
|
666
|
-
"",
|
667
|
-
f"Execution Time: {duration*1000:.2f}ms",
|
668
|
-
"",
|
669
|
-
"Execution Plan:",
|
670
|
-
explain_result
|
671
|
-
]
|
672
|
-
|
673
|
-
# Add optimization suggestions based on execution plan and timing
|
674
|
-
suggestions = []
|
675
|
-
if "seq scan" in explain_result.lower() and duration > 0.1:
|
676
|
-
suggestions.append("- Consider adding an index to avoid sequential scan")
|
677
|
-
if "hash join" in explain_result.lower() and duration > 0.5:
|
678
|
-
suggestions.append("- Consider optimizing join conditions")
|
679
|
-
if duration > 0.5: # 500ms
|
680
|
-
suggestions.append("- Query is slow, consider optimizing or adding caching")
|
681
|
-
if "temporary" in explain_result.lower():
|
682
|
-
suggestions.append("- Query creates temporary tables, consider restructuring")
|
683
|
-
|
684
|
-
if suggestions:
|
685
|
-
analysis.append("\nOptimization Suggestions:")
|
686
|
-
analysis.extend(suggestions)
|
687
|
-
|
688
|
-
return [types.TextContent(type="text", text="\n".join(analysis))]
|
806
|
+
return await self._handle_analyze_query(connection, sql)
|
689
807
|
else:
|
690
808
|
raise ConfigurationError(f"Unknown tool: {name}")
|
691
809
|
|
mcp_dbutils/mysql/config.py
CHANGED
@@ -77,15 +77,19 @@ class MySQLConfig(ConnectionConfig):
|
|
77
77
|
ssl: Optional[SSLConfig] = None
|
78
78
|
|
79
79
|
@classmethod
|
80
|
-
def
|
81
|
-
"""
|
82
|
-
|
80
|
+
def _validate_connection_config(cls, configs: dict, db_name: str) -> dict:
|
81
|
+
"""验证连接配置是否有效
|
82
|
+
|
83
83
|
Args:
|
84
|
-
|
85
|
-
db_name:
|
86
|
-
|
84
|
+
configs: 配置字典
|
85
|
+
db_name: 连接名称
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
dict: 数据库配置
|
89
|
+
|
90
|
+
Raises:
|
91
|
+
ValueError: 如果配置无效
|
87
92
|
"""
|
88
|
-
configs = cls.load_yaml_config(yaml_path)
|
89
93
|
if not db_name:
|
90
94
|
raise ValueError("Connection name must be specified")
|
91
95
|
if db_name not in configs:
|
@@ -103,57 +107,121 @@ class MySQLConfig(ConnectionConfig):
|
|
103
107
|
raise ValueError("User must be specified in connection configuration")
|
104
108
|
if not db_config.get('password'):
|
105
109
|
raise ValueError("Password must be specified in connection configuration")
|
110
|
+
|
111
|
+
return db_config
|
112
|
+
|
113
|
+
@classmethod
|
114
|
+
def _create_config_from_url(cls, db_config: dict, local_host: Optional[str] = None) -> 'MySQLConfig':
|
115
|
+
"""从URL创建配置
|
116
|
+
|
117
|
+
Args:
|
118
|
+
db_config: 数据库配置
|
119
|
+
local_host: 可选的本地主机地址
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
MySQLConfig: 配置对象
|
123
|
+
"""
|
124
|
+
# Parse URL for connection parameters
|
125
|
+
params = parse_url(db_config['url'])
|
126
|
+
config = cls(
|
127
|
+
database=params['database'],
|
128
|
+
user=db_config['user'],
|
129
|
+
password=db_config['password'],
|
130
|
+
host=params['host'],
|
131
|
+
port=params['port'],
|
132
|
+
charset=params['charset'],
|
133
|
+
local_host=local_host,
|
134
|
+
url=db_config['url'],
|
135
|
+
ssl=params.get('ssl')
|
136
|
+
)
|
137
|
+
return config
|
138
|
+
|
139
|
+
@classmethod
|
140
|
+
def _create_config_from_params(cls, db_config: dict, local_host: Optional[str] = None) -> 'MySQLConfig':
|
141
|
+
"""从参数创建配置
|
142
|
+
|
143
|
+
Args:
|
144
|
+
db_config: 数据库配置
|
145
|
+
local_host: 可选的本地主机地址
|
146
|
+
|
147
|
+
Returns:
|
148
|
+
MySQLConfig: 配置对象
|
149
|
+
|
150
|
+
Raises:
|
151
|
+
ValueError: 如果缺少必需参数或SSL配置无效
|
152
|
+
"""
|
153
|
+
if not db_config.get('database'):
|
154
|
+
raise ValueError("MySQL database name must be specified in configuration")
|
155
|
+
if not db_config.get('host'):
|
156
|
+
raise ValueError("Host must be specified in connection configuration")
|
157
|
+
if not db_config.get('port'):
|
158
|
+
raise ValueError("Port must be specified in connection configuration")
|
159
|
+
|
160
|
+
# Parse SSL configuration if present
|
161
|
+
ssl_config = cls._parse_ssl_config(db_config)
|
162
|
+
|
163
|
+
config = cls(
|
164
|
+
database=db_config['database'],
|
165
|
+
user=db_config['user'],
|
166
|
+
password=db_config['password'],
|
167
|
+
host=db_config['host'],
|
168
|
+
port=str(db_config['port']),
|
169
|
+
charset=db_config.get('charset', 'utf8mb4'),
|
170
|
+
local_host=local_host,
|
171
|
+
ssl=ssl_config
|
172
|
+
)
|
173
|
+
return config
|
174
|
+
|
175
|
+
@classmethod
|
176
|
+
def _parse_ssl_config(cls, db_config: dict) -> Optional[SSLConfig]:
|
177
|
+
"""解析SSL配置
|
178
|
+
|
179
|
+
Args:
|
180
|
+
db_config: 数据库配置
|
181
|
+
|
182
|
+
Returns:
|
183
|
+
Optional[SSLConfig]: SSL配置或None
|
184
|
+
|
185
|
+
Raises:
|
186
|
+
ValueError: 如果SSL配置无效
|
187
|
+
"""
|
188
|
+
if 'ssl' not in db_config:
|
189
|
+
return None
|
190
|
+
|
191
|
+
ssl_params = db_config['ssl']
|
192
|
+
if not isinstance(ssl_params, dict):
|
193
|
+
raise ValueError("SSL configuration must be a dictionary")
|
194
|
+
|
195
|
+
if ssl_params.get('mode') not in [None, 'disabled', 'required', 'verify_ca', 'verify_identity']:
|
196
|
+
raise ValueError(f"Invalid ssl-mode: {ssl_params.get('mode')}")
|
197
|
+
|
198
|
+
return SSLConfig(
|
199
|
+
mode=ssl_params.get('mode', 'disabled'),
|
200
|
+
ca=ssl_params.get('ca'),
|
201
|
+
cert=ssl_params.get('cert'),
|
202
|
+
key=ssl_params.get('key')
|
203
|
+
)
|
106
204
|
|
107
|
-
|
205
|
+
@classmethod
|
206
|
+
def from_yaml(cls, yaml_path: str, db_name: str, local_host: Optional[str] = None) -> 'MySQLConfig':
|
207
|
+
"""Create configuration from YAML file
|
208
|
+
|
209
|
+
Args:
|
210
|
+
yaml_path: Path to YAML configuration file
|
211
|
+
db_name: Connection configuration name to use
|
212
|
+
local_host: Optional local host address
|
213
|
+
"""
|
214
|
+
configs = cls.load_yaml_config(yaml_path)
|
215
|
+
|
216
|
+
# Validate connection config
|
217
|
+
db_config = cls._validate_connection_config(configs, db_name)
|
218
|
+
|
219
|
+
# Create configuration based on URL or parameters
|
108
220
|
if 'url' in db_config:
|
109
|
-
|
110
|
-
params = parse_url(db_config['url'])
|
111
|
-
config = cls(
|
112
|
-
database=params['database'],
|
113
|
-
user=db_config['user'],
|
114
|
-
password=db_config['password'],
|
115
|
-
host=params['host'],
|
116
|
-
port=params['port'],
|
117
|
-
charset=params['charset'],
|
118
|
-
local_host=local_host,
|
119
|
-
url=db_config['url'],
|
120
|
-
ssl=params.get('ssl')
|
121
|
-
)
|
221
|
+
config = cls._create_config_from_url(db_config, local_host)
|
122
222
|
else:
|
123
|
-
|
124
|
-
raise ValueError("MySQL database name must be specified in configuration")
|
125
|
-
if not db_config.get('host'):
|
126
|
-
raise ValueError("Host must be specified in connection configuration")
|
127
|
-
if not db_config.get('port'):
|
128
|
-
raise ValueError("Port must be specified in connection configuration")
|
129
|
-
|
130
|
-
# Parse SSL configuration if present
|
131
|
-
ssl_config = None
|
132
|
-
if 'ssl' in db_config:
|
133
|
-
ssl_params = db_config['ssl']
|
134
|
-
if not isinstance(ssl_params, dict):
|
135
|
-
raise ValueError("SSL configuration must be a dictionary")
|
136
|
-
|
137
|
-
if ssl_params.get('mode') not in [None, 'disabled', 'required', 'verify_ca', 'verify_identity']:
|
138
|
-
raise ValueError(f"Invalid ssl-mode: {ssl_params.get('mode')}")
|
139
|
-
|
140
|
-
ssl_config = SSLConfig(
|
141
|
-
mode=ssl_params.get('mode', 'disabled'),
|
142
|
-
ca=ssl_params.get('ca'),
|
143
|
-
cert=ssl_params.get('cert'),
|
144
|
-
key=ssl_params.get('key')
|
145
|
-
)
|
223
|
+
config = cls._create_config_from_params(db_config, local_host)
|
146
224
|
|
147
|
-
config = cls(
|
148
|
-
database=db_config['database'],
|
149
|
-
user=db_config['user'],
|
150
|
-
password=db_config['password'],
|
151
|
-
host=db_config['host'],
|
152
|
-
port=str(db_config['port']),
|
153
|
-
charset=db_config.get('charset', 'utf8mb4'),
|
154
|
-
local_host=local_host,
|
155
|
-
ssl=ssl_config
|
156
|
-
)
|
157
225
|
config.debug = cls.get_debug_mode()
|
158
226
|
return config
|
159
227
|
|
mcp_dbutils/mysql/handler.py
CHANGED
@@ -36,7 +36,7 @@ class MySQLHandler(ConnectionHandler):
|
|
36
36
|
try:
|
37
37
|
conn_params = self.config.get_connection_params()
|
38
38
|
conn = mysql.connector.connect(**conn_params)
|
39
|
-
with conn.cursor(dictionary=True) as cur:
|
39
|
+
with conn.cursor(dictionary=True) as cur: # NOSONAR
|
40
40
|
cur.execute("""
|
41
41
|
SELECT
|
42
42
|
TABLE_NAME as table_name,
|
@@ -66,7 +66,7 @@ class MySQLHandler(ConnectionHandler):
|
|
66
66
|
try:
|
67
67
|
conn_params = self.config.get_connection_params()
|
68
68
|
conn = mysql.connector.connect(**conn_params)
|
69
|
-
with conn.cursor(dictionary=True) as cur:
|
69
|
+
with conn.cursor(dictionary=True) as cur: # NOSONAR
|
70
70
|
# Get column information
|
71
71
|
cur.execute("""
|
72
72
|
SELECT
|
@@ -118,7 +118,7 @@ class MySQLHandler(ConnectionHandler):
|
|
118
118
|
conn = mysql.connector.connect(**conn_params)
|
119
119
|
self.log("debug", f"Executing query: {sql}")
|
120
120
|
|
121
|
-
with conn.cursor(dictionary=True) as cur:
|
121
|
+
with conn.cursor(dictionary=True) as cur: # NOSONAR
|
122
122
|
# Start read-only transaction
|
123
123
|
cur.execute("SET TRANSACTION READ ONLY")
|
124
124
|
try:
|
@@ -150,7 +150,7 @@ class MySQLHandler(ConnectionHandler):
|
|
150
150
|
try:
|
151
151
|
conn_params = self.config.get_connection_params()
|
152
152
|
conn = mysql.connector.connect(**conn_params)
|
153
|
-
with conn.cursor(dictionary=True) as cur:
|
153
|
+
with conn.cursor(dictionary=True) as cur: # NOSONAR
|
154
154
|
# Get table information and comment
|
155
155
|
cur.execute("""
|
156
156
|
SELECT
|
@@ -220,7 +220,7 @@ class MySQLHandler(ConnectionHandler):
|
|
220
220
|
try:
|
221
221
|
conn_params = self.config.get_connection_params()
|
222
222
|
conn = mysql.connector.connect(**conn_params)
|
223
|
-
with conn.cursor(dictionary=True) as cur:
|
223
|
+
with conn.cursor(dictionary=True) as cur: # NOSONAR
|
224
224
|
# MySQL provides a SHOW CREATE TABLE statement
|
225
225
|
cur.execute(f"SHOW CREATE TABLE {table_name}")
|
226
226
|
result = cur.fetchone()
|
@@ -242,7 +242,7 @@ class MySQLHandler(ConnectionHandler):
|
|
242
242
|
try:
|
243
243
|
conn_params = self.config.get_connection_params()
|
244
244
|
conn = mysql.connector.connect(**conn_params)
|
245
|
-
with conn.cursor(dictionary=True) as cur:
|
245
|
+
with conn.cursor(dictionary=True) as cur: # NOSONAR
|
246
246
|
# Get index information
|
247
247
|
cur.execute("""
|
248
248
|
SELECT
|
@@ -301,7 +301,7 @@ class MySQLHandler(ConnectionHandler):
|
|
301
301
|
try:
|
302
302
|
conn_params = self.config.get_connection_params()
|
303
303
|
conn = mysql.connector.connect(**conn_params)
|
304
|
-
with conn.cursor(dictionary=True) as cur:
|
304
|
+
with conn.cursor(dictionary=True) as cur: # NOSONAR
|
305
305
|
# Get table statistics
|
306
306
|
cur.execute("""
|
307
307
|
SELECT
|
@@ -366,7 +366,7 @@ class MySQLHandler(ConnectionHandler):
|
|
366
366
|
try:
|
367
367
|
conn_params = self.config.get_connection_params()
|
368
368
|
conn = mysql.connector.connect(**conn_params)
|
369
|
-
with conn.cursor(dictionary=True) as cur:
|
369
|
+
with conn.cursor(dictionary=True) as cur: # NOSONAR
|
370
370
|
# Get constraint information
|
371
371
|
cur.execute("""
|
372
372
|
SELECT
|
@@ -429,7 +429,7 @@ class MySQLHandler(ConnectionHandler):
|
|
429
429
|
try:
|
430
430
|
conn_params = self.config.get_connection_params()
|
431
431
|
conn = mysql.connector.connect(**conn_params)
|
432
|
-
with conn.cursor(dictionary=True) as cur:
|
432
|
+
with conn.cursor(dictionary=True) as cur: # NOSONAR
|
433
433
|
# Get EXPLAIN output
|
434
434
|
cur.execute(f"EXPLAIN FORMAT=TREE {sql}")
|
435
435
|
explain_result = cur.fetchall()
|
mcp_dbutils/mysql/server.py
CHANGED
@@ -49,7 +49,7 @@ class MySQLServer(ConnectionServer):
|
|
49
49
|
"""列出所有表资源"""
|
50
50
|
try:
|
51
51
|
conn = self.pool.get_connection()
|
52
|
-
with conn.cursor(dictionary=True) as cur:
|
52
|
+
with conn.cursor(dictionary=True) as cur: # NOSONAR - dictionary参数是正确的,用于返回字典格式的结果
|
53
53
|
cur.execute("""
|
54
54
|
SELECT
|
55
55
|
table_name,
|
@@ -78,7 +78,7 @@ class MySQLServer(ConnectionServer):
|
|
78
78
|
try:
|
79
79
|
table_name = uri.split('/')[-2]
|
80
80
|
conn = self.pool.get_connection()
|
81
|
-
with conn.cursor(dictionary=True) as cur:
|
81
|
+
with conn.cursor(dictionary=True) as cur: # NOSONAR - dictionary参数是正确的,用于返回字典格式的结果
|
82
82
|
# 获取列信息
|
83
83
|
cur.execute("""
|
84
84
|
SELECT
|
@@ -170,7 +170,7 @@ class MySQLServer(ConnectionServer):
|
|
170
170
|
conn = self.pool.get_connection()
|
171
171
|
|
172
172
|
self.log("info", f"执行查询: {sql}")
|
173
|
-
with conn.cursor(dictionary=True) as cur:
|
173
|
+
with conn.cursor(dictionary=True) as cur: # NOSONAR - dictionary参数是正确的,用于返回字典格式的结果
|
174
174
|
# 设置只读事务
|
175
175
|
cur.execute("SET TRANSACTION READ ONLY")
|
176
176
|
try:
|
mcp_dbutils/sqlite/handler.py
CHANGED
@@ -142,7 +142,7 @@ class SQLiteHandler(ConnectionHandler):
|
|
142
142
|
with sqlite3.connect(self.config.path) as conn:
|
143
143
|
cur = conn.cursor()
|
144
144
|
# SQLite provides the complete CREATE statement
|
145
|
-
cur.execute(
|
145
|
+
cur.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
|
146
146
|
result = cur.fetchone()
|
147
147
|
|
148
148
|
if not result:
|
@@ -151,7 +151,7 @@ class SQLiteHandler(ConnectionHandler):
|
|
151
151
|
ddl = result[0]
|
152
152
|
|
153
153
|
# Get indexes
|
154
|
-
cur.execute(
|
154
|
+
cur.execute("SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name=?", (table_name,))
|
155
155
|
indexes = cur.fetchall()
|
156
156
|
|
157
157
|
# Add index definitions
|
@@ -234,9 +234,9 @@ class SQLiteHandler(ConnectionHandler):
|
|
234
234
|
indexes = cur.fetchall()
|
235
235
|
|
236
236
|
# Get page count and size
|
237
|
-
cur.execute(
|
237
|
+
cur.execute("PRAGMA page_count")
|
238
238
|
page_count = cur.fetchone()[0]
|
239
|
-
cur.execute(
|
239
|
+
cur.execute("PRAGMA page_size")
|
240
240
|
page_size = cur.fetchone()[0]
|
241
241
|
|
242
242
|
# Calculate total size
|
mcp_dbutils/sqlite/server.py
CHANGED
@@ -54,7 +54,7 @@ class SQLiteServer(ConnectionServer):
|
|
54
54
|
# 使用默认连接
|
55
55
|
conn = self._get_connection()
|
56
56
|
|
57
|
-
with closing(conn) as
|
57
|
+
with closing(conn) as _:
|
58
58
|
cursor = conn.execute(
|
59
59
|
"SELECT name FROM sqlite_master WHERE type='table'"
|
60
60
|
)
|
@@ -155,7 +155,7 @@ class SQLiteServer(ConnectionServer):
|
|
155
155
|
# 使用默认连接
|
156
156
|
conn = self._get_connection()
|
157
157
|
|
158
|
-
with closing(conn) as
|
158
|
+
with closing(conn) as _:
|
159
159
|
self.log("info", f"执行查询: {sql}")
|
160
160
|
cursor = conn.execute(sql)
|
161
161
|
results = cursor.fetchall()
|
@@ -1,22 +1,22 @@
|
|
1
1
|
mcp_dbutils/__init__.py,sha256=6LLccQv7je2L4IpY_I3OzSJZcK32VUDJv2IY31y6eYg,1900
|
2
|
-
mcp_dbutils/base.py,sha256=
|
2
|
+
mcp_dbutils/base.py,sha256=9UDhPFpw6YdkUMhAqgGyy6vuXA2C78LIGrWiW0zeB8s,30839
|
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
|
-
mcp_dbutils/mysql/config.py,sha256=
|
8
|
-
mcp_dbutils/mysql/handler.py,sha256=
|
9
|
-
mcp_dbutils/mysql/server.py,sha256=
|
7
|
+
mcp_dbutils/mysql/config.py,sha256=BTPPFqlhoTp7EBFIeLJZh8x6bCn3q9NivHYz9yZHziw,9820
|
8
|
+
mcp_dbutils/mysql/handler.py,sha256=knBoFVYmdse5hsjr4GPi4fZhEaYOPRBPGR2d3w8qqzw,19837
|
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
12
|
mcp_dbutils/postgres/handler.py,sha256=ppltSKtSk-BlPpp3iEVJlmoyl4AmqKcQHx_0zHlz03Y,24403
|
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=
|
17
|
-
mcp_dbutils/sqlite/server.py,sha256=
|
18
|
-
mcp_dbutils-0.
|
19
|
-
mcp_dbutils-0.
|
20
|
-
mcp_dbutils-0.
|
21
|
-
mcp_dbutils-0.
|
22
|
-
mcp_dbutils-0.
|
16
|
+
mcp_dbutils/sqlite/handler.py,sha256=25zqoQpMhRNKeO3MH2a0E5dO3-4A8sPb7q87cn7Cs0E,17746
|
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,,
|
File without changes
|
File without changes
|
File without changes
|