This is an automated email from the ASF dual-hosted git repository.

terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new 8bbf402e53b Refine MCP descriptor and workflow test helpers (#38976)
8bbf402e53b is described below

commit 8bbf402e53b4db74e24faddbecb199bf189d6cef
Author: Liang Zhang <[email protected]>
AuthorDate: Wed Jul 1 22:45:59 2026 +0800

    Refine MCP descriptor and workflow test helpers (#38976)
    
    * Fix documentation accuracy and consistency
    
    - align DistSQL syntax docs, return columns, and examples with current 
source
    - normalize SQL and server error code documentation
    - fix MCP resources, management metadata docs, links, versions, and typos
    
    * Fix documentation accuracy and consistency
    
    - align DistSQL syntax docs, return columns, and examples with current 
source
    - normalize SQL and server error code documentation
    - fix MCP resources, management metadata docs, links, versions, and typos
    
    * Refactor MCP workflow support duplication
    
    Centralize repeated workflow validation, identifier checks, DistSQL rule 
query fallback, and MCP error/recovery mapping while preserving existing 
behavior. Reuse shared helpers across MCP feature planning/validation services 
and simplify duplicated MCP E2E test setup helpers.
    
    * Refine MCP descriptor and workflow test helpers
    
    Extract shared MCP tool schema lookup helpers into
    MCPToolDescriptorValidationUtils and reuse them from descriptor validators.
    
    Simplify repeated MCP tool specification, JDBC metadata loader, and workflow
    execution test setup with focused test helpers while preserving existing 
test
    coverage and behavior.
---
 .../AbstractMCPToolSpecificationFactoryTest.java   |  24 +++-
 .../tool/MCPToolSpecificationFactoryTest.java      |  83 ++++--------
 .../descriptor/CoreToolDescriptorValidator.java    |  35 +----
 .../workflow/WorkflowExecutionServiceTest.java     | 144 +++++++--------------
 .../descriptor/MCPDescriptorCatalogValidator.java  |  20 +--
 .../MCPToolDescriptorValidationUtils.java          |  44 +++++++
 .../metadata/jdbc/MCPJdbcMetadataLoaderTest.java   |  66 +++++-----
 .../MCPToolDescriptorValidationUtilsTest.java      |  32 +++++
 8 files changed, 212 insertions(+), 236 deletions(-)

diff --git 
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/AbstractMCPToolSpecificationFactoryTest.java
 
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/AbstractMCPToolSpecificationFactoryTest.java
index 56d99424d52..1b5f3606823 100644
--- 
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/AbstractMCPToolSpecificationFactoryTest.java
+++ 
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/AbstractMCPToolSpecificationFactoryTest.java
@@ -122,6 +122,25 @@ abstract class AbstractMCPToolSpecificationFactoryTest {
         return result;
     }
     
+    protected SyncToolSpecification createToolSpecification(final String 
activeTransport) {
+        return createToolSpecification(createRuntimeContext(activeTransport));
+    }
+    
+    protected SyncToolSpecification createToolSpecification(final 
MCPRuntimeContext runtimeContext) {
+        return new 
MCPToolSpecificationFactory(runtimeContext).createToolSpecifications().getFirst();
+    }
+    
+    protected McpSyncServerExchange createExchange() {
+        McpSyncServerExchange result = mock(McpSyncServerExchange.class);
+        when(result.sessionId()).thenReturn("session-id");
+        return result;
+    }
+    
+    protected CallToolResult callTool(final SyncToolSpecification 
toolSpecification, final McpSyncServerExchange exchange, final String toolName,
+                                      final Map<String, Object> arguments) {
+        return toolSpecification.callHandler().apply(exchange, new 
CallToolRequest(toolName, arguments));
+    }
+    
     protected void assertStructuredFallback(final CallToolResult actual, final 
String expectedReason, final boolean expectedFormMode, final boolean 
expectedUrlMode,
                                             final String expectedInteraction) {
         Map<String, Object> actualPayload = getStructuredContent(actual);
@@ -160,10 +179,7 @@ abstract class AbstractMCPToolSpecificationFactoryTest {
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
             MCPRuntimeContext runtimeContext = mock(MCPRuntimeContext.class, 
RETURNS_DEEP_STUBS);
             
when(runtimeContext.getSessionManager().getTransactionResourceManager().getRuntimeDatabases()).thenReturn(Collections.emptyMap());
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(runtimeContext).createToolSpecifications().get(0);
-            McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
-            when(exchange.sessionId()).thenReturn("session-id");
-            return actualSpecification.callHandler().apply(exchange, new 
CallToolRequest(toolName, Map.of()));
+            return callTool(createToolSpecification(runtimeContext), 
createExchange(), toolName, Map.of());
         }
     }
     
diff --git 
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/MCPToolSpecificationFactoryTest.java
 
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/MCPToolSpecificationFactoryTest.java
index 5cfef51658c..3bc9b54783a 100644
--- 
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/MCPToolSpecificationFactoryTest.java
+++ 
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/MCPToolSpecificationFactoryTest.java
@@ -140,9 +140,8 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             MCPResponse response = new MCPMapResponse(Map.of("status", "ok"));
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createToolDescriptor("database_gateway_search_metadata"));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
-            McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
-            when(exchange.sessionId()).thenReturn("session-id");
+            SyncToolSpecification actualSpecification = 
createToolSpecification("stdio");
+            McpSyncServerExchange exchange = createExchange();
             CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new 
CallToolRequest("database_gateway_search_metadata", null));
             assertThat(actual.structuredContent(), is(Map.of("status", "ok")));
             assertThat(((TextContent) actual.content().getFirst()).text(), 
is("{\"status\":\"ok\"}"));
@@ -155,10 +154,7 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
         try (MockedStatic<ToolDefinitionRegistry> mockedToolDefinitionRegistry 
= mockStatic(ToolDefinitionRegistry.class)) {
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createToolDescriptor("database_gateway_search_metadata"));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of("query", "foo_query"), new MCPErrorResponse(""));
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
-            McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
-            when(exchange.sessionId()).thenReturn("session-id");
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new 
CallToolRequest("database_gateway_search_metadata", Map.of("query", 
"foo_query")));
+            CallToolResult actual = callTool(createToolSpecification("stdio"), 
createExchange(), "database_gateway_search_metadata", Map.of("query", 
"foo_query"));
             @SuppressWarnings("unchecked")
             Map<String, Object> actualPayload = (Map<String, Object>) 
actual.structuredContent();
             assertThat(actualPayload.get("response_mode"), is("recovery"));
@@ -262,10 +258,7 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
         try (MockedStatic<ToolDefinitionRegistry> mockedToolDefinitionRegistry 
= mockStatic(ToolDefinitionRegistry.class)) {
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createToolDescriptor("fixture_ping"));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), new MCPMapResponse(Map.of("status", "ok")));
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
-            McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
-            when(exchange.sessionId()).thenReturn("session-id");
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new 
CallToolRequest("fixture_ping", Map.of()));
+            CallToolResult actual = callTool(createToolSpecification("stdio"), 
createExchange(), "fixture_ping", Map.of());
             assertThat(actual.structuredContent(), is(Map.of("status", "ok")));
             assertThat(((TextContent) actual.content().getFirst()).text(), 
is("{\"status\":\"ok\"}"));
         }
@@ -278,10 +271,9 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             mockedToolDefinitionRegistry.when(() -> 
ToolDefinitionRegistry.getToolDefinition("database_gateway_search_metadata")).thenThrow(UnsupportedToolException.class);
             MCPRuntimeContext runtimeContext = mock(MCPRuntimeContext.class, 
RETURNS_DEEP_STUBS);
             
when(runtimeContext.getSessionManager().getTransactionResourceManager().getRuntimeDatabases()).thenReturn(Collections.emptyMap());
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(runtimeContext).createToolSpecifications().getFirst();
-            McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
-            when(exchange.sessionId()).thenReturn("session-id");
-            McpError actual = assertThrows(McpError.class, () -> 
actualSpecification.callHandler().apply(exchange, new 
CallToolRequest("database_gateway_search_metadata", Map.of())));
+            SyncToolSpecification actualSpecification = 
createToolSpecification(runtimeContext);
+            McpSyncServerExchange exchange = createExchange();
+            McpError actual = assertThrows(McpError.class, () -> 
callTool(actualSpecification, exchange, "database_gateway_search_metadata", 
Map.of()));
             assertThat(actual.getJsonRpcError().code(), 
is(McpSchema.ErrorCodes.INVALID_PARAMS));
             assertThat(actual.getJsonRpcError().message(), is("Unsupported 
tool `database_gateway_search_metadata`."));
             @SuppressWarnings("unchecked")
@@ -295,10 +287,7 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
         MCPRuntimeContext runtimeContext = mock(MCPRuntimeContext.class, 
RETURNS_DEEP_STUBS);
         
when(runtimeContext.getSessionManager().getTransactionResourceManager().getRuntimeDatabases()).thenReturn(Collections.emptyMap());
         SyncToolSpecification actualSpecification = findToolSpecification(new 
MCPToolSpecificationFactory(runtimeContext).createToolSpecifications(), 
"database_gateway_search_metadata");
-        McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
-        when(exchange.sessionId()).thenReturn("session-id");
-        CallToolResult actual = 
actualSpecification.callHandler().apply(exchange,
-                new CallToolRequest("database_gateway_search_metadata", 
Map.of("query", "order", "object_types", List.of("TABLE"))));
+        CallToolResult actual = callTool(actualSpecification, 
createExchange(), "database_gateway_search_metadata", Map.of("query", "order", 
"object_types", List.of("TABLE")));
         @SuppressWarnings("unchecked")
         Map<String, Object> actualPayload = (Map<String, Object>) 
actual.structuredContent();
         Map<?, ?> actualRecovery = (Map<?, ?>) actualPayload.get("recovery");
@@ -317,10 +306,7 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), new MCPMapResponse(Map.of("count", 1)));
             MCPRuntimeContext runtimeContext = mock(MCPRuntimeContext.class, 
RETURNS_DEEP_STUBS);
             
when(runtimeContext.getSessionManager().getTransactionResourceManager().getRuntimeDatabases()).thenReturn(Collections.emptyMap());
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(runtimeContext).createToolSpecifications().getFirst();
-            McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
-            when(exchange.sessionId()).thenReturn("session-id");
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new 
CallToolRequest("database_gateway_search_metadata", Map.of()));
+            CallToolResult actual = 
callTool(createToolSpecification(runtimeContext), createExchange(), 
"database_gateway_search_metadata", Map.of());
             @SuppressWarnings("unchecked")
             Map<String, Object> actualPayload = (Map<String, Object>) 
actual.structuredContent();
             
assertTrue(String.valueOf(actualPayload.get("message")).contains("database_gateway_search_metadata"));
@@ -351,10 +337,9 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createPlanningToolDescriptor(toolName));
             mockedToolDefinitionRegistry.when(() -> 
ToolDefinitionRegistry.dispatch(any(MCPRequestScope.class), eq(toolDefinition), 
eq("session-id"), any()))
                     .thenReturn(clarifyingResponse, plannedResponse);
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
             McpSyncServerExchange exchange = createElicitationExchange(new 
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT,
                     Map.of("field_1", "foo_display", "field_2", true)), 
clientCapabilities);
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new CallToolRequest(toolName, 
Map.of()));
+            CallToolResult actual = callTool(createToolSpecification("stdio"), 
exchange, toolName, Map.of());
             assertThat(actual.structuredContent(), is(Map.of("status", 
"planned")));
             ArgumentCaptor<McpSchema.ElicitRequest> requestCaptor = 
ArgumentCaptor.forClass(McpSchema.ElicitRequest.class);
             verify(exchange).createElicitation(requestCaptor.capture());
@@ -389,9 +374,8 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
                     
createClarifyingQuestion("primary_algorithm_properties.access-token", "string", 
false, "Provide access token.")));
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createPlanningToolDescriptor(toolName));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
             McpSyncServerExchange exchange = createElicitationExchange(new 
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of()), 
createFormAndUrlClientCapabilities());
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new CallToolRequest(toolName, 
Map.of()));
+            CallToolResult actual = callTool(createToolSpecification("stdio"), 
exchange, toolName, Map.of());
             assertStructuredFallback(actual, "url_mode_not_implemented", true, 
true, "url_fallback");
             assertSanitizedSensitiveFallback(actual);
             verify(exchange, never()).createElicitation(any());
@@ -423,9 +407,8 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             MCPResponse response = new MCPMapResponse(expectedPayload);
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createAmbiguousPlanningToolDescriptor(toolName));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
             McpSyncServerExchange exchange = createElicitationExchange(new 
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of("field_1", 
true)));
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new CallToolRequest(toolName, 
Map.of()));
+            CallToolResult actual = callTool(createToolSpecification("stdio"), 
exchange, toolName, Map.of());
             assertStructuredFallback(actual, "ambiguous_field_binding", true, 
false, "structured_fallback");
             verify(exchange, never()).createElicitation(any());
         }
@@ -439,9 +422,8 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             MCPResponse response = new MCPMapResponse(expectedPayload);
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createPlanningToolDescriptor(toolName));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
             McpSyncServerExchange exchange = 
createUrlOnlyElicitationExchange();
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new CallToolRequest(toolName, 
Map.of()));
+            CallToolResult actual = callTool(createToolSpecification("stdio"), 
exchange, toolName, Map.of());
             assertStructuredFallback(actual, "client_unsupported", false, 
true, "structured_fallback");
             verify(exchange, never()).createElicitation(any());
         }
@@ -454,9 +436,8 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             MCPResponse response = new 
MCPMapResponse(createClarifyingPayload());
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createPlanningToolDescriptor(toolName));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("http")).createToolSpecifications().getFirst();
             McpSyncServerExchange exchange = createElicitationExchange(new 
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of("field_1", 
"foo_display")));
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new CallToolRequest(toolName, 
Map.of()));
+            CallToolResult actual = callTool(createToolSpecification("http"), 
exchange, toolName, Map.of());
             assertStructuredFallback(actual, "remote_identity_required", true, 
false, "structured_fallback");
             verify(exchange, never()).createElicitation(any());
         }
@@ -469,9 +450,8 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             MCPResponse response = new MCPMapResponse(expectedPayload);
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createPlanningToolDescriptor(toolName));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
             McpSyncServerExchange exchange = createElicitationExchange(new 
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, 
Map.of("custom_properties.display-name", "foo_display")));
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new CallToolRequest(toolName, 
Map.of()));
+            CallToolResult actual = callTool(createToolSpecification("stdio"), 
exchange, toolName, Map.of());
             assertStructuredFallback(actual, "sensitive_form_blocked", true, 
false, "url_fallback");
             assertSanitizedSensitiveFallback(actual);
             verify(exchange, never()).createElicitation(any());
@@ -485,9 +465,8 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             MCPResponse response = new MCPMapResponse(expectedPayload);
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createPlanningToolDescriptor(toolName));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
             McpSyncServerExchange exchange = createElicitationExchange(new 
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of("field_1", 
"foo_display")));
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new CallToolRequest(toolName, 
Map.of()));
+            CallToolResult actual = callTool(createToolSpecification("stdio"), 
exchange, toolName, Map.of());
             assertStructuredFallback(actual, expectedReason, true, false, 
expectedInteraction);
             if ("url_fallback".equals(expectedInteraction)) {
                 assertSanitizedSensitiveFallback(actual);
@@ -505,9 +484,8 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
             MCPRuntimeContext runtimeContext = mock(MCPRuntimeContext.class, 
RETURNS_DEEP_STUBS);
             
when(runtimeContext.getSessionManager().getTransactionResourceManager().getRuntimeDatabases()).thenReturn(Collections.emptyMap());
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(runtimeContext).createToolSpecifications().getFirst();
             McpSyncServerExchange exchange = createElicitationExchange(new 
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of()));
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new 
CallToolRequest("database_gateway_search_metadata", Map.of()));
+            CallToolResult actual = 
callTool(createToolSpecification(runtimeContext), exchange, 
"database_gateway_search_metadata", Map.of());
             assertThat(actual.structuredContent(), is(expectedPayload));
             verify(exchange, never()).createElicitation(any());
         }
@@ -522,9 +500,8 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
             MCPRuntimeContext runtimeContext = mock(MCPRuntimeContext.class, 
RETURNS_DEEP_STUBS);
             
when(runtimeContext.getSessionManager().getTransactionResourceManager().getRuntimeDatabases()).thenReturn(Collections.emptyMap());
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(runtimeContext).createToolSpecifications().getFirst();
             McpSyncServerExchange exchange = createElicitationExchange(new 
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of()));
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new 
CallToolRequest("fixture_ping", Map.of()));
+            CallToolResult actual = 
callTool(createToolSpecification(runtimeContext), exchange, "fixture_ping", 
Map.of());
             assertThat(actual.structuredContent(), is(expectedPayload));
             verify(exchange, never()).createElicitation(any());
         }
@@ -536,10 +513,8 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             MCPResponse response = new 
MCPMapResponse(createClarifyingPayload());
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createPlanningToolDescriptor("database_gateway_plan_encrypt_rule"));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
-            McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
-            when(exchange.sessionId()).thenReturn("session-id");
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new 
CallToolRequest("database_gateway_plan_encrypt_rule", Map.of()));
+            McpSyncServerExchange exchange = createExchange();
+            CallToolResult actual = callTool(createToolSpecification("stdio"), 
exchange, "database_gateway_plan_encrypt_rule", Map.of());
             assertStructuredFallback(actual, "client_unsupported", false, 
false, "structured_fallback");
             verify(exchange, never()).createElicitation(any());
         }
@@ -551,10 +526,9 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             MCPResponse response = new 
MCPMapResponse(createClarifyingPayload());
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createPlanningToolDescriptor("database_gateway_plan_encrypt_rule"));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
             McpSchema.ClientCapabilities clientCapabilities = new 
McpSchema.ClientCapabilities(Collections.emptyMap(), null, null, null);
             McpSyncServerExchange exchange = createElicitationExchange(new 
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of()), 
clientCapabilities);
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new 
CallToolRequest("database_gateway_plan_encrypt_rule", Map.of()));
+            CallToolResult actual = callTool(createToolSpecification("stdio"), 
exchange, "database_gateway_plan_encrypt_rule", Map.of());
             assertStructuredFallback(actual, "client_unsupported", false, 
false, "structured_fallback");
             verify(exchange, never()).createElicitation(any());
         }
@@ -603,9 +577,8 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             MCPResponse response = new 
MCPMapResponse(createClarifyingPayload());
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createPlanningToolDescriptor("database_gateway_plan_encrypt_rule"));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
             McpSyncServerExchange exchange = 
createThrowingElicitationExchange();
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new 
CallToolRequest("database_gateway_plan_encrypt_rule", Map.of()));
+            CallToolResult actual = callTool(createToolSpecification("stdio"), 
exchange, "database_gateway_plan_encrypt_rule", Map.of());
             assertStructuredFallback(actual, "elicitation_failed", true, 
false, "structured_fallback");
             verify(exchange).createElicitation(any());
         }
@@ -626,13 +599,12 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             MCPResponse response = new 
MCPMapResponse(createClarifyingPayload());
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createPlanningToolDescriptor("database_gateway_plan_encrypt_rule"));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
             McpSyncServerExchange exchange = createElicitationExchange(new 
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of("field_1", 
"foo_display", "field_2", true)));
             when(exchange.createElicitation(any())).thenAnswer(invocation -> {
                 clock.advance(Duration.ofMinutes(11L));
                 return new 
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of("field_1", 
"foo_display", "field_2", true));
             });
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new 
CallToolRequest("database_gateway_plan_encrypt_rule", Map.of()));
+            CallToolResult actual = callTool(createToolSpecification("stdio"), 
exchange, "database_gateway_plan_encrypt_rule", Map.of());
             assertStructuredFallback(actual, "stale_elicitation", true, false, 
"structured_fallback");
             mockedToolDefinitionRegistry.verify(() -> 
ToolDefinitionRegistry.dispatch(any(MCPRequestScope.class), eq(toolDefinition), 
eq("session-id"), eq(createElicitedArguments())), never());
         }
@@ -647,9 +619,8 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             MCPResponse response = new MCPMapResponse(expectedPayload);
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createPlanningToolDescriptor("database_gateway_plan_encrypt_rule"));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
             McpSyncServerExchange exchange = createElicitationExchange(new 
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, elicitedContent));
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new 
CallToolRequest("database_gateway_plan_encrypt_rule", Map.of()));
+            CallToolResult actual = callTool(createToolSpecification("stdio"), 
exchange, "database_gateway_plan_encrypt_rule", Map.of());
             assertStructuredFallback(actual, "invalid_elicited_content", true, 
false, "structured_fallback");
             verify(exchange).createElicitation(any());
             mockedToolDefinitionRegistry.verify(() -> 
ToolDefinitionRegistry.dispatch(any(MCPRequestScope.class), eq(toolDefinition), 
eq("session-id"), eq(createElicitedArguments())), never());
@@ -661,9 +632,8 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             MCPResponse response = new 
MCPMapResponse(createClarifyingPayload());
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createPlanningToolDescriptor("database_gateway_plan_encrypt_rule"));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
             McpSyncServerExchange exchange = 
createElicitationExchange(elicitedResult);
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new 
CallToolRequest("database_gateway_plan_encrypt_rule", Map.of()));
+            CallToolResult actual = callTool(createToolSpecification("stdio"), 
exchange, "database_gateway_plan_encrypt_rule", Map.of());
             assertStructuredFallback(actual, expectedReason, true, false, 
"structured_fallback");
             verify(exchange).createElicitation(any());
             mockedToolDefinitionRegistry.verify(() -> 
ToolDefinitionRegistry.dispatch(any(MCPRequestScope.class), eq(toolDefinition), 
eq("session-id"), eq(createElicitedArguments())), never());
@@ -763,9 +733,8 @@ class MCPToolSpecificationFactoryTest extends 
AbstractMCPToolSpecificationFactor
             MCPResponse response = new MCPMapResponse(expectedPayload);
             MCPToolDefinition toolDefinition = 
mockSupportedTool(mockedToolDefinitionRegistry, 
createPlanningToolDescriptor("database_gateway_plan_encrypt_rule"));
             mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition, 
Map.of(), response);
-            SyncToolSpecification actualSpecification = new 
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
             McpSyncServerExchange exchange = createElicitationExchange(new 
McpSchema.ElicitResult(action, Map.of()));
-            CallToolResult actual = 
actualSpecification.callHandler().apply(exchange, new 
CallToolRequest("database_gateway_plan_encrypt_rule", Map.of()));
+            CallToolResult actual = callTool(createToolSpecification("stdio"), 
exchange, "database_gateway_plan_encrypt_rule", Map.of());
             assertThat(actual.structuredContent(), is(expectedPayload));
             verify(exchange).createElicitation(any());
         }
diff --git 
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidator.java
 
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidator.java
index 0fee9873b34..b06bceaa202 100644
--- 
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidator.java
+++ 
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidator.java
@@ -26,7 +26,6 @@ import 
org.apache.shardingsphere.mcp.support.protocol.MCPPayloadFieldNames;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 
 /**
@@ -102,9 +101,9 @@ public final class CoreToolDescriptorValidator implements 
MCPToolDescriptorValid
     }
     
     private void validateExecuteUpdateDescriptor(final MCPToolDescriptor 
descriptor) {
-        Map<?, ?> executionMode = findToolInputProperty(descriptor, 
MCPPayloadFieldNames.EXECUTION_MODE).orElseThrow(
+        Map<?, ?> executionMode = 
MCPToolDescriptorValidationUtils.findToolInputProperty(descriptor, 
MCPPayloadFieldNames.EXECUTION_MODE).orElseThrow(
                 () -> new IllegalStateException("Tool 
`database_gateway_execute_update` must declare execution_mode."));
-        ShardingSpherePreconditions.checkState(isRequiredToolInput(descriptor, 
MCPPayloadFieldNames.EXECUTION_MODE),
+        
ShardingSpherePreconditions.checkState(MCPToolDescriptorValidationUtils.isRequiredToolInput(descriptor,
 MCPPayloadFieldNames.EXECUTION_MODE),
                 () -> new IllegalStateException("Tool 
`database_gateway_execute_update` execution_mode must be required."));
         Object executionModes = executionMode.get("enum");
         ShardingSpherePreconditions.checkState(executionModes instanceof 
Collection && ((Collection<?>) executionModes).containsAll(List.of("execute", 
"preview")),
@@ -112,17 +111,17 @@ public final class CoreToolDescriptorValidator implements 
MCPToolDescriptorValid
     }
     
     private void validateRuntimeDatabaseContract(final MCPToolDescriptor 
descriptor) {
-        Map<?, ?> responseMode = findToolOutputProperty(descriptor, 
"response_mode").orElseThrow(
+        Map<?, ?> responseMode = 
MCPToolDescriptorValidationUtils.findToolOutputProperty(descriptor, 
"response_mode").orElseThrow(
                 () -> new IllegalStateException("Tool 
`database_gateway_validate_runtime_database` must declare response_mode."));
         Object responseModes = responseMode.get("enum");
         ShardingSpherePreconditions.checkState(responseModes instanceof 
Collection && ((Collection<?>) responseModes).contains("validation"),
                 () -> new IllegalStateException("Tool 
`database_gateway_validate_runtime_database` response_mode must allow 
validation."));
-        
ShardingSpherePreconditions.checkState(findToolInputProperty(descriptor, 
"database").isPresent(),
+        
ShardingSpherePreconditions.checkState(MCPToolDescriptorValidationUtils.findToolInputProperty(descriptor,
 "database").isPresent(),
                 () -> new IllegalStateException("Tool 
`database_gateway_validate_runtime_database` must declare `database`."));
-        ShardingSpherePreconditions.checkState(isRequiredToolInput(descriptor, 
"database"),
+        
ShardingSpherePreconditions.checkState(MCPToolDescriptorValidationUtils.isRequiredToolInput(descriptor,
 "database"),
                 () -> new IllegalStateException("Tool 
`database_gateway_validate_runtime_database` database must be required."));
         for (String each : List.of("databaseType", "jdbcUrl", "username", 
"password", "driverClassName")) {
-            
ShardingSpherePreconditions.checkState(findToolInputProperty(descriptor, 
each).isEmpty(),
+            
ShardingSpherePreconditions.checkState(MCPToolDescriptorValidationUtils.findToolInputProperty(descriptor,
 each).isEmpty(),
                     () -> new IllegalStateException(String.format("Tool 
`database_gateway_validate_runtime_database` must not expose `%s`.", each)));
         }
     }
@@ -144,26 +143,4 @@ public final class CoreToolDescriptorValidator implements 
MCPToolDescriptorValid
         }
     }
     
-    private Optional<Map<?, ?>> findToolInputProperty(final MCPToolDescriptor 
descriptor, final String fieldName) {
-        Object properties = descriptor.getInputSchema().get("properties");
-        if (!(properties instanceof Map)) {
-            return Optional.empty();
-        }
-        Object property = ((Map<?, ?>) properties).get(fieldName);
-        return property instanceof Map ? Optional.of((Map<?, ?>) property) : 
Optional.empty();
-    }
-    
-    private Optional<Map<?, ?>> findToolOutputProperty(final MCPToolDescriptor 
descriptor, final String fieldName) {
-        Object properties = descriptor.getOutputSchema().get("properties");
-        if (!(properties instanceof Map)) {
-            return Optional.empty();
-        }
-        Object property = ((Map<?, ?>) properties).get(fieldName);
-        return property instanceof Map ? Optional.of((Map<?, ?>) property) : 
Optional.empty();
-    }
-    
-    private boolean isRequiredToolInput(final MCPToolDescriptor descriptor, 
final String fieldName) {
-        Object required = descriptor.getInputSchema().get("required");
-        return required instanceof Collection && ((Collection<?>) 
required).contains(fieldName);
-    }
 }
diff --git 
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionServiceTest.java
 
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionServiceTest.java
index cab4a8820f8..76261c5c109 100644
--- 
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionServiceTest.java
+++ 
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionServiceTest.java
@@ -65,11 +65,7 @@ class WorkflowExecutionServiceTest {
         snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER 
TABLE orders ADD COLUMN phone_mask VARCHAR(64)", 10));
         snapshot.getIndexPlans().add(new IndexPlan("idx_orders_phone_mask", 
"phone_mask", "mask lookup", "CREATE INDEX idx_orders_phone_mask ON 
orders(phone_mask)"));
         snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE 
ENCRYPT RULE orders (PROPERTIES('aes-key-value'='123456'))"));
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                mock(MCPFeatureExecutionFacade.class), 
MCPWorkflowApplySynchronizationHandler.NO_OP, "session-1", snapshot, List.of(), 
"manual-only");
+        Map<String, Object> actualResponse = apply(snapshot, List.of(), 
"manual-only");
         assertThat(actualResponse.get("status"), 
is("awaiting-manual-execution"));
         assertThat(actualResponse.get("response_mode"), is("manual_only"));
         assertThat(actualResponse.get("plan_id"), is("plan-1"));
@@ -99,11 +95,7 @@ class WorkflowExecutionServiceTest {
         snapshot.setWorkflowKind(WorkflowKind.valueOf("encrypt.rule"));
         snapshot.getPropertyRequirements().add(new 
AlgorithmPropertyRequirement("primary", "aes-key-value", true, true, "AES 
key.", ""));
         snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE 
ENCRYPT RULE orders (PROPERTIES('aes-key-value'='123456'))"));
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                mock(MCPFeatureExecutionFacade.class), 
MCPWorkflowApplySynchronizationHandler.NO_OP, "session-1", snapshot, List.of(), 
"manual-only");
+        Map<String, Object> actualResponse = apply(snapshot, List.of(), 
"manual-only");
         assertFalse(actualResponse.containsKey("executed_ddl"));
         Map<?, ?> actualManualArtifactSummary = (Map<?, ?>) 
actualResponse.get("manual_artifact_summary");
         
assertFalse(actualManualArtifactSummary.containsKey("ddl_artifact_count"));
@@ -133,13 +125,9 @@ class WorkflowExecutionServiceTest {
     
     @Test
     void assertApplyRejectsInvalidLifecycleStatus() {
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
         WorkflowContextSnapshot snapshot = createSnapshot();
         snapshot.setStatus("clarifying");
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                mock(MCPFeatureExecutionFacade.class), 
MCPWorkflowApplySynchronizationHandler.NO_OP, "session-1", snapshot, List.of(), 
"review-then-execute");
+        Map<String, Object> actualResponse = apply(snapshot, List.of(), 
"review-then-execute");
         assertThat(actualResponse.get("status"), is("failed"));
         assertThat(actualResponse.get("response_mode"), is("terminal"));
         assertThat(actualResponse.get("plan_id"), is("plan-1"));
@@ -179,13 +167,9 @@ class WorkflowExecutionServiceTest {
         WorkflowContextSnapshot snapshot = createSnapshot();
         snapshot.setStatus("previewed");
         snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER 
TABLE orders ADD COLUMN order_id_cipher VARCHAR(32)", 10));
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
         MCPFeatureExecutionFacade executionFacade = 
mock(MCPFeatureExecutionFacade.class);
         
when(executionFacade.execute(any())).thenReturn(mock(SQLExecutionResponse.class));
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP, 
"session-1", snapshot, List.of(), "review-then-execute");
+        Map<String, Object> actualResponse = apply(snapshot, executionFacade, 
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of(), "review-then-execute");
         assertThat(actualResponse.get("status"), is("failed"));
         assertThat(((Map<?, ?>) ((List<?>) 
actualResponse.get("issues")).getFirst()).get("code"), 
is(WorkflowIssueCode.WORKFLOW_STATUS_INVALID));
         verify(executionFacade, never()).execute(any());
@@ -195,12 +179,8 @@ class WorkflowExecutionServiceTest {
     void assertApplyRejectsExecutionBeforePreview() {
         WorkflowContextSnapshot snapshot = createSnapshot();
         snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER 
TABLE orders ADD COLUMN order_id_cipher VARCHAR(32)", 10));
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
         MCPFeatureExecutionFacade executionFacade = 
mock(MCPFeatureExecutionFacade.class);
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP, 
"session-1", snapshot, List.of("ddl"), "review-then-execute");
+        Map<String, Object> actualResponse = apply(snapshot, executionFacade, 
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of("ddl"), 
"review-then-execute");
         assertThat(actualResponse.get("status"), is("failed"));
         assertThat(((Map<?, ?>) ((List<?>) 
actualResponse.get("issues")).getFirst()).get("code"), 
is(WorkflowIssueCode.WORKFLOW_STATUS_INVALID));
         verify(executionFacade, never()).execute(any());
@@ -211,12 +191,8 @@ class WorkflowExecutionServiceTest {
         WorkflowContextSnapshot snapshot = createSnapshot();
         snapshot.setStatus("previewed");
         snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE 
MASK RULE orders"));
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
         MCPFeatureExecutionFacade executionFacade = 
mock(MCPFeatureExecutionFacade.class);
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP, 
"session-1", snapshot, List.of("ddl"), "review-then-execute");
+        Map<String, Object> actualResponse = apply(snapshot, executionFacade, 
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of("ddl"), 
"review-then-execute");
         assertThat(actualResponse.get("status"), is("failed"));
         assertThat(((Map<?, ?>) ((List<?>) 
actualResponse.get("issues")).getFirst()).get("code"), 
is(WorkflowIssueCode.WORKFLOW_STATUS_INVALID));
         verify(executionFacade, never()).execute(any());
@@ -227,13 +203,10 @@ class WorkflowExecutionServiceTest {
         WorkflowContextSnapshot snapshot = createSnapshot();
         snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER 
TABLE orders ADD COLUMN order_id_cipher VARCHAR(32)", 10));
         snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE 
MASK RULE orders"));
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
+        WorkflowSessionContext workflowSessionContext = 
createWorkflowSessionContext(snapshot);
         MCPFeatureExecutionFacade executionFacade = 
mock(MCPFeatureExecutionFacade.class);
         MCPWorkflowApplySynchronizationHandler 
workflowApplySynchronizationHandler = 
mock(MCPWorkflowApplySynchronizationHandler.class);
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                executionFacade, workflowApplySynchronizationHandler, 
"session-1", snapshot, List.of(), "preview");
+        Map<String, Object> actualResponse = apply(workflowSessionContext, 
snapshot, executionFacade, workflowApplySynchronizationHandler, List.of(), 
"preview");
         assertThat(actualResponse.get("status"), is("preview"));
         assertThat(actualResponse.get("response_mode"), is("preview"));
         assertFalse((boolean) actualResponse.get("would_apply"));
@@ -265,11 +238,7 @@ class WorkflowExecutionServiceTest {
     @Test
     void assertApplyPreviewWithoutArtifacts() {
         WorkflowContextSnapshot snapshot = createSnapshot();
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                mock(MCPFeatureExecutionFacade.class), 
MCPWorkflowApplySynchronizationHandler.NO_OP, "session-1", snapshot, List.of(), 
"preview");
+        Map<String, Object> actualResponse = apply(snapshot, List.of(), 
"preview");
         assertThat(actualResponse.get("review_summary"), is("Previewed 0 
workflow artifacts. Nothing has been applied."));
         assertFalse(actualResponse.containsKey("approval_question"));
         assertThat(((Map<?, ?>) ((List<?>) 
actualResponse.get("next_actions")).getFirst()).get("type"), is("terminal"));
@@ -282,11 +251,7 @@ class WorkflowExecutionServiceTest {
         snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER 
TABLE orders ADD COLUMN order_id_cipher VARCHAR(32)", 10));
         snapshot.getIndexPlans().add(new 
IndexPlan("idx_orders_order_id_cipher", "order_id_cipher", "assist lookup", 
"CREATE INDEX idx_orders_order_id_cipher ON orders(order_id_cipher)"));
         snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE 
ENCRYPT RULE orders"));
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                mock(MCPFeatureExecutionFacade.class), 
MCPWorkflowApplySynchronizationHandler.NO_OP, "session-1", snapshot, List.of(), 
"preview");
+        Map<String, Object> actualResponse = apply(snapshot, List.of(), 
"preview");
         assertFalse(actualResponse.containsKey("executed_ddl"));
         assertThat(((List<?>) actualResponse.get("preview_artifacts")).size(), 
is(1));
         Map<?, ?> actualManualArtifactPackage = (Map<?, ?>) 
actualResponse.get("manual_artifact_package");
@@ -303,11 +268,7 @@ class WorkflowExecutionServiceTest {
         snapshot.setWorkflowKind(WorkflowKind.valueOf("encrypt.rule"));
         snapshot.getPropertyRequirements().add(new 
AlgorithmPropertyRequirement("primary", "aes-key-value", true, true, "AES 
key.", ""));
         snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE 
ENCRYPT RULE orders (PROPERTIES('aes-key-value'='123456'))"));
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                mock(MCPFeatureExecutionFacade.class), 
MCPWorkflowApplySynchronizationHandler.NO_OP, "session-1", snapshot, List.of(), 
"preview");
+        Map<String, Object> actualResponse = apply(snapshot, List.of(), 
"preview");
         Map<?, ?> actualPreviewArtifact = (Map<?, ?>) ((List<?>) 
actualResponse.get("preview_artifacts")).getFirst();
         assertThat(actualPreviewArtifact.get("sql"), is("CREATE ENCRYPT RULE 
orders (PROPERTIES('aes-key-value'='******'))"));
         assertFalse(String.valueOf(actualResponse).contains("123456"));
@@ -346,11 +307,7 @@ class WorkflowExecutionServiceTest {
         WorkflowContextSnapshot snapshot = createSnapshot();
         snapshot.getRequest().setExecutionMode("manual-only");
         snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE 
MASK RULE orders"));
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                mock(MCPFeatureExecutionFacade.class), 
MCPWorkflowApplySynchronizationHandler.NO_OP, "session-1", snapshot, List.of(), 
"preview");
+        Map<String, Object> actualResponse = apply(snapshot, List.of(), 
"preview");
         List<?> actualNextActions = (List<?>) 
actualResponse.get("next_actions");
         assertThat(actualNextActions.size(), is(1));
         Map<?, ?> actualNextAction = (Map<?, ?>) actualNextActions.getFirst();
@@ -364,13 +321,11 @@ class WorkflowExecutionServiceTest {
         snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER 
TABLE orders ADD COLUMN order_id_cipher VARCHAR(32)", 10));
         snapshot.getIndexPlans().add(new 
IndexPlan("idx_orders_order_id_cipher", "order_id_cipher", "assist lookup", 
"CREATE INDEX idx_orders_order_id_cipher ON orders(order_id_cipher)"));
         snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE 
MASK RULE orders"));
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
+        WorkflowSessionContext workflowSessionContext = 
createWorkflowSessionContext(snapshot);
         MCPFeatureExecutionFacade executionFacade = 
mock(MCPFeatureExecutionFacade.class);
         
when(executionFacade.execute(any())).thenReturn(mock(SQLExecutionResponse.class));
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP, 
"session-1", snapshot, List.of("ddl", "index_ddl", "rule_distsql"), 
"review-then-execute");
+        Map<String, Object> actualResponse = apply(workflowSessionContext, 
snapshot, executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP,
+                List.of("ddl", "index_ddl", "rule_distsql"), 
"review-then-execute");
         assertThat(actualResponse.get("status"), is("completed"));
         assertThat(actualResponse.get("response_mode"), is("executed"));
         assertThat(((List<?>) actualResponse.get("applied_artifacts")).size(), 
is(3));
@@ -387,13 +342,9 @@ class WorkflowExecutionServiceTest {
         snapshot.setWorkflowKind(WorkflowKind.valueOf("encrypt.rule"));
         snapshot.getPropertyRequirements().add(new 
AlgorithmPropertyRequirement("primary", "aes-key-value", true, true, "AES 
key.", ""));
         snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE 
ENCRYPT RULE orders (PROPERTIES('aes-key-value'='123456'))"));
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
         MCPFeatureExecutionFacade executionFacade = 
mock(MCPFeatureExecutionFacade.class);
         
when(executionFacade.execute(any())).thenReturn(mock(SQLExecutionResponse.class));
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP, 
"session-1", snapshot, List.of("rule_distsql"), "review-then-execute");
+        Map<String, Object> actualResponse = apply(snapshot, executionFacade, 
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of("rule_distsql"), 
"review-then-execute");
         assertThat(actualResponse.get("status"), is("completed"));
         assertThat(((List<?>) 
actualResponse.get("executed_distsql")).getFirst(), is("CREATE ENCRYPT RULE 
orders (PROPERTIES('aes-key-value'='******'))"));
         assertThat(((Map<?, ?>) ((List<?>) 
actualResponse.get("step_results")).getFirst()).get("sql"),
@@ -405,12 +356,8 @@ class WorkflowExecutionServiceTest {
     @Test
     void assertApplyRequiresManualExecutionForSecretReference() {
         WorkflowContextSnapshot snapshot = createSecretReferenceSnapshot();
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
         MCPFeatureExecutionFacade executionFacade = 
mock(MCPFeatureExecutionFacade.class);
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP, 
"session-1", snapshot, List.of("rule_distsql"), "review-then-execute");
+        Map<String, Object> actualResponse = apply(snapshot, executionFacade, 
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of("rule_distsql"), 
"review-then-execute");
         assertThat(actualResponse.get("status"), is("failed"));
         assertThat(actualResponse.get("response_mode"), is("recovery"));
         assertThat(actualResponse.get("category"), 
is(MCPDiagnosticCategory.SECRET_REFERENCE_MANUAL_EXECUTION_REQUIRED));
@@ -429,14 +376,11 @@ class WorkflowExecutionServiceTest {
         snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER 
TABLE orders ADD COLUMN order_id_cipher VARCHAR(32)", 10));
         snapshot.getIndexPlans().add(new 
IndexPlan("idx_orders_order_id_cipher", "order_id_cipher", "assist lookup", 
"CREATE INDEX idx_orders_order_id_cipher ON orders(order_id_cipher)"));
         snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE 
MASK RULE orders"));
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
+        WorkflowSessionContext workflowSessionContext = 
createWorkflowSessionContext(snapshot);
         MCPFeatureExecutionFacade executionFacade = 
mock(MCPFeatureExecutionFacade.class);
         
when(executionFacade.execute(any())).thenReturn(mock(SQLExecutionResponse.class));
         MCPWorkflowApplySynchronizationHandler 
workflowApplySynchronizationHandler = 
mock(MCPWorkflowApplySynchronizationHandler.class);
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                executionFacade, workflowApplySynchronizationHandler, 
"session-1", snapshot, List.of("ddl"), "review-then-execute");
+        Map<String, Object> actualResponse = apply(workflowSessionContext, 
snapshot, executionFacade, workflowApplySynchronizationHandler, List.of("ddl"), 
"review-then-execute");
         List<?> actualStepResults = (List<?>) 
actualResponse.get("step_results");
         assertThat(actualResponse.get("status"), is("completed"));
         assertThat(((List<?>) actualResponse.get("skipped_artifacts")).size(), 
is(2));
@@ -449,19 +393,17 @@ class WorkflowExecutionServiceTest {
     
     @Test
     void assertApplyFailsWhenSynchronizationDoesNotConverge() {
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
         WorkflowContextSnapshot snapshot = createSnapshot();
         snapshot.setStatus("previewed");
         snapshot.getRuleArtifacts().add(new RuleArtifact("create", "ALTER MASK 
RULE orders"));
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
+        WorkflowSessionContext workflowSessionContext = 
createWorkflowSessionContext(snapshot);
         MCPFeatureExecutionFacade executionFacade = 
mock(MCPFeatureExecutionFacade.class);
         
when(executionFacade.execute(any())).thenReturn(mock(SQLExecutionResponse.class));
         MCPWorkflowApplySynchronizationHandler 
workflowApplySynchronizationHandler = 
mock(MCPWorkflowApplySynchronizationHandler.class);
         doThrow(new 
WorkflowSynchronizationException(WorkflowIssueCode.RULE_STATE_MISMATCH, "Mask 
rule is missing.",
                 List.of(Map.of("code", 
WorkflowIssueCode.RULE_STATE_MISMATCH)))).when(workflowApplySynchronizationHandler).synchronize(any(),
 any(), any(), any(), any());
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                executionFacade, workflowApplySynchronizationHandler, 
"session-1", snapshot, List.of("rule_distsql"), "review-then-execute");
+        Map<String, Object> actualResponse = apply(workflowSessionContext, 
snapshot, executionFacade, workflowApplySynchronizationHandler,
+                List.of("rule_distsql"), "review-then-execute");
         Map<?, ?> actualIssue = (Map<?, ?>) ((List<?>) 
actualResponse.get("issues")).getFirst();
         assertThat(actualResponse.get("status"), is("failed"));
         assertThat(actualIssue.get("code"), 
is(WorkflowIssueCode.RULE_STATE_MISMATCH));
@@ -470,16 +412,12 @@ class WorkflowExecutionServiceTest {
     
     @Test
     void assertApplyReturnsDdlExecutionFailureForDdlArtifact() {
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
         WorkflowContextSnapshot snapshot = createSnapshot();
         snapshot.setStatus("previewed");
         snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER 
TABLE orders ADD COLUMN order_id_cipher VARCHAR(32)", 10));
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
         MCPFeatureExecutionFacade executionFacade = 
mock(MCPFeatureExecutionFacade.class);
         when(executionFacade.execute(any())).thenThrow(new 
IllegalStateException("ddl failed"));
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP, 
"session-1", snapshot, List.of("ddl"), "review-then-execute");
+        Map<String, Object> actualResponse = apply(snapshot, executionFacade, 
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of("ddl"), 
"review-then-execute");
         Map<?, ?> actualIssue = (Map<?, ?>) ((List<?>) 
actualResponse.get("issues")).getFirst();
         Map<?, ?> actualStep = (Map<?, ?>) ((List<?>) 
actualResponse.get("step_results")).getFirst();
         assertThat(actualResponse.get("status"), is("failed"));
@@ -489,16 +427,12 @@ class WorkflowExecutionServiceTest {
     
     @Test
     void assertApplyReturnsDdlExecutionFailureForIndexArtifact() {
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
         WorkflowContextSnapshot snapshot = createSnapshot();
         snapshot.setStatus("previewed");
         snapshot.getIndexPlans().add(new 
IndexPlan("idx_orders_order_id_cipher", "order_id_cipher", "assist lookup", 
"CREATE INDEX idx_orders_order_id_cipher ON orders(order_id_cipher)"));
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
         MCPFeatureExecutionFacade executionFacade = 
mock(MCPFeatureExecutionFacade.class);
         when(executionFacade.execute(any())).thenThrow(new 
IllegalStateException("index failed"));
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP, 
"session-1", snapshot, List.of("index_ddl"), "review-then-execute");
+        Map<String, Object> actualResponse = apply(snapshot, executionFacade, 
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of("index_ddl"), 
"review-then-execute");
         Map<?, ?> actualIssue = (Map<?, ?>) ((List<?>) 
actualResponse.get("issues")).getFirst();
         Map<?, ?> actualStep = (Map<?, ?>) ((List<?>) 
actualResponse.get("step_results")).getFirst();
         assertThat(actualResponse.get("status"), is("failed"));
@@ -513,13 +447,9 @@ class WorkflowExecutionServiceTest {
         snapshot.setWorkflowKind(WorkflowKind.valueOf("encrypt.rule"));
         snapshot.getPropertyRequirements().add(new 
AlgorithmPropertyRequirement("primary", "aes-key-value", true, true, "AES 
key.", ""));
         snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE 
ENCRYPT RULE orders (PROPERTIES('aes-key-value'='123456'))"));
-        WorkflowSessionContext workflowSessionContext = new 
InMemoryWorkflowSessionContext();
-        workflowSessionContext.save(snapshot);
-        WorkflowExecutionService executionService = new 
WorkflowExecutionService();
         MCPFeatureExecutionFacade executionFacade = 
mock(MCPFeatureExecutionFacade.class);
         when(executionFacade.execute(any())).thenThrow(new 
IllegalStateException("Failed to execute CREATE ENCRYPT RULE orders 
(PROPERTIES('aes-key-value'='123456'))"));
-        Map<String, Object> actualResponse = 
executionService.apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
-                executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP, 
"session-1", snapshot, List.of("rule_distsql"), "review-then-execute");
+        Map<String, Object> actualResponse = apply(snapshot, executionFacade, 
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of("rule_distsql"), 
"review-then-execute");
         Map<?, ?> actualIssue = (Map<?, ?>) ((List<?>) 
actualResponse.get("issues")).getFirst();
         assertThat(actualResponse.get("status"), is("failed"));
         assertThat(actualIssue.get("code"), 
is(WorkflowIssueCode.RULE_EXECUTION_FAILED));
@@ -527,6 +457,30 @@ class WorkflowExecutionServiceTest {
         assertFalse(String.valueOf(actualResponse).contains("123456"));
     }
     
+    private Map<String, Object> apply(final WorkflowContextSnapshot snapshot, 
final List<String> approvedSteps, final String executionMode) {
+        return apply(createWorkflowSessionContext(snapshot), snapshot, 
mock(MCPFeatureExecutionFacade.class), 
MCPWorkflowApplySynchronizationHandler.NO_OP, approvedSteps, executionMode);
+    }
+    
+    private Map<String, Object> apply(final WorkflowContextSnapshot snapshot, 
final MCPFeatureExecutionFacade executionFacade,
+                                      final 
MCPWorkflowApplySynchronizationHandler workflowApplySynchronizationHandler, 
final List<String> approvedSteps,
+                                      final String executionMode) {
+        return apply(createWorkflowSessionContext(snapshot), snapshot, 
executionFacade, workflowApplySynchronizationHandler, approvedSteps, 
executionMode);
+    }
+    
+    private Map<String, Object> apply(final WorkflowSessionContext 
workflowSessionContext, final WorkflowContextSnapshot snapshot,
+                                      final MCPFeatureExecutionFacade 
executionFacade,
+                                      final 
MCPWorkflowApplySynchronizationHandler workflowApplySynchronizationHandler, 
final List<String> approvedSteps,
+                                      final String executionMode) {
+        return new WorkflowExecutionService().apply(workflowSessionContext, 
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
+                executionFacade, workflowApplySynchronizationHandler, 
"session-1", snapshot, approvedSteps, executionMode);
+    }
+    
+    private WorkflowSessionContext createWorkflowSessionContext(final 
WorkflowContextSnapshot snapshot) {
+        WorkflowSessionContext result = new InMemoryWorkflowSessionContext();
+        result.save(snapshot);
+        return result;
+    }
+    
     private WorkflowContextSnapshot createSnapshot() {
         WorkflowContextSnapshot result = new WorkflowContextSnapshot();
         result.setPlanId("plan-1");
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidator.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidator.java
index 61e3da032a5..9dc9a6a8361 100644
--- 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidator.java
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidator.java
@@ -577,9 +577,9 @@ final class MCPDescriptorCatalogValidator {
         if (!descriptor.getAnnotations().isDestructiveHint()) {
             return;
         }
-        Map<?, ?> executionMode = findToolInputProperty(descriptor, 
MCPPayloadFieldNames.EXECUTION_MODE).orElseThrow(
+        Map<?, ?> executionMode = 
MCPToolDescriptorValidationUtils.findToolInputProperty(descriptor, 
MCPPayloadFieldNames.EXECUTION_MODE).orElseThrow(
                 () -> new IllegalStateException(String.format("Destructive 
tool `%s` must declare execution_mode.", descriptor.getName())));
-        ShardingSpherePreconditions.checkState(isRequiredToolInput(descriptor, 
MCPPayloadFieldNames.EXECUTION_MODE),
+        
ShardingSpherePreconditions.checkState(MCPToolDescriptorValidationUtils.isRequiredToolInput(descriptor,
 MCPPayloadFieldNames.EXECUTION_MODE),
                 () -> new IllegalStateException(String.format("Destructive 
tool `%s` execution_mode must be required.", descriptor.getName())));
         Collection<?> executionModes = executionMode.get("enum") instanceof 
Collection ? (Collection<?>) executionMode.get("enum") : List.of();
         
ShardingSpherePreconditions.checkState(executionModes.contains("preview"),
@@ -594,7 +594,7 @@ final class MCPDescriptorCatalogValidator {
         if (descriptor.getAnnotations().isDestructiveHint()) {
             return;
         }
-        Optional<Map<?, ?>> executionMode = findToolInputProperty(descriptor, 
MCPPayloadFieldNames.EXECUTION_MODE);
+        Optional<Map<?, ?>> executionMode = 
MCPToolDescriptorValidationUtils.findToolInputProperty(descriptor, 
MCPPayloadFieldNames.EXECUTION_MODE);
         if (executionMode.isEmpty()) {
             return;
         }
@@ -605,20 +605,6 @@ final class MCPDescriptorCatalogValidator {
                 () -> new IllegalStateException(String.format("Planning tool 
`%s` execution_mode must not expose auto-execute.", descriptor.getName())));
     }
     
-    private static Optional<Map<?, ?>> findToolInputProperty(final 
MCPToolDescriptor descriptor, final String fieldName) {
-        Object properties = descriptor.getInputSchema().get("properties");
-        if (!(properties instanceof Map)) {
-            return Optional.empty();
-        }
-        Object property = ((Map<?, ?>) properties).get(fieldName);
-        return property instanceof Map ? Optional.of((Map<?, ?>) property) : 
Optional.empty();
-    }
-    
-    private static boolean isRequiredToolInput(final MCPToolDescriptor 
descriptor, final String fieldName) {
-        Object required = descriptor.getInputSchema().get("required");
-        return required instanceof Collection && ((Collection<?>) 
required).contains(fieldName);
-    }
-    
     private static boolean isNonEmptyCollection(final Object value) {
         return value instanceof Collection && !((Collection<?>) 
value).isEmpty();
     }
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtils.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtils.java
index f005de4719f..1a6c5f3b49f 100644
--- 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtils.java
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtils.java
@@ -26,6 +26,7 @@ import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 /**
  * MCP tool descriptor validation utilities.
@@ -85,6 +86,49 @@ public final class MCPToolDescriptorValidationUtils {
         }
     }
     
+    /**
+     * Find input schema property.
+     *
+     * @param descriptor tool descriptor
+     * @param fieldName field name
+     * @return found input schema property
+     */
+    public static Optional<Map<?, ?>> findToolInputProperty(final 
MCPToolDescriptor descriptor, final String fieldName) {
+        return findToolSchemaProperty(descriptor.getInputSchema(), fieldName);
+    }
+    
+    /**
+     * Find output schema property.
+     *
+     * @param descriptor tool descriptor
+     * @param fieldName field name
+     * @return found output schema property
+     */
+    public static Optional<Map<?, ?>> findToolOutputProperty(final 
MCPToolDescriptor descriptor, final String fieldName) {
+        return findToolSchemaProperty(descriptor.getOutputSchema(), fieldName);
+    }
+    
+    /**
+     * Check whether input schema property is required.
+     *
+     * @param descriptor tool descriptor
+     * @param fieldName field name
+     * @return whether input schema property is required
+     */
+    public static boolean isRequiredToolInput(final MCPToolDescriptor 
descriptor, final String fieldName) {
+        Object required = descriptor.getInputSchema().get("required");
+        return required instanceof Collection && ((Collection<?>) 
required).contains(fieldName);
+    }
+    
+    private static Optional<Map<?, ?>> findToolSchemaProperty(final 
Map<String, Object> schema, final String fieldName) {
+        Object properties = schema.get("properties");
+        if (!(properties instanceof Map)) {
+            return Optional.empty();
+        }
+        Object property = ((Map<?, ?>) properties).get(fieldName);
+        return property instanceof Map ? Optional.of((Map<?, ?>) property) : 
Optional.empty();
+    }
+    
     /**
      * Validate required workflow planning metadata fields.
      *
diff --git 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcMetadataLoaderTest.java
 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcMetadataLoaderTest.java
index c1174c11fd5..86302879859 100644
--- 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcMetadataLoaderTest.java
+++ 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcMetadataLoaderTest.java
@@ -70,6 +70,15 @@ import static org.mockito.Mockito.when;
 
 class MCPJdbcMetadataLoaderTest {
     
+    private static final Map<String, String> METADATA_JDBC_URLS = Map.of(
+            "MySQL", "jdbc:mysql://metadata-loader/test",
+            "PostgreSQL", "jdbc:postgresql://metadata-loader/test",
+            "openGauss", "jdbc:opengauss://metadata-loader/test",
+            "SQLServer", "jdbc:sqlserver://metadata-loader",
+            "Oracle", "jdbc:oracle:thin:@metadata-loader",
+            "MariaDB", "jdbc:mariadb://metadata-loader/test",
+            "Firebird", "jdbc:firebirdsql://metadata-loader/test");
+    
     @Test
     void assertLoad() throws SQLException {
         LoadedMetadataCatalog actual = load(Map.of("logic_db", 
createMockRuntimeDatabaseConfiguration(createStandardPostgreSQLMetadataConnection())));
@@ -97,8 +106,7 @@ class MCPJdbcMetadataLoaderTest {
     @Test
     void assertLoadWithoutSchemaObjects() throws SQLException {
         Driver mockDriver = new MockDriver("jdbc:mock:no-schema", 
createConnectionWithoutSchema("MySQL"));
-        DriverManager.registerDriver(mockDriver);
-        try {
+        try (MockDriverRegistration registration = 
MockDriverRegistration.register(mockDriver)) {
             LoadedMetadataCatalog actual = load(Map.of("logic_db", new 
RuntimeDatabaseConfiguration("jdbc:mock:no-schema", "", "", 
MockDriver.class.getName())));
             MCPDatabaseMetadata databaseMetadata = 
actual.findMetadata("logic_db").orElseThrow();
             assertThat(databaseMetadata.getSchemas().size(), is(1));
@@ -106,8 +114,6 @@ class MCPJdbcMetadataLoaderTest {
             assertTrue(containsMetadata(databaseMetadata, 
SupportedMCPMetadataObjectType.TABLE, "orders"));
             assertTrue(containsMetadata(databaseMetadata, 
SupportedMCPMetadataObjectType.COLUMN, "order_id"));
             assertThat(databaseMetadata.getDatabaseVersion(), is(""));
-        } finally {
-            DriverManager.deregisterDriver(mockDriver);
         }
     }
     
@@ -306,14 +312,11 @@ class MCPJdbcMetadataLoaderTest {
     @Test
     void assertLoadWithFailedSequenceMetadataQuery() throws SQLException {
         Driver mockDriver = new MockDriver("jdbc:mock:failed-sequence-query", 
createConnectionWithFailedSequenceMetadataQuery());
-        DriverManager.registerDriver(mockDriver);
-        try {
+        try (MockDriverRegistration registration = 
MockDriverRegistration.register(mockDriver)) {
             RuntimeDatabaseConnectionException actual = 
assertThrows(RuntimeDatabaseConnectionException.class,
                     () -> load(Map.of("logic_db", new 
RuntimeDatabaseConfiguration("jdbc:mock:failed-sequence-query", "", "", 
MockDriver.class.getName()))));
             assertThat(actual.getMessage(), is("Runtime database `logic_db` 
connection failed: connection_failed."));
             assertThat(actual.getCause().getMessage(), is("sequence metadata 
query failed"));
-        } finally {
-            DriverManager.deregisterDriver(mockDriver);
         }
     }
     
@@ -323,12 +326,9 @@ class MCPJdbcMetadataLoaderTest {
                                                final String sequenceName, 
final String sequenceQuery) throws SQLException {
         String jdbcUrl = "jdbc:mock:sequence:" + 
databaseType.toLowerCase(Locale.ENGLISH);
         Driver mockDriver = new MockDriver(jdbcUrl, 
createConnectionWithSequenceMetadata(databaseType, sequenceSchema, 
sequenceName, sequenceQuery));
-        DriverManager.registerDriver(mockDriver);
-        try {
+        try (MockDriverRegistration registration = 
MockDriverRegistration.register(mockDriver)) {
             LoadedMetadataCatalog actual = load(Map.of("logic_db", new 
RuntimeDatabaseConfiguration(jdbcUrl, "", "", MockDriver.class.getName())));
             
assertTrue(containsMetadata(actual.findMetadata("logic_db").orElseThrow(), 
SupportedMCPMetadataObjectType.SEQUENCE, sequenceName));
-        } finally {
-            DriverManager.deregisterDriver(mockDriver);
         }
     }
     
@@ -558,28 +558,7 @@ class MCPJdbcMetadataLoaderTest {
     }
     
     private String getMetadataJdbcUrl(final String databaseType) {
-        if ("MySQL".equals(databaseType)) {
-            return "jdbc:mysql://metadata-loader/test";
-        }
-        if ("PostgreSQL".equals(databaseType)) {
-            return "jdbc:postgresql://metadata-loader/test";
-        }
-        if ("openGauss".equals(databaseType)) {
-            return "jdbc:opengauss://metadata-loader/test";
-        }
-        if ("SQLServer".equals(databaseType)) {
-            return "jdbc:sqlserver://metadata-loader";
-        }
-        if ("Oracle".equals(databaseType)) {
-            return "jdbc:oracle:thin:@metadata-loader";
-        }
-        if ("MariaDB".equals(databaseType)) {
-            return "jdbc:mariadb://metadata-loader/test";
-        }
-        if ("Firebird".equals(databaseType)) {
-            return "jdbc:firebirdsql://metadata-loader/test";
-        }
-        return "jdbc:postgresql://metadata-loader/test";
+        return METADATA_JDBC_URLS.getOrDefault(databaseType, 
METADATA_JDBC_URLS.get("PostgreSQL"));
     }
     
     private int countMetadata(final MCPDatabaseMetadata databaseMetadata, 
final SupportedMCPMetadataObjectType objectType, final String objectName) {
@@ -640,6 +619,25 @@ class MCPJdbcMetadataLoaderTest {
         }
     }
     
+    private static final class MockDriverRegistration implements AutoCloseable 
{
+        
+        private final Driver driver;
+        
+        private MockDriverRegistration(final Driver driver) throws 
SQLException {
+            this.driver = driver;
+            DriverManager.registerDriver(driver);
+        }
+        
+        private static MockDriverRegistration register(final Driver driver) 
throws SQLException {
+            return new MockDriverRegistration(driver);
+        }
+        
+        @Override
+        public void close() throws SQLException {
+            DriverManager.deregisterDriver(driver);
+        }
+    }
+    
     @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
     private static final class MockDriver implements Driver {
         
diff --git 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtilsTest.java
 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtilsTest.java
index 7cc972d503b..1af04338ea6 100644
--- 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtilsTest.java
+++ 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtilsTest.java
@@ -27,11 +27,13 @@ import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class MCPToolDescriptorValidationUtilsTest {
     
@@ -68,6 +70,32 @@ class MCPToolDescriptorValidationUtilsTest {
         assertThat(actual.getMessage(), is("Tool `fixture_tool` outputSchema 
property `status` must be an object."));
     }
     
+    @Test
+    void assertFindToolInputProperty() {
+        MCPToolDescriptor descriptor = createDescriptor(
+                Map.of("properties", Map.of("database", Map.of("type", 
"string"))), Map.of());
+        Optional<Map<?, ?>> actual = 
MCPToolDescriptorValidationUtils.findToolInputProperty(descriptor, "database");
+        assertTrue(actual.isPresent());
+        assertThat(actual.get().get("type"), is("string"));
+    }
+    
+    @Test
+    void assertFindToolOutputProperty() {
+        MCPToolDescriptor descriptor = createDescriptor(
+                Map.of(), Map.of("properties", Map.of("response_mode", 
Map.of("enum", List.of("validation")))));
+        Optional<Map<?, ?>> actual = 
MCPToolDescriptorValidationUtils.findToolOutputProperty(descriptor, 
"response_mode");
+        assertTrue(actual.isPresent());
+        assertThat(actual.get().get("enum"), is(List.of("validation")));
+    }
+    
+    @Test
+    void assertIsRequiredToolInput() {
+        MCPToolDescriptor descriptor = createDescriptor(
+                Map.of("required", List.of("database"), "properties", 
Map.of("database", Map.of("type", "string"))), Map.of());
+        boolean actual = 
MCPToolDescriptorValidationUtils.isRequiredToolInput(descriptor, "database");
+        assertTrue(actual);
+    }
+    
     @Test
     void assertValidateRequiredWorkflowPlanMetaFields() {
         MCPToolDescriptor descriptor = 
createDescriptor(WORKFLOW_PLAN_OUTPUT_FIELDS, createWorkflowPlanMeta());
@@ -93,6 +121,10 @@ class MCPToolDescriptorValidationUtilsTest {
                 Map.of("type", "object", "properties", 
createOutputProperties(outputFields)), createAnnotations(), meta);
     }
     
+    private MCPToolDescriptor createDescriptor(final Map<String, Object> 
inputSchema, final Map<String, Object> outputSchema) {
+        return new MCPToolDescriptor("fixture_tool", "Fixture Tool", "Fixture 
tool.", inputSchema, outputSchema, createAnnotations(), Map.of());
+    }
+    
     private MCPToolAnnotations createAnnotations() {
         return new MCPToolAnnotations("Fixture Tool", true, false, true, 
false);
     }

Reply via email to