mcp-dbutils 0.16.0__py3-none-any.whl → 0.17.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/base.py +441 -323
- mcp_dbutils/mysql/config.py +122 -54
- mcp_dbutils/mysql/handler.py +69 -22
- mcp_dbutils/mysql/server.py +3 -3
- mcp_dbutils/sqlite/handler.py +66 -24
- mcp_dbutils/sqlite/server.py +2 -2
- mcp_dbutils-0.17.0.dist-info/METADATA +308 -0
- {mcp_dbutils-0.16.0.dist-info → mcp_dbutils-0.17.0.dist-info}/RECORD +11 -11
- mcp_dbutils-0.16.0.dist-info/METADATA +0 -572
- {mcp_dbutils-0.16.0.dist-info → mcp_dbutils-0.17.0.dist-info}/WHEEL +0 -0
- {mcp_dbutils-0.16.0.dist-info → mcp_dbutils-0.17.0.dist-info}/entry_points.txt +0 -0
- {mcp_dbutils-0.16.0.dist-info → mcp_dbutils-0.17.0.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
|
|