mcp-dbutils 0.8.0__py3-none-any.whl → 0.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mcp_dbutils/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- """MCP Database Utilities Service"""
1
+ """MCP Connection Utilities Service"""
2
2
 
3
3
  import asyncio
4
4
  import argparse
@@ -9,7 +9,7 @@ import yaml
9
9
  from importlib.metadata import metadata
10
10
 
11
11
  from .log import create_logger
12
- from .base import DatabaseServer
12
+ from .base import ConnectionServer
13
13
 
14
14
  # 获取包信息
15
15
  pkg_meta = metadata("mcp-dbutils")
@@ -19,7 +19,7 @@ log = create_logger(pkg_meta["Name"])
19
19
 
20
20
  async def run_server():
21
21
  """服务器运行逻辑"""
22
- parser = argparse.ArgumentParser(description='MCP Database Server')
22
+ parser = argparse.ArgumentParser(description='MCP Connection Server')
23
23
  parser.add_argument('--config', required=True, help='YAML配置文件路径')
24
24
  parser.add_argument('--local-host', help='本地主机地址')
25
25
 
@@ -32,7 +32,7 @@ async def run_server():
32
32
  global log
33
33
  log = create_logger(pkg_meta["Name"], debug)
34
34
 
35
- log("info", f"MCP Database Utilities Service v{pkg_meta['Version']}")
35
+ log("info", f"MCP Connection Utilities Service v{pkg_meta['Version']}")
36
36
  if debug:
37
37
  log("debug", "Debug模式已开启")
38
38
 
@@ -40,11 +40,11 @@ async def run_server():
40
40
  try:
41
41
  with open(args.config, 'r') as f:
42
42
  config = yaml.safe_load(f)
43
- if not config or 'databases' not in config:
44
- log("error", "配置文件必须包含 databases 配置")
43
+ if not config or 'connections' not in config:
44
+ log("error", "配置文件必须包含 connections 配置")
45
45
  sys.exit(1)
46
- if not config['databases']:
47
- log("error", "配置文件必须包含至少一个数据库配置")
46
+ if not config['connections']:
47
+ log("error", "配置文件必须包含至少一个连接配置")
48
48
  sys.exit(1)
49
49
  except Exception as e:
50
50
  log("error", f"读取配置文件失败: {str(e)}")
@@ -52,7 +52,7 @@ async def run_server():
52
52
 
53
53
  # 创建并运行服务器
54
54
  try:
55
- server = DatabaseServer(args.config, debug)
55
+ server = ConnectionServer(args.config, debug)
56
56
  await server.run()
57
57
  except KeyboardInterrupt:
58
58
  log("info", "服务器已停止")
mcp_dbutils/base.py CHANGED
@@ -1,14 +1,14 @@
1
- """Database server base class"""
1
+ """Connection server base class"""
2
2
 
3
- class DatabaseError(Exception):
4
- """Base exception for database errors"""
3
+ class ConnectionHandlerError(Exception):
4
+ """Base exception for connection errors"""
5
5
  pass
6
6
 
7
- class ConfigurationError(DatabaseError):
7
+ class ConfigurationError(ConnectionHandlerError):
8
8
  """Configuration related errors"""
9
9
  pass
10
10
 
11
- class ConnectionError(DatabaseError):
11
+ class ConnectionError(ConnectionHandlerError):
12
12
  """Connection related errors"""
13
13
  pass
14
14
 
@@ -17,6 +17,8 @@ from typing import Any, List, Optional, AsyncContextManager
17
17
  from contextlib import asynccontextmanager
18
18
  import json
19
19
  import yaml
20
+ import time
21
+ from datetime import datetime
20
22
  from importlib.metadata import metadata
21
23
  from mcp.server import Server, NotificationOptions
22
24
  import mcp.server.stdio
@@ -29,21 +31,21 @@ from .stats import ResourceStats
29
31
  # 获取包信息用于日志命名
30
32
  pkg_meta = metadata("mcp-dbutils")
31
33
 
32
- class DatabaseHandler(ABC):
33
- """Abstract base class defining common interface for database handlers"""
34
+ class ConnectionHandler(ABC):
35
+ """Abstract base class defining common interface for connection handlers"""
34
36
 
35
- def __init__(self, config_path: str, database: str, debug: bool = False):
36
- """Initialize database handler
37
+ def __init__(self, config_path: str, connection: str, debug: bool = False):
38
+ """Initialize connection handler
37
39
 
38
40
  Args:
39
41
  config_path: Path to configuration file
40
- database: Database configuration name
42
+ connection: Database connection name
41
43
  debug: Enable debug mode
42
44
  """
43
45
  self.config_path = config_path
44
- self.database = database
46
+ self.connection = connection
45
47
  self.debug = debug
46
- self.log = create_logger(f"{pkg_meta['Name']}.handler.{database}", debug)
48
+ self.log = create_logger(f"{pkg_meta['Name']}.handler.{connection}", debug)
47
49
  self.stats = ResourceStats()
48
50
 
49
51
  @property
@@ -54,7 +56,7 @@ class DatabaseHandler(ABC):
54
56
 
55
57
  @abstractmethod
56
58
  async def get_tables(self) -> list[types.Resource]:
57
- """Get list of table resources from database"""
59
+ """Get list of table resources from database connection"""
58
60
  pass
59
61
 
60
62
  @abstractmethod
@@ -68,28 +70,144 @@ class DatabaseHandler(ABC):
68
70
  pass
69
71
 
70
72
  async def execute_query(self, sql: str) -> str:
71
- """Execute SQL query"""
73
+ """Execute SQL query with performance tracking"""
74
+ start_time = datetime.now()
72
75
  try:
73
76
  self.stats.record_query()
74
77
  result = await self._execute_query(sql)
78
+ duration = (datetime.now() - start_time).total_seconds()
79
+ self.stats.record_query_duration(sql, duration)
75
80
  self.stats.update_memory_usage(result)
76
- self.log("info", f"Resource stats: {json.dumps(self.stats.to_dict())}")
81
+ self.log("info", f"Query executed in {duration*1000:.2f}ms. Resource stats: {json.dumps(self.stats.to_dict())}")
77
82
  return result
78
83
  except Exception as e:
84
+ duration = (datetime.now() - start_time).total_seconds()
79
85
  self.stats.record_error(e.__class__.__name__)
80
- self.log("error", f"Query error - {str(e)}\nResource stats: {json.dumps(self.stats.to_dict())}")
86
+ self.log("error", f"Query error after {duration*1000:.2f}ms - {str(e)}\nResource stats: {json.dumps(self.stats.to_dict())}")
81
87
  raise
82
88
 
89
+ @abstractmethod
90
+ async def get_table_description(self, table_name: str) -> str:
91
+ """Get detailed table description including columns, types, and comments
92
+
93
+ Args:
94
+ table_name: Name of the table to describe
95
+
96
+ Returns:
97
+ Formatted table description
98
+ """
99
+ pass
100
+
101
+ @abstractmethod
102
+ async def get_table_ddl(self, table_name: str) -> str:
103
+ """Get DDL statement for table including columns, constraints and indexes
104
+
105
+ Args:
106
+ table_name: Name of the table to get DDL for
107
+
108
+ Returns:
109
+ DDL statement as string
110
+ """
111
+ pass
112
+
113
+ @abstractmethod
114
+ async def get_table_indexes(self, table_name: str) -> str:
115
+ """Get index information for table
116
+
117
+ Args:
118
+ table_name: Name of the table to get indexes for
119
+
120
+ Returns:
121
+ Formatted index information
122
+ """
123
+ pass
124
+
125
+ @abstractmethod
126
+ async def get_table_stats(self, table_name: str) -> str:
127
+ """Get table statistics information
128
+
129
+ Args:
130
+ table_name: Name of the table to get statistics for
131
+
132
+ Returns:
133
+ Formatted statistics information including row count, size, etc.
134
+ """
135
+ pass
136
+
137
+ @abstractmethod
138
+ async def get_table_constraints(self, table_name: str) -> str:
139
+ """Get constraint information for table
140
+
141
+ Args:
142
+ table_name: Name of the table to get constraints for
143
+
144
+ Returns:
145
+ Formatted constraint information including primary keys, foreign keys, etc.
146
+ """
147
+ pass
148
+
149
+ @abstractmethod
150
+ async def explain_query(self, sql: str) -> str:
151
+ """Get query execution plan
152
+
153
+ Args:
154
+ sql: SQL query to explain
155
+
156
+ Returns:
157
+ Formatted query execution plan with cost estimates
158
+ """
159
+ pass
160
+
83
161
  @abstractmethod
84
162
  async def cleanup(self):
85
163
  """Cleanup resources"""
86
164
  pass
87
165
 
88
- class DatabaseServer:
89
- """Unified database server class"""
166
+ async def execute_tool_query(self, tool_name: str, table_name: str = "", sql: str = "") -> str:
167
+ """Execute a tool query and return formatted result
168
+
169
+ Args:
170
+ tool_name: Name of the tool to execute
171
+ table_name: Name of the table to query (for table-related tools)
172
+ sql: SQL query (for query-related tools)
173
+
174
+ Returns:
175
+ Formatted query result
176
+ """
177
+ try:
178
+ self.stats.record_query()
179
+
180
+ if tool_name == "dbutils-describe-table":
181
+ result = await self.get_table_description(table_name)
182
+ elif tool_name == "dbutils-get-ddl":
183
+ result = await self.get_table_ddl(table_name)
184
+ elif tool_name == "dbutils-list-indexes":
185
+ result = await self.get_table_indexes(table_name)
186
+ elif tool_name == "dbutils-get-stats":
187
+ result = await self.get_table_stats(table_name)
188
+ elif tool_name == "dbutils-list-constraints":
189
+ result = await self.get_table_constraints(table_name)
190
+ elif tool_name == "dbutils-explain-query":
191
+ if not sql:
192
+ raise ValueError("SQL query required for explain-query tool")
193
+ result = await self.explain_query(sql)
194
+ else:
195
+ raise ValueError(f"Unknown tool: {tool_name}")
196
+
197
+ self.stats.update_memory_usage(result)
198
+ self.log("info", f"Resource stats: {json.dumps(self.stats.to_dict())}")
199
+ return f"[{self.db_type}]\n{result}"
200
+
201
+ except Exception as e:
202
+ self.stats.record_error(e.__class__.__name__)
203
+ self.log("error", f"Tool error - {str(e)}\nResource stats: {json.dumps(self.stats.to_dict())}")
204
+ raise
205
+
206
+ class ConnectionServer:
207
+ """Unified connection server class"""
90
208
 
91
209
  def __init__(self, config_path: str, debug: bool = False):
92
- """Initialize database server
210
+ """Initialize connection server
93
211
 
94
212
  Args:
95
213
  config_path: Path to configuration file
@@ -120,27 +238,27 @@ class DatabaseServer:
120
238
  raise
121
239
 
122
240
  @asynccontextmanager
123
- async def get_handler(self, database: str) -> AsyncContextManager[DatabaseHandler]:
124
- """Get database handler
241
+ async def get_handler(self, connection: str) -> AsyncContextManager[ConnectionHandler]:
242
+ """Get connection handler
125
243
 
126
- Get appropriate database handler based on configuration name
244
+ Get appropriate connection handler based on connection name
127
245
 
128
246
  Args:
129
- database: Database configuration name
247
+ connection: Database connection name
130
248
 
131
249
  Returns:
132
- AsyncContextManager[DatabaseHandler]: Context manager for database handler
250
+ AsyncContextManager[ConnectionHandler]: Context manager for connection handler
133
251
  """
134
252
  # Read configuration file to determine database type
135
253
  with open(self.config_path, 'r') as f:
136
254
  config = yaml.safe_load(f)
137
- if not config or 'databases' not in config:
138
- raise ConfigurationError("Configuration file must contain 'databases' section")
139
- if database not in config['databases']:
140
- available_dbs = list(config['databases'].keys())
141
- raise ConfigurationError(f"Database configuration not found: {database}. Available configurations: {available_dbs}")
255
+ if not config or 'connections' not in config:
256
+ raise ConfigurationError("Configuration file must contain 'connections' section")
257
+ if connection not in config['connections']:
258
+ available_connections = list(config['connections'].keys())
259
+ raise ConfigurationError(f"Connection not found: {connection}. Available connections: {available_connections}")
142
260
 
143
- db_config = config['databases'][database]
261
+ db_config = config['connections'][connection]
144
262
 
145
263
  handler = None
146
264
  try:
@@ -150,16 +268,16 @@ class DatabaseServer:
150
268
  db_type = db_config['type']
151
269
  self.logger("debug", f"Creating handler for database type: {db_type}")
152
270
  if db_type == 'sqlite':
153
- from .sqlite.handler import SqliteHandler
154
- handler = SqliteHandler(self.config_path, database, self.debug)
271
+ from .sqlite.handler import SQLiteHandler
272
+ handler = SQLiteHandler(self.config_path, connection, self.debug)
155
273
  elif db_type == 'postgres':
156
- from .postgres.handler import PostgresHandler
157
- handler = PostgresHandler(self.config_path, database, self.debug)
274
+ from .postgres.handler import PostgreSQLHandler
275
+ handler = PostgreSQLHandler(self.config_path, connection, self.debug)
158
276
  else:
159
277
  raise ConfigurationError(f"Unsupported database type: {db_type}")
160
278
 
161
279
  handler.stats.record_connection_start()
162
- self.logger("debug", f"Handler created successfully for {database}")
280
+ self.logger("debug", f"Handler created successfully for {connection}")
163
281
  self.logger("info", f"Resource stats: {json.dumps(handler.stats.to_dict())}")
164
282
  yield handler
165
283
  except yaml.YAMLError as e:
@@ -168,7 +286,7 @@ class DatabaseServer:
168
286
  raise ConfigurationError(f"Failed to import handler for {db_type}: {str(e)}")
169
287
  finally:
170
288
  if handler:
171
- self.logger("debug", f"Cleaning up handler for {database}")
289
+ self.logger("debug", f"Cleaning up handler for {connection}")
172
290
  handler.stats.record_connection_end()
173
291
  self.logger("info", f"Final resource stats: {json.dumps(handler.stats.to_dict())}")
174
292
  await handler.cleanup()
@@ -177,27 +295,27 @@ class DatabaseServer:
177
295
  """Setup MCP handlers"""
178
296
  @self.server.list_resources()
179
297
  async def handle_list_resources(arguments: dict | None = None) -> list[types.Resource]:
180
- if not arguments or 'database' not in arguments:
181
- # Return empty list when no database specified
298
+ if not arguments or 'connection' not in arguments:
299
+ # Return empty list when no connection specified
182
300
  return []
183
301
 
184
- database = arguments['database']
185
- async with self.get_handler(database) as handler:
302
+ connection = arguments['connection']
303
+ async with self.get_handler(connection) as handler:
186
304
  return await handler.get_tables()
187
305
 
188
306
  @self.server.read_resource()
189
307
  async def handle_read_resource(uri: str, arguments: dict | None = None) -> str:
190
- if not arguments or 'database' not in arguments:
191
- raise ConfigurationError("Database configuration name must be specified")
308
+ if not arguments or 'connection' not in arguments:
309
+ raise ConfigurationError("Connection name must be specified")
192
310
 
193
311
  parts = uri.split('/')
194
312
  if len(parts) < 3:
195
313
  raise ConfigurationError("Invalid resource URI format")
196
314
 
197
- database = arguments['database']
315
+ connection = arguments['connection']
198
316
  table_name = parts[-2] # URI format: xxx/table_name/schema
199
317
 
200
- async with self.get_handler(database) as handler:
318
+ async with self.get_handler(connection) as handler:
201
319
  return await handler.get_schema(table_name)
202
320
 
203
321
  @self.server.list_tools()
@@ -205,47 +323,187 @@ class DatabaseServer:
205
323
  return [
206
324
  types.Tool(
207
325
  name="dbutils-run-query",
208
- description="Execute read-only SQL query on database",
326
+ description="Execute read-only SQL query on database connection",
209
327
  inputSchema={
210
328
  "type": "object",
211
329
  "properties": {
212
- "database": {
330
+ "connection": {
213
331
  "type": "string",
214
- "description": "Database configuration name"
332
+ "description": "Database connection name"
215
333
  },
216
334
  "sql": {
217
335
  "type": "string",
218
336
  "description": "SQL query (SELECT only)"
219
337
  }
220
338
  },
221
- "required": ["database", "sql"]
339
+ "required": ["connection", "sql"]
222
340
  }
223
341
  ),
224
342
  types.Tool(
225
343
  name="dbutils-list-tables",
226
- description="List all available tables in the specified database",
344
+ description="List all available tables in the specified database connection",
345
+ inputSchema={
346
+ "type": "object",
347
+ "properties": {
348
+ "connection": {
349
+ "type": "string",
350
+ "description": "Database connection name"
351
+ }
352
+ },
353
+ "required": ["connection"]
354
+ }
355
+ ),
356
+ types.Tool(
357
+ name="dbutils-describe-table",
358
+ description="Get detailed information about a table's structure",
227
359
  inputSchema={
228
360
  "type": "object",
229
361
  "properties": {
230
- "database": {
362
+ "connection": {
231
363
  "type": "string",
232
- "description": "Database configuration name"
364
+ "description": "Database connection name"
365
+ },
366
+ "table": {
367
+ "type": "string",
368
+ "description": "Table name to describe"
233
369
  }
234
370
  },
235
- "required": ["database"]
371
+ "required": ["connection", "table"]
372
+ }
373
+ ),
374
+ types.Tool(
375
+ name="dbutils-get-ddl",
376
+ description="Get DDL statement for creating the table",
377
+ inputSchema={
378
+ "type": "object",
379
+ "properties": {
380
+ "connection": {
381
+ "type": "string",
382
+ "description": "Database connection name"
383
+ },
384
+ "table": {
385
+ "type": "string",
386
+ "description": "Table name to get DDL for"
387
+ }
388
+ },
389
+ "required": ["connection", "table"]
390
+ }
391
+ ),
392
+ types.Tool(
393
+ name="dbutils-list-indexes",
394
+ description="List all indexes on the specified table",
395
+ inputSchema={
396
+ "type": "object",
397
+ "properties": {
398
+ "connection": {
399
+ "type": "string",
400
+ "description": "Database connection name"
401
+ },
402
+ "table": {
403
+ "type": "string",
404
+ "description": "Table name to list indexes for"
405
+ }
406
+ },
407
+ "required": ["connection", "table"]
408
+ }
409
+ ),
410
+ types.Tool(
411
+ name="dbutils-get-stats",
412
+ description="Get table statistics like row count and size",
413
+ inputSchema={
414
+ "type": "object",
415
+ "properties": {
416
+ "connection": {
417
+ "type": "string",
418
+ "description": "Database connection name"
419
+ },
420
+ "table": {
421
+ "type": "string",
422
+ "description": "Table name to get statistics for"
423
+ }
424
+ },
425
+ "required": ["connection", "table"]
426
+ }
427
+ ),
428
+ types.Tool(
429
+ name="dbutils-list-constraints",
430
+ description="List all constraints (primary key, foreign keys, etc) on the table",
431
+ inputSchema={
432
+ "type": "object",
433
+ "properties": {
434
+ "connection": {
435
+ "type": "string",
436
+ "description": "Database connection name"
437
+ },
438
+ "table": {
439
+ "type": "string",
440
+ "description": "Table name to list constraints for"
441
+ }
442
+ },
443
+ "required": ["connection", "table"]
444
+ }
445
+ ),
446
+ types.Tool(
447
+ name="dbutils-explain-query",
448
+ description="Get execution plan for a SQL query",
449
+ inputSchema={
450
+ "type": "object",
451
+ "properties": {
452
+ "connection": {
453
+ "type": "string",
454
+ "description": "Database connection name"
455
+ },
456
+ "sql": {
457
+ "type": "string",
458
+ "description": "SQL query to explain"
459
+ }
460
+ },
461
+ "required": ["connection", "sql"]
462
+ }
463
+ ),
464
+ types.Tool(
465
+ name="dbutils-get-performance",
466
+ description="Get database performance statistics",
467
+ inputSchema={
468
+ "type": "object",
469
+ "properties": {
470
+ "connection": {
471
+ "type": "string",
472
+ "description": "Database connection name"
473
+ }
474
+ },
475
+ "required": ["connection"]
476
+ }
477
+ ),
478
+ types.Tool(
479
+ name="dbutils-analyze-query",
480
+ description="Analyze a SQL query for performance",
481
+ inputSchema={
482
+ "type": "object",
483
+ "properties": {
484
+ "connection": {
485
+ "type": "string",
486
+ "description": "Database connection name"
487
+ },
488
+ "sql": {
489
+ "type": "string",
490
+ "description": "SQL query to analyze"
491
+ }
492
+ },
493
+ "required": ["connection", "sql"]
236
494
  }
237
495
  )
238
496
  ]
239
497
 
240
498
  @self.server.call_tool()
241
499
  async def handle_call_tool(name: str, arguments: dict) -> list[types.TextContent]:
242
- if "database" not in arguments:
243
- raise ConfigurationError("Database configuration name must be specified")
244
-
245
- database = arguments["database"]
500
+ if "connection" not in arguments:
501
+ raise ConfigurationError("Connection name must be specified")
502
+
503
+ connection = arguments["connection"]
246
504
 
247
505
  if name == "dbutils-list-tables":
248
- async with self.get_handler(database) as handler:
506
+ async with self.get_handler(connection) as handler:
249
507
  tables = await handler.get_tables()
250
508
  if not tables:
251
509
  # 空表列表的情况也返回数据库类型
@@ -269,9 +527,76 @@ class DatabaseServer:
269
527
  if not sql.lower().startswith("select"):
270
528
  raise ConfigurationError("Only SELECT queries are supported for security reasons")
271
529
 
272
- async with self.get_handler(database) as handler:
530
+ async with self.get_handler(connection) as handler:
273
531
  result = await handler.execute_query(sql)
274
532
  return [types.TextContent(type="text", text=result)]
533
+ elif name in ["dbutils-describe-table", "dbutils-get-ddl", "dbutils-list-indexes",
534
+ "dbutils-get-stats", "dbutils-list-constraints"]:
535
+ table = arguments.get("table", "").strip()
536
+ if not table:
537
+ raise ConfigurationError("Table name cannot be empty")
538
+
539
+ async with self.get_handler(connection) as handler:
540
+ result = await handler.execute_tool_query(name, table_name=table)
541
+ return [types.TextContent(type="text", text=result)]
542
+ elif name == "dbutils-explain-query":
543
+ sql = arguments.get("sql", "").strip()
544
+ if not sql:
545
+ raise ConfigurationError("SQL query cannot be empty")
546
+
547
+ async with self.get_handler(connection) as handler:
548
+ result = await handler.execute_tool_query(name, sql=sql)
549
+ return [types.TextContent(type="text", text=result)]
550
+ elif name == "dbutils-get-performance":
551
+ async with self.get_handler(connection) as handler:
552
+ performance_stats = handler.stats.get_performance_stats()
553
+ return [types.TextContent(type="text", text=f"[{handler.db_type}]\n{performance_stats}")]
554
+ elif name == "dbutils-analyze-query":
555
+ sql = arguments.get("sql", "").strip()
556
+ if not sql:
557
+ raise ConfigurationError("SQL query cannot be empty")
558
+
559
+ async with self.get_handler(connection) as handler:
560
+ # First get the execution plan
561
+ explain_result = await handler.explain_query(sql)
562
+
563
+ # Then execute the actual query to measure performance
564
+ start_time = datetime.now()
565
+ if sql.lower().startswith("select"):
566
+ try:
567
+ await handler.execute_query(sql)
568
+ except Exception as e:
569
+ # If query fails, we still provide the execution plan
570
+ self.logger("error", f"Query execution failed during analysis: {str(e)}")
571
+ duration = (datetime.now() - start_time).total_seconds()
572
+
573
+ # Combine analysis results
574
+ analysis = [
575
+ f"[{handler.db_type}] Query Analysis",
576
+ f"SQL: {sql}",
577
+ f"",
578
+ f"Execution Time: {duration*1000:.2f}ms",
579
+ f"",
580
+ f"Execution Plan:",
581
+ explain_result
582
+ ]
583
+
584
+ # Add optimization suggestions based on execution plan and timing
585
+ suggestions = []
586
+ if "seq scan" in explain_result.lower() and duration > 0.1:
587
+ suggestions.append("- Consider adding an index to avoid sequential scan")
588
+ if "hash join" in explain_result.lower() and duration > 0.5:
589
+ suggestions.append("- Consider optimizing join conditions")
590
+ if duration > 0.5: # 500ms
591
+ suggestions.append("- Query is slow, consider optimizing or adding caching")
592
+ if "temporary" in explain_result.lower():
593
+ suggestions.append("- Query creates temporary tables, consider restructuring")
594
+
595
+ if suggestions:
596
+ analysis.append("\nOptimization Suggestions:")
597
+ analysis.extend(suggestions)
598
+
599
+ return [types.TextContent(type="text", text="\n".join(analysis))]
275
600
  else:
276
601
  raise ConfigurationError(f"Unknown tool: {name}")
277
602