Skip to content

Commit d8bd1a4

Browse files
committed
Make include_plan_description configurable, so optionally sql query plans containing sensitive data can be masked
1 parent a7ccd60 commit d8bd1a4

File tree

4 files changed

+71
-3
lines changed

4 files changed

+71
-3
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ servers:
112112
auth: # optional
113113
username: "user"
114114
password: "pass"
115+
include_plan_description: false # optional, whether to include SQL execution plans by default (default: false)
115116
mcp:
116117
transports:
117118
- streamable-http # streamable-http or stdio.
@@ -185,7 +186,7 @@ The MCP server provides **18 specialized tools** organized by analysis patterns.
185186
*SQL performance analysis and execution plan comparison*
186187
| 🔧 Tool | 📝 Description |
187188
|---------|----------------|
188-
| `list_slowest_sql_queries` | 🐌 Get the top N slowest SQL queries for an application with detailed execution metrics |
189+
| `list_slowest_sql_queries` | 🐌 Get the top N slowest SQL queries for an application with detailed execution metrics and optional plan descriptions |
189190
| `compare_sql_execution_plans` | 🔍 Compare SQL execution plans between two Spark jobs, analyzing logical/physical plans and execution metrics |
190191

191192
### 🚨 Performance & Bottleneck Analysis
@@ -302,6 +303,7 @@ SHS_SERVERS_*_AUTH_TOKEN - Token for a specific server
302303
SHS_SERVERS_*_VERIFY_SSL - Whether to verify SSL for a specific server (true/false)
303304
SHS_SERVERS_*_TIMEOUT - HTTP request timeout in seconds for a specific server (default: 30)
304305
SHS_SERVERS_*_EMR_CLUSTER_ARN - EMR cluster ARN for a specific server
306+
SHS_SERVERS_*_INCLUDE_PLAN_DESCRIPTION - Whether to include SQL execution plans by default for a specific server (true/false, default: false)
305307
```
306308
307309
## 🤖 AI Agent Integration

src/spark_history_mcp/config/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class ServerConfig(BaseSettings):
2828
emr_cluster_arn: Optional[str] = None # EMR specific field
2929
use_proxy: bool = False
3030
timeout: int = 30 # HTTP request timeout in seconds
31+
include_plan_description: bool = False
3132

3233

3334
class McpConfig(BaseSettings):

src/spark_history_mcp/tools/tools.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,7 @@ def list_slowest_sql_queries(
922922
top_n: int = 1,
923923
page_size: int = 100,
924924
include_running: bool = False,
925-
include_plan_description: bool = True,
925+
include_plan_description: Optional[bool] = None,
926926
plan_description_max_length: int = 2000,
927927
) -> List[SqlQuerySummary]:
928928
"""
@@ -935,7 +935,7 @@ def list_slowest_sql_queries(
935935
top_n: Number of slowest queries to return (default: 1)
936936
page_size: Number of executions to fetch per page (default: 100)
937937
include_running: Whether to include running queries (default: False)
938-
include_plan_description: Whether to include execution plans (default: True)
938+
include_plan_description: Whether to include execution plans (uses server config if not specified)
939939
plan_description_max_length: Max characters for plan description (default: 1500)
940940
941941
Returns:
@@ -944,6 +944,10 @@ def list_slowest_sql_queries(
944944
ctx = mcp.get_context()
945945
client = get_client_or_default(ctx, server, app_id)
946946

947+
# Use server config if include_plan_description not explicitly provided
948+
if include_plan_description is None:
949+
include_plan_description = client.config.include_plan_description
950+
947951
all_executions: List[ExecutionData] = []
948952
offset = 0
949953

tests/unit/test_tools.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from unittest.mock import MagicMock, patch
44

55
from spark_history_mcp.api.spark_client import SparkRestClient
6+
from spark_history_mcp.config.config import ServerConfig
67
from spark_history_mcp.models.spark_types import (
78
ApplicationInfo,
89
ExecutionData,
@@ -1087,3 +1088,63 @@ def test_get_slowest_sql_queries_limit(self, mock_get_client):
10871088
self.assertEqual(result[0].duration, 10000)
10881089
self.assertEqual(result[1].duration, 9000)
10891090
self.assertEqual(result[2].duration, 8000)
1091+
1092+
@patch("spark_history_mcp.tools.tools.get_client_or_default")
1093+
def test_list_slowest_sql_queries_uses_server_config_for_plan_description(self, mock_get_client):
1094+
"""Test that include_plan_description falls back to server config when not provided"""
1095+
# Setup mock client with server config
1096+
mock_client = MagicMock()
1097+
server_config = ServerConfig(url="http://test:18080", include_plan_description=False)
1098+
mock_client.config = server_config
1099+
1100+
# Create mock SQL execution
1101+
sql = MagicMock(spec=ExecutionData)
1102+
sql.id = 1
1103+
sql.duration = 5000
1104+
sql.status = "COMPLETED"
1105+
sql.success_job_ids = [1]
1106+
sql.failed_job_ids = []
1107+
sql.running_job_ids = []
1108+
sql.description = "Test Query"
1109+
sql.submission_time = datetime.now()
1110+
sql.plan_description = "Sample plan description"
1111+
1112+
mock_client.get_sql_list.return_value = [sql]
1113+
mock_get_client.return_value = mock_client
1114+
1115+
# Call function without include_plan_description parameter (should use server config)
1116+
result = list_slowest_sql_queries("spark-app-123")
1117+
1118+
# Verify plan description is empty due to server config setting False
1119+
self.assertEqual(len(result), 1)
1120+
self.assertEqual(result[0].plan_description, "")
1121+
1122+
@patch("spark_history_mcp.tools.tools.get_client_or_default")
1123+
def test_list_slowest_sql_queries_explicit_override_server_config(self, mock_get_client):
1124+
"""Test that explicit include_plan_description parameter overrides server config"""
1125+
# Setup mock client with server config set to False
1126+
mock_client = MagicMock()
1127+
server_config = ServerConfig(url="http://test:18080", include_plan_description=False)
1128+
mock_client.config = server_config
1129+
1130+
# Create mock SQL execution
1131+
sql = MagicMock(spec=ExecutionData)
1132+
sql.id = 1
1133+
sql.duration = 5000
1134+
sql.status = "COMPLETED"
1135+
sql.success_job_ids = [1]
1136+
sql.failed_job_ids = []
1137+
sql.running_job_ids = []
1138+
sql.description = "Test Query"
1139+
sql.submission_time = datetime.now()
1140+
sql.plan_description = "Sample plan description"
1141+
1142+
mock_client.get_sql_list.return_value = [sql]
1143+
mock_get_client.return_value = mock_client
1144+
1145+
# Call function with explicit include_plan_description=True (should override server config)
1146+
result = list_slowest_sql_queries("spark-app-123", include_plan_description=True)
1147+
1148+
# Verify plan description is included despite server config being False
1149+
self.assertEqual(len(result), 1)
1150+
self.assertEqual(result[0].plan_description, "Sample plan description")

0 commit comments

Comments
 (0)