Skip to content

Commit 3b5a4d2

Browse files
committed
Rebased
1 parent 1b80672 commit 3b5a4d2

File tree

2 files changed

+64
-1
lines changed

2 files changed

+64
-1
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ The MCP MariaDB Server exposes a set of tools for interacting with MariaDB datab
2424
- Listing databases and tables
2525
- Retrieving table schemas
2626
- Executing safe, read-only SQL queries
27+
- Query performance analysis with EXPLAIN and EXPLAIN EXTENDED
28+
- Comprehensive tool usage guide for LLM self-discovery
2729
- Creating and managing vector stores for embedding-based search
2830
- Integrating with embedding providers (currently OpenAI, Gemini, and HuggingFace) (optional)
2931

@@ -63,6 +65,18 @@ The MCP MariaDB Server exposes a set of tools for interacting with MariaDB datab
6365
- Creates a new database if it doesn't exist.
6466
- Parameters: `database_name` (string, required)
6567

68+
### Query Performance Analysis Tools
69+
70+
- **explain_query**
71+
- Executes EXPLAIN on a SQL query to show the execution plan for performance analysis.
72+
- Parameters: `sql_query` (string, required), `database_name` (string, required), `parameters` (list, optional)
73+
- _Note: Helps analyze query performance and optimization opportunities. Does not execute the actual query._
74+
75+
- **explain_query_extended**
76+
- Executes EXPLAIN EXTENDED on a SQL query to show detailed execution plan with additional information.
77+
- Parameters: `sql_query` (string, required), `database_name` (string, required), `parameters` (list, optional)
78+
- _Note: Provides comprehensive analysis including filtered rows percentage and extra optimization details._
79+
6680
### Vector Store & Embedding Tools (optional)
6781

6882
**Note**: These tools are only available when `EMBEDDING_PROVIDER` is configured. If no embedding provider is set, these tools will be disabled.

src/server.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ async def _execute_query(self, sql: str, params: Optional[tuple] = None, databas
112112
logger.error("Connection pool is not initialized.")
113113
raise RuntimeError("Database connection pool not available.")
114114

115-
allowed_prefixes = ('SELECT', 'SHOW', 'DESC', 'DESCRIBE', 'USE')
115+
allowed_prefixes = ('SELECT', 'SHOW', 'DESC', 'DESCRIBE', 'USE', 'EXPLAIN')
116116
query_upper = sql.strip().upper()
117117
is_allowed_read_query = any(query_upper.startswith(prefix) for prefix in allowed_prefixes)
118118

@@ -359,6 +359,52 @@ async def create_database(self, database_name: str) -> Dict[str, Any]:
359359
logger.error(f"TOOL ERROR: create_database. {error_message} Error: {e}", exc_info=True)
360360
raise RuntimeError(f"{error_message} Reason: {str(e)}")
361361

362+
async def explain_query(self, sql_query: str, database_name: str, parameters: Optional[List[Any]] = None) -> List[Dict[str, Any]]:
363+
"""
364+
Executes EXPLAIN on a SQL query to show the execution plan.
365+
This helps analyze query performance and optimization opportunities.
366+
Example `parameters`: ["value1", 123] corresponding to %s placeholders in `sql_query`.
367+
"""
368+
logger.info(f"TOOL START: explain_query called. database_name={database_name}, sql_query={sql_query[:100]}, parameters={parameters}")
369+
if database_name and not database_name.isidentifier():
370+
logger.warning(f"TOOL WARNING: explain_query called with invalid database_name: {database_name}")
371+
raise ValueError(f"Invalid database name provided: {database_name}")
372+
373+
# EXPLAIN 키워드를 쿼리 앞에 추가
374+
explain_sql = f"EXPLAIN {sql_query.strip()}"
375+
param_tuple = tuple(parameters) if parameters is not None else None
376+
377+
try:
378+
results = await self._execute_query(explain_sql, params=param_tuple, database=database_name)
379+
logger.info(f"TOOL END: explain_query completed. Execution plan rows returned: {len(results)}.")
380+
return results
381+
except Exception as e:
382+
logger.error(f"TOOL ERROR: explain_query failed for database_name={database_name}, sql_query={sql_query[:100]}, parameters={parameters}: {e}", exc_info=True)
383+
raise
384+
385+
async def explain_query_extended(self, sql_query: str, database_name: str, parameters: Optional[List[Any]] = None) -> List[Dict[str, Any]]:
386+
"""
387+
Executes EXPLAIN EXTENDED on a SQL query to show detailed execution plan with additional information.
388+
This provides more comprehensive analysis including filtered rows percentage and extra information.
389+
Example `parameters`: ["value1", 123] corresponding to %s placeholders in `sql_query`.
390+
"""
391+
logger.info(f"TOOL START: explain_query_extended called. database_name={database_name}, sql_query={sql_query[:100]}, parameters={parameters}")
392+
if database_name and not database_name.isidentifier():
393+
logger.warning(f"TOOL WARNING: explain_query_extended called with invalid database_name: {database_name}")
394+
raise ValueError(f"Invalid database name provided: {database_name}")
395+
396+
# EXPLAIN EXTENDED 키워드를 쿼리 앞에 추가
397+
explain_sql = f"EXPLAIN EXTENDED {sql_query.strip()}"
398+
param_tuple = tuple(parameters) if parameters is not None else None
399+
400+
try:
401+
results = await self._execute_query(explain_sql, params=param_tuple, database=database_name)
402+
logger.info(f"TOOL END: explain_query_extended completed. Extended execution plan rows returned: {len(results)}.")
403+
return results
404+
except Exception as e:
405+
logger.error(f"TOOL ERROR: explain_query_extended failed for database_name={database_name}, sql_query={sql_query[:100]}, parameters={parameters}: {e}", exc_info=True)
406+
raise
407+
362408
async def create_vector_store_tool(self,
363409
database_name: str,
364410
vector_store_name: str,
@@ -701,12 +747,15 @@ def register_tools(self):
701747
self.mcp.add_tool(self.get_table_schema)
702748
self.mcp.add_tool(self.execute_sql)
703749
self.mcp.add_tool(self.create_database)
750+
self.mcp.add_tool(self.explain_query)
751+
self.mcp.add_tool(self.explain_query_extended)
704752
if EMBEDDING_PROVIDER is not None:
705753
self.mcp.add_tool(self.create_vector_store)
706754
self.mcp.add_tool(self.list_vector_stores)
707755
self.mcp.add_tool(self.delete_vector_store)
708756
self.mcp.add_tool(self.insert_docs_vector_store)
709757
self.mcp.add_tool(self.search_vector_store)
758+
710759
logger.info("Registered MCP tools explicitly.")
711760

712761
# --- Async Main Server Logic ---

0 commit comments

Comments
 (0)