You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by zh...@apache.org on 2026/07/14 02:50:47 UTC

(shardingsphere) branch master updated: Refactor MCP workflow and metadata boundaries (#39089)

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 0b523bd4797 Refactor MCP workflow and metadata boundaries (#39089)
0b523bd4797 is described below

commit 0b523bd47978445cd759aab596f8349d59646a06
Author: Liang Zhang <zh...@apache.org>
AuthorDate: Tue Jul 14 10:50:40 2026 +0800

    Refactor MCP workflow and metadata boundaries (#39089)
---
 .../api/tool/descriptor/MCPToolAnnotations.java    |   2 +-
 .../MCPCompletionSpecificationFactory.java         |   2 +-
 .../MCPCompletionSpecificationFactoryTest.java     |   6 +-
 .../AbstractMCPToolSpecificationFactoryTest.java   |  19 +-
 .../tool/MCPToolSpecificationFactoryTest.java      |   3 +-
 .../mcp/core/completion/MCPCompletionResult.java   |  11 +
 .../mcp/core/completion/MCPCompletionService.java  |  15 +-
 .../provider/MetadataCompletionProvider.java       | 167 ++++++------
 .../error/MCPWorkflowRecoveryPayloadFactory.java   |  18 +-
 .../handler/ResourceDefinitionRegistry.java        |   2 +-
 .../handler/capability/RuntimeStatusHandler.java   |   2 +-
 .../handler/metadata/MetadataResourceHandler.java  | 300 +--------------------
 ...r.java => MetadataResourceResponseFactory.java} | 165 ++++++------
 .../mcp/core/resource/uri/MCPUriPattern.java       |   2 +-
 .../mcp/core/tool/MCPToolController.java           |   6 +-
 .../handler/execute/MCPJdbcStatementExecutor.java  |  72 +++--
 .../MCPJdbcTransactionStatementExecutor.java       |  55 ++--
 .../handler/metadata/MetadataSearchCollector.java  |  87 +++---
 .../metadata/MetadataSearchResourceUriFactory.java |  40 ++-
 .../workflow/WorkflowExecutionToolHandler.java     |  16 +-
 .../workflow/WorkflowValidationToolHandler.java    |   9 +-
 .../mcp/core/tool/response/MetadataSearchHit.java  |  10 +-
 .../mcp/core/workflow/WorkflowApplyOutcome.java    |   2 +-
 .../workflow/WorkflowApplyResponseBuilder.java     |  31 ++-
 .../core/workflow/WorkflowExecutionService.java    |  38 ++-
 .../workflow/WorkflowSessionSnapshotResolver.java  |  15 ++
 .../core/completion/MCPCompletionServiceTest.java  |  10 +-
 .../provider/MetadataCompletionProviderTest.java   |   5 +-
 .../metadata/MetadataResourceHandlerTest.java      |  80 +-----
 .../MetadataResourcePayloadMapperTest.java         |   4 +-
 .../MetadataResourceResponseFactoryTest.java       | 104 +++++++
 .../mcp/core/resource/uri/MCPUriPatternTest.java   |   2 +-
 .../mcp/core/tool/MCPToolControllerTest.java       |   2 +-
 .../tool/handler/ToolDefinitionRegistryTest.java   |  10 +-
 .../execute/MCPJdbcStatementExecutorTest.java      |   3 +-
 .../MCPJdbcTransactionStatementExecutorTest.java   |   8 +-
 .../metadata/SearchMetadataPayloadBuilderTest.java |   9 +-
 .../metadata/SearchMetadataToolServiceTest.java    |  10 +
 .../WorkflowSessionSnapshotResolverTest.java       |  48 ++++
 .../BroadcastRuleDistSQLPlanningService.java       |   5 +-
 .../service/BroadcastWorkflowPlanningService.java  |  12 +-
 .../EncryptAlgorithmRecommendationService.java     |  24 +-
 .../service/EncryptRuleDistSQLPlanningService.java |   5 +-
 .../service/EncryptWorkflowIntentResolver.java     |   3 +-
 .../service/EncryptWorkflowPlanningService.java    |   8 +-
 .../service/EncryptWorkflowValidationService.java  |  11 +-
 .../EncryptWorkflowPlanningServiceTest.java        |   8 +-
 .../EncryptWorkflowValidationServiceTest.java      |   4 +-
 .../MaskAlgorithmRecommendationService.java        |  12 +-
 .../service/MaskRuleDistSQLPlanningService.java    |   5 +-
 .../tool/service/MaskWorkflowPlanningService.java  |   4 +-
 .../service/MaskWorkflowValidationService.java     |   7 +-
 .../service/MaskWorkflowPlanningServiceTest.java   |   7 +-
 .../service/MaskWorkflowValidationServiceTest.java |   4 +-
 .../PlanReadwriteSplittingRuleToolHandler.java     |  34 +--
 .../PlanReadwriteSplittingStatusToolHandler.java   |  26 +-
 ...iteSplittingAlgorithmRecommendationService.java |   7 +-
 ...adwriteSplittingRuleDistSQLPlanningService.java |   7 +-
 ...dwriteSplittingRuleWorkflowPlanningService.java |  27 +-
 ...riteSplittingStatusWorkflowPlanningService.java |  14 +-
 ...iteSplittingWorkflowValidationServicesTest.java |   4 +-
 .../PlanDefaultShadowAlgorithmToolHandler.java     |  19 +-
 .../PlanShadowAlgorithmCleanupToolHandler.java     |  18 +-
 .../tool/handler/PlanShadowRuleToolHandler.java    |  35 +--
 .../ShadowAlgorithmRecommendationService.java      |   7 +-
 .../tool/service/ShadowDistSQLPlanningService.java |  15 +-
 .../service/ShadowWorkflowPlanningService.java     |  18 +-
 .../ShadowWorkflowValidationServiceTest.java       |   6 +-
 .../handler/ShardingPlanningRequestBinder.java     | 116 ++++----
 .../ShardingAlgorithmRecommendationService.java    |   7 +-
 .../service/ShardingDistSQLPlanningService.java    |  30 ++-
 .../service/ShardingWorkflowInputValidator.java    |   2 +-
 .../service/ShardingWorkflowPlanningService.java   |  16 +-
 .../ShardingWorkflowValidationServiceTest.java     |   8 +-
 .../database/capability/MCPDatabaseCapability.java |  11 +-
 .../database/capability/MCPDatabaseDialect.java    |  14 -
 .../TransactionCapability.java                     |   4 +-
 .../jdbc/MCPJdbcDatabaseProfileLoader.java         |  19 +-
 .../metadata/jdbc/RuntimeDatabaseProfile.java      |   5 +-
 .../response/MCPDatabaseCapabilityResponse.java    |   8 +-
 .../tool/response/SQLExecutionResponse.java        |  56 ++--
 .../MCPDescriptorCatalogYamlSwapper.java           |   9 +-
 .../MCPToolDescriptorCatalogValidator.java         |   2 +-
 .../MCPToolDescriptorValidationUtils.java          |  59 +++-
 .../descriptor/MCPToolOutputSchemaValidator.java   |  46 +---
 .../mcp/support/protocol/MCPCompletionAction.java  |  66 +++++
 .../mcp/support/protocol/MCPNextActionUtils.java   |  35 +--
 .../support/workflow/model/AlgorithmCandidate.java |   7 +-
 .../support/workflow/model/InteractionPlan.java    |   4 +-
 .../workflow/model/SecretReferenceValue.java       |   3 +-
 .../workflow/model/WorkflowContextSnapshot.java    |  21 +-
 .../support/workflow/model/WorkflowLifecycle.java  |  26 ++
 .../support/workflow/model/WorkflowRequest.java    |   4 +-
 .../WorkflowAlgorithmRequirementCollector.java     |   5 +-
 .../workflow/service/WorkflowAlgorithmUtils.java   | 113 +++++++-
 .../service/WorkflowGuidancePayloadBuilder.java    |   8 +-
 .../service/WorkflowIntentResolverSupport.java     |   8 +-
 .../service/WorkflowPlanPayloadBuilder.java        |  12 +-
 .../service/WorkflowPlanningArguments.java         |  14 +
 .../service/WorkflowPlanningContextValidator.java  |  18 +-
 .../workflow/service/WorkflowPlanningSupport.java  |  14 +-
 .../workflow/service/WorkflowRequestBinder.java    |  14 +
 .../support/workflow/service/WorkflowSQLUtils.java | 109 +-------
 .../service/WorkflowValidationSupport.java         |   4 +-
 .../MCPDatabaseCapabilityProviderTest.java         |  27 +-
 .../capability/MCPDatabaseDialectTest.java         |  19 --
 .../jdbc/MCPJdbcDatabaseProfileLoaderTest.java     |  22 +-
 .../jdbc/MCPJdbcMetadataLoaderFailureTest.java     |   4 +-
 .../MCPDatabaseCapabilityResponseTest.java         |   6 +-
 .../RuntimeDatabaseValidationServiceTest.java      |  10 +-
 .../MCPDescriptorCatalogPayloadBuilderTest.java    |   6 +-
 .../MCPDescriptorCatalogValidatorTest.java         |  43 +--
 .../MCPDescriptorComponentValidatorsTest.java      |   2 +-
 .../MCPProtocolDescriptorCatalogTest.java          |   2 +-
 .../MCPToolDescriptorValidationUtilsTest.java      |   2 +-
 .../support/protocol/MCPNextActionUtilsTest.java   |  19 +-
 .../model/WorkflowContextSnapshotTest.java         |  13 +-
 .../service/WorkflowAlgorithmUtilsTest.java        |  30 ++-
 .../service/WorkflowPlanPayloadBuilderTest.java    |   3 +-
 .../service/WorkflowPlanningArgumentsTest.java     |  16 ++
 .../WorkflowPlanningContextValidatorTest.java      |   4 +-
 .../service/WorkflowPlanningSupportTest.java       |   4 +-
 .../service/WorkflowRequestBinderTest.java         |  14 +
 .../workflow/service/WorkflowSQLUtilsTest.java     |  33 ---
 .../e2e/mcp/llm/config/LLME2EConfiguration.java    |  43 +--
 .../mcp/llm/config/LLME2EConfigurationTest.java    |  21 +-
 .../conversation/LLMMCPConversationArtifacts.java  |  14 +-
 .../LLMMCPConversationArtifactsTest.java           |   6 +-
 .../LLMMCPConversationInstructionFactory.java      |  34 +--
 .../llm/conversation/LLMMCPConversationRunner.java |  10 +-
 .../LLMMCPConversationRunnerFailureTest.java       |  39 +++
 .../LLMMCPConversationTurnPlanner.java             |  10 +-
 .../LLMMCPConversationTurnPlannerTest.java         |  24 +-
 .../conversation/LLMMCPFinalAnswerValidator.java   |   2 +-
 .../mcp/llm/conversation/LLMMCPNextActions.java    |  53 ++++
 .../llm/conversation/LLMMCPNextActionsTest.java    |   1 +
 .../artifact/LLME2EArtifactBundle.java             |   7 +-
 .../artifact/LLME2EArtifactWriterTest.java         |  29 +-
 .../artifact/LLME2EAssertionReport.java            |  13 +-
 .../llm/conversation/client/LLMChatMessage.java    |   3 +-
 .../client/LLMChatModelClientTest.java             |  19 +-
 .../e2e/mcp/llm/fixture/LLMRuntimeSupportTest.java |  26 +-
 .../suite/usability/LLMUsabilitySuiteE2ETest.java  |   4 +-
 .../suite/usability/LLMUsabilitySuiteRunner.java   |  14 +-
 .../usability/LLMUsabilitySuiteRunnerTest.java     |  56 +++-
 .../assessment/LLMUsabilityMetricCalculator.java   |  49 +++-
 .../LLMUsabilityMetricCalculatorTest.java          | 103 ++++++-
 .../assessment/LLMUsabilityReportWriterTest.java   |  47 +++-
 .../assessment/LLMUsabilityScenarioResult.java     |   7 +-
 .../assessment/LLMUsabilityScorecard.java          |   7 +-
 .../assessment/LLMUsabilityTraceMetrics.java       |   4 +-
 .../usability/scenario/LLMUsabilityScenario.java   |   7 +-
 .../scenario/LLMUsabilityScenarioCatalog.java      |  14 +-
 .../support/runtime/MySQLRuntimeTestSupport.java   |  39 ++-
 .../runtime/MySQLRuntimeTestSupportTest.java       |  23 ++
 155 files changed, 2012 insertions(+), 1638 deletions(-)

diff --git a/mcp/api/src/main/java/org/apache/shardingsphere/mcp/api/tool/descriptor/MCPToolAnnotations.java b/mcp/api/src/main/java/org/apache/shardingsphere/mcp/api/tool/descriptor/MCPToolAnnotations.java
index b2f5b90f0dd..9e974b75b5f 100644
--- a/mcp/api/src/main/java/org/apache/shardingsphere/mcp/api/tool/descriptor/MCPToolAnnotations.java
+++ b/mcp/api/src/main/java/org/apache/shardingsphere/mcp/api/tool/descriptor/MCPToolAnnotations.java
@@ -37,7 +37,7 @@ public final class MCPToolAnnotations {
     private final boolean openWorldHint;
     
     @Builder
-    public MCPToolAnnotations(final String title, final boolean readOnlyHint, final boolean destructiveHint, final boolean idempotentHint, final boolean openWorldHint) {
+    private MCPToolAnnotations(final String title, final boolean readOnlyHint, final boolean destructiveHint, final boolean idempotentHint, final boolean openWorldHint) {
         this.title = title;
         this.readOnlyHint = readOnlyHint;
         this.destructiveHint = destructiveHint;
diff --git a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/completion/MCPCompletionSpecificationFactory.java b/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/completion/MCPCompletionSpecificationFactory.java
index 01f7abcc00e..a90b0d8bc54 100644
--- a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/completion/MCPCompletionSpecificationFactory.java
+++ b/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/completion/MCPCompletionSpecificationFactory.java
@@ -67,7 +67,7 @@ public final class MCPCompletionSpecificationFactory {
         Map<String, String> contextArguments = null == request.context() || null == request.context().arguments() ? Map.of() : request.context().arguments();
         try {
             MCPCompletionResult result = completionService.complete(exchange.sessionId(), descriptor, argumentName, prefix, contextArguments);
-            return new McpSchema.CompleteResult(new McpSchema.CompleteResult.CompleteCompletion(result.getValues(), result.getTotal(), result.isHasMore()), result.getMeta());
+            return new McpSchema.CompleteResult(new McpSchema.CompleteResult.CompleteCompletion(result.getValues(), result.getTotal(), result.hasMore()), result.getMeta());
         } catch (final MCPInvalidRequestException ex) {
             throw MCPTransportErrorFactory.createError(ex);
         }
diff --git a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/completion/MCPCompletionSpecificationFactoryTest.java b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/completion/MCPCompletionSpecificationFactoryTest.java
index 93239f3ff38..fcda15215f3 100644
--- a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/completion/MCPCompletionSpecificationFactoryTest.java
+++ b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/completion/MCPCompletionSpecificationFactoryTest.java
@@ -17,6 +17,8 @@
 
 package org.apache.shardingsphere.mcp.bootstrap.transport.capability.completion;
 
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
+
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicyFactory;
 import io.modelcontextprotocol.server.McpServerFeatures.SyncCompletionSpecification;
 import io.modelcontextprotocol.server.McpSyncServerExchange;
@@ -199,8 +201,8 @@ class MCPCompletionSpecificationFactoryTest {
     private MCPRuntimeContext createRuntimeContext(final WorkflowSessionContext workflowSessionContext) {
         MCPDatabaseCapabilityProvider databaseCapabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
         when(databaseCapabilityProvider.getDatabaseProfiles()).thenReturn(List.of(
-                new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet()),
-                new RuntimeDatabaseProfile("warehouse", "FixtureWarehouseDB", "2.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet())));
+                new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet()),
+                new RuntimeDatabaseProfile("warehouse", "FixtureWarehouseDB", "2.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet())));
         MCPRuntimeContext result = mock(MCPRuntimeContext.class, RETURNS_DEEP_STUBS);
         when(result.getDatabaseCapabilityProvider()).thenReturn(databaseCapabilityProvider);
         when(result.getWorkflowSessionContext()).thenReturn(workflowSessionContext);
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 1b5f3606823..92109b18e27 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
@@ -239,7 +239,8 @@ abstract class AbstractMCPToolSpecificationFactoryTest {
         properties.put("object_types", Map.of("type", "array", "description", "Optional object-type filter.",
                 "items", Map.of("type", "string", "description", "Object type.", "enum", List.of("TABLE", "VIEW"))));
         return new MCPToolDescriptor(toolName, "Search Metadata", "Search database metadata.", createInputSchema(properties, List.of("query")),
-                Map.of("type", "object"), new MCPToolAnnotations("Search Metadata", true, false, true, true),
+                Map.of("type", "object"), MCPToolAnnotations.builder()
+                        .title("Search Metadata").readOnlyHint(true).destructiveHint(false).idempotentHint(true).openWorldHint(true).build(),
                 Map.of(MCPShardingSphereMetadataKeys.RELATED_RESOURCE_URIS, List.of("shardingsphere://databases")));
     }
     
@@ -249,13 +250,17 @@ abstract class AbstractMCPToolSpecificationFactoryTest {
     
     protected MCPToolDescriptor createToolDescriptorWithoutOutputSchema(final String toolName) {
         return new MCPToolDescriptor(toolName, "Fixture Tool", "Run a fixture tool.", createInputSchema(Map.of(), List.of()), Collections.emptyMap(),
-                new MCPToolAnnotations("Fixture Tool", true, false, true, true), Collections.emptyMap());
+                MCPToolAnnotations.builder()
+                        .title("Fixture Tool").readOnlyHint(true).destructiveHint(false).idempotentHint(true).openWorldHint(true).build(),
+                Collections.emptyMap());
     }
     
     protected MCPToolDescriptor createStrictToolDescriptor(final String toolName) {
         return new MCPToolDescriptor(toolName, "Search Metadata", "Search database metadata.", createInputSchema(Map.of(), List.of()),
                 Map.of("type", "object", "properties", Map.of("status", Map.of("type", "string")), "required", List.of("status")),
-                new MCPToolAnnotations("Search Metadata", true, false, true, true), Collections.emptyMap());
+                MCPToolAnnotations.builder()
+                        .title("Search Metadata").readOnlyHint(true).destructiveHint(false).idempotentHint(true).openWorldHint(true).build(),
+                Collections.emptyMap());
     }
     
     protected MCPToolDescriptor createPlanningToolDescriptor(final String toolName) {
@@ -264,7 +269,9 @@ abstract class AbstractMCPToolSpecificationFactoryTest {
         properties.put("intent", Map.of("type", "object", "description", "Intent.", "properties",
                 Map.of("requires_review", Map.of("type", "boolean", "description", "Requires review.")), "required", List.of(), "additionalProperties", false));
         return new MCPToolDescriptor(toolName, "Plan Custom Rule", "Plan a custom rule.", createInputSchema(properties, List.of()),
-                Map.of("type", "object"), new MCPToolAnnotations("Plan Custom Rule", false, false, true, true), Collections.emptyMap());
+                Map.of("type", "object"), MCPToolAnnotations.builder()
+                        .title("Plan Custom Rule").readOnlyHint(false).destructiveHint(false).idempotentHint(true).openWorldHint(true).build(),
+                Collections.emptyMap());
     }
     
     protected MCPToolDescriptor createAmbiguousPlanningToolDescriptor(final String toolName) {
@@ -278,7 +285,9 @@ abstract class AbstractMCPToolSpecificationFactoryTest {
         properties.put("intent", objectProperty);
         properties.put("review_policy", objectProperty);
         return new MCPToolDescriptor(toolName, "Plan Custom Rule", "Plan a custom rule.", createInputSchema(properties, List.of()),
-                Map.of("type", "object"), new MCPToolAnnotations("Plan Custom Rule", false, false, true, true), Collections.emptyMap());
+                Map.of("type", "object"), MCPToolAnnotations.builder()
+                        .title("Plan Custom Rule").readOnlyHint(false).destructiveHint(false).idempotentHint(true).openWorldHint(true).build(),
+                Collections.emptyMap());
     }
     
     protected Map<String, Object> createInputSchema(final Map<String, Object> properties, final List<String> required) {
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 dc9e6cde946..ec9f953b07e 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
@@ -80,7 +80,8 @@ class MCPToolSpecificationFactoryTest extends AbstractMCPToolSpecificationFactor
     void assertCreateToolSpecificationsMapAnnotationPresence() {
         try (MockedStatic<ToolDefinitionRegistry> mockedToolDefinitionRegistry = mockStatic(ToolDefinitionRegistry.class)) {
             mockedToolDefinitionRegistry.when(ToolDefinitionRegistry::getSupportedToolDescriptors).thenReturn(List.of(createToolDescriptor(
-                    "fixture_declared_defaults", new MCPToolAnnotations(null, false, true, false, true))));
+                    "fixture_declared_defaults", MCPToolAnnotations.builder()
+                            .title(null).readOnlyHint(false).destructiveHint(true).idempotentHint(false).openWorldHint(true).build())));
             MCPToolSpecificationFactory actualFactory = new MCPToolSpecificationFactory(mock(MCPRuntimeContext.class, RETURNS_DEEP_STUBS));
             List<SyncToolSpecification> actual = actualFactory.createToolSpecifications();
             assertNotNull(actual.getFirst().tool().annotations());
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/completion/MCPCompletionResult.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/completion/MCPCompletionResult.java
index ee27377cdd6..45b7445e7f8 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/completion/MCPCompletionResult.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/completion/MCPCompletionResult.java
@@ -17,6 +17,7 @@
 
 package org.apache.shardingsphere.mcp.core.completion;
 
+import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 
@@ -34,7 +35,17 @@ public final class MCPCompletionResult {
     
     private final int total;
     
+    @Getter(AccessLevel.NONE)
     private final boolean hasMore;
     
     private final Map<String, Object> meta;
+    
+    /**
+     * Judge whether more completion values are available.
+     *
+     * @return whether more completion values are available
+     */
+    public boolean hasMore() {
+        return hasMore;
+    }
 }
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/completion/MCPCompletionService.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/completion/MCPCompletionService.java
index 577d299040b..e9e3cd4e053 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/completion/MCPCompletionService.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/completion/MCPCompletionService.java
@@ -27,6 +27,7 @@ import org.apache.shardingsphere.mcp.support.completion.MCPCompletionProviderRes
 import org.apache.shardingsphere.mcp.support.completion.MCPCompletionRequestContext;
 import org.apache.shardingsphere.mcp.support.descriptor.MCPCompletionTargetDescriptor;
 import org.apache.shardingsphere.mcp.support.descriptor.MCPShardingSphereMetadataKeys;
+import org.apache.shardingsphere.mcp.support.protocol.MCPCompletionAction;
 import org.apache.shardingsphere.mcp.support.protocol.MCPNextActionUtils;
 import org.apache.shardingsphere.mcp.support.protocol.MCPResponseMode;
 
@@ -234,8 +235,18 @@ public final class MCPCompletionService {
     
     private Map<String, Object> createCompletionAction(final MCPCompletionTargetDescriptor descriptor, final String argumentName, final String argumentPrefix,
                                                        final Map<String, String> contextArguments, final Collection<String> missingContextArguments, final String reason) {
-        return MCPNextActionUtils.completeArgument(descriptor.getReferenceType(), descriptor.getReference(), argumentName, argumentPrefix, contextArguments, missingContextArguments,
-                descriptor.getReferenceType(), descriptor.getReference(), contextArguments, reason);
+        return MCPNextActionUtils.completeArgument(MCPCompletionAction.builder()
+                .referenceType(descriptor.getReferenceType())
+                .reference(descriptor.getReference())
+                .argumentName(argumentName)
+                .argumentPrefix(argumentPrefix)
+                .contextArguments(contextArguments)
+                .missingContextArguments(missingContextArguments)
+                .resumeTargetType(descriptor.getReferenceType())
+                .resumeTarget(descriptor.getReference())
+                .resumeArguments(contextArguments)
+                .reason(reason)
+                .build());
     }
     
     private String createDiagnostic(final Collection<MCPCompletionCandidate> candidates, final Collection<MCPCompletionCandidate> filteredCandidates,
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/completion/provider/MetadataCompletionProvider.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/completion/provider/MetadataCompletionProvider.java
index 56ed1bfc543..9fd7bc49b6a 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/completion/provider/MetadataCompletionProvider.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/completion/provider/MetadataCompletionProvider.java
@@ -40,7 +40,6 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
-import java.util.stream.Stream;
 
 /**
  * Metadata completion provider.
@@ -49,8 +48,6 @@ public final class MetadataCompletionProvider implements MCPCompletionProvider<M
     
     private static final Set<String> STORAGE_UNIT_ARGUMENTS = Set.of("storageUnit", "storage_unit", "write_storage_unit", "source_storage_unit", "shadow_storage_unit");
     
-    private static final Set<String> SUPPORTED_ARGUMENTS = Set.of("database", "schema", "table", "column", "index", "sequence", "storageUnit");
-    
     private final GovernanceMetadataQueryService governanceMetadataQueryService = new GovernanceMetadataQueryService();
     
     @Override
@@ -60,36 +57,34 @@ public final class MetadataCompletionProvider implements MCPCompletionProvider<M
     
     @Override
     public boolean supports(final MCPCompletionRequestContext requestContext) {
-        return SUPPORTED_ARGUMENTS.contains(canonicalizeArgumentName(requestContext.getArgumentName()));
+        return MetadataCompletionTarget.UNKNOWN != MetadataCompletionTarget.from(requestContext.getArgumentName());
     }
     
     @Override
     public MCPCompletionProviderResult complete(final MCPDatabaseHandlerContext handlerContext, final MCPCompletionRequestContext requestContext) {
-        String argumentName = canonicalizeArgumentName(requestContext.getArgumentName());
+        MetadataCompletionTarget target = MetadataCompletionTarget.from(requestContext.getArgumentName());
         Map<String, String> contextArguments = new LinkedHashMap<>(requestContext.getContextArguments());
-        Map<String, Object> inferredContextArguments = applyContextDefaults(handlerContext, argumentName, contextArguments);
-        Collection<String> missingContextArguments = createMissingContextArguments(argumentName, contextArguments);
-        String nearestResourceUri = createNearestResourceUri(
-                missingContextArguments.isEmpty() ? argumentName : missingContextArguments.iterator().next(), contextArguments);
+        Map<String, Object> inferredContextArguments = applyContextDefaults(handlerContext, target, contextArguments);
+        Collection<String> missingContextArguments = createMissingContextArguments(target, contextArguments);
+        MetadataCompletionTarget nearestTarget = missingContextArguments.isEmpty() ? target : MetadataCompletionTarget.from(missingContextArguments.iterator().next());
+        String nearestResourceUri = createNearestResourceUri(nearestTarget, contextArguments);
         return new MCPCompletionProviderResult(
-                completeMetadata(handlerContext, argumentName, contextArguments), inferredContextArguments, missingContextArguments, nearestResourceUri);
-    }
-    
-    private String canonicalizeArgumentName(final String argumentName) {
-        return STORAGE_UNIT_ARGUMENTS.contains(argumentName) ? "storageUnit" : argumentName;
+                completeMetadata(handlerContext, target, contextArguments), inferredContextArguments, missingContextArguments, nearestResourceUri);
     }
     
-    private Map<String, Object> applyContextDefaults(final MCPDatabaseHandlerContext handlerContext, final String argumentName, final Map<String, String> contextArguments) {
+    private Map<String, Object> applyContextDefaults(final MCPDatabaseHandlerContext handlerContext, final MetadataCompletionTarget target,
+                                                     final Map<String, String> contextArguments) {
         Map<String, Object> result = new LinkedHashMap<>(2, 1F);
-        result.putAll(applySingleDatabaseDefault(handlerContext, argumentName, contextArguments));
+        result.putAll(applySingleDatabaseDefault(handlerContext, target, contextArguments));
         mergeInferredContextArguments(contextArguments, result);
-        result.putAll(applySingleSchemaDefault(handlerContext, argumentName, contextArguments));
+        result.putAll(applySingleSchemaDefault(handlerContext, target, contextArguments));
         mergeInferredContextArguments(contextArguments, result);
         return result;
     }
     
-    private Map<String, Object> applySingleDatabaseDefault(final MCPDatabaseHandlerContext handlerContext, final String argumentName, final Map<String, String> contextArguments) {
-        if (!requiresDatabaseContext(argumentName) || !Objects.toString(contextArguments.get("database"), "").isEmpty()) {
+    private Map<String, Object> applySingleDatabaseDefault(final MCPDatabaseHandlerContext handlerContext, final MetadataCompletionTarget target,
+                                                           final Map<String, String> contextArguments) {
+        if (!target.requires("database") || !Objects.toString(contextArguments.get("database"), "").isEmpty()) {
             return Map.of();
         }
         List<RuntimeDatabaseProfile> databaseProfiles = handlerContext.getCapabilityFacade().getDatabaseProfiles();
@@ -100,8 +95,9 @@ public final class MetadataCompletionProvider implements MCPCompletionProvider<M
         return database.isEmpty() ? Map.of() : Map.of("database", database);
     }
     
-    private Map<String, Object> applySingleSchemaDefault(final MCPDatabaseHandlerContext handlerContext, final String argumentName, final Map<String, String> contextArguments) {
-        if (!requiresSchemaContext(argumentName) || !Objects.toString(contextArguments.get("schema"), "").isEmpty()
+    private Map<String, Object> applySingleSchemaDefault(final MCPDatabaseHandlerContext handlerContext, final MetadataCompletionTarget target,
+                                                         final Map<String, String> contextArguments) {
+        if (!target.requires("schema") || !Objects.toString(contextArguments.get("schema"), "").isEmpty()
                 || Objects.toString(contextArguments.get("database"), "").isEmpty()) {
             return Map.of();
         }
@@ -117,34 +113,18 @@ public final class MetadataCompletionProvider implements MCPCompletionProvider<M
         }
     }
     
-    private boolean requiresDatabaseContext(final String argumentName) {
-        return "schema".equals(argumentName) || "storageUnit".equals(argumentName) || requiresSchemaContext(argumentName);
-    }
-    
-    private boolean requiresSchemaContext(final String argumentName) {
-        return "table".equals(argumentName) || "column".equals(argumentName) || "index".equals(argumentName) || "sequence".equals(argumentName);
-    }
-    
-    private Collection<MCPCompletionCandidate> completeMetadata(final MCPDatabaseHandlerContext handlerContext, final String argumentName, final Map<String, String> contextArguments) {
-        if ("database".equals(argumentName)) {
-            return completeDatabases(handlerContext);
-        }
-        if ("schema".equals(argumentName)) {
-            return completeSchemas(handlerContext, contextArguments);
-        }
-        if ("table".equals(argumentName)) {
-            return completeTables(handlerContext, contextArguments);
-        }
-        if ("column".equals(argumentName)) {
-            return completeColumns(handlerContext, contextArguments);
-        }
-        if ("index".equals(argumentName)) {
-            return completeIndexes(handlerContext, contextArguments);
-        }
-        if ("storageUnit".equals(argumentName)) {
-            return completeStorageUnits(handlerContext, contextArguments);
-        }
-        return "sequence".equals(argumentName) ? completeSequences(handlerContext, contextArguments) : List.of();
+    private Collection<MCPCompletionCandidate> completeMetadata(final MCPDatabaseHandlerContext handlerContext, final MetadataCompletionTarget target,
+                                                                final Map<String, String> contextArguments) {
+        return switch (target) {
+            case DATABASE -> completeDatabases(handlerContext);
+            case SCHEMA -> completeSchemas(handlerContext, contextArguments);
+            case TABLE -> completeTables(handlerContext, contextArguments);
+            case COLUMN -> completeColumns(handlerContext, contextArguments);
+            case INDEX -> completeIndexes(handlerContext, contextArguments);
+            case SEQUENCE -> completeSequences(handlerContext, contextArguments);
+            case STORAGE_UNIT -> completeStorageUnits(handlerContext, contextArguments);
+            case UNKNOWN -> List.of();
+        };
     }
     
     private List<MCPCompletionCandidate> completeDatabases(final MCPDatabaseHandlerContext handlerContext) {
@@ -221,52 +201,65 @@ public final class MetadataCompletionProvider implements MCPCompletionProvider<M
         return Objects.toString(contextArguments.get("schema"), "");
     }
     
-    private List<String> createMissingContextArguments(final String argumentName, final Map<String, String> contextArguments) {
-        if ("schema".equals(argumentName) || "storageUnit".equals(argumentName)) {
-            return createMissingArguments(contextArguments, "database");
-        }
-        if ("table".equals(argumentName) || "sequence".equals(argumentName)) {
-            return createMissingArguments(contextArguments, "database", "schema");
-        }
-        if ("column".equals(argumentName) || "index".equals(argumentName)) {
-            return createMissingArguments(contextArguments, "database", "schema", "table");
-        }
-        return List.of();
+    private List<String> createMissingContextArguments(final MetadataCompletionTarget target, final Map<String, String> contextArguments) {
+        return target.requiredContextArguments.stream().filter(each -> Objects.toString(contextArguments.get(each), "").isEmpty()).toList();
     }
     
-    private List<String> createMissingArguments(final Map<String, String> contextArguments, final String... requiredArguments) {
-        return Stream.of(requiredArguments).filter(each -> Objects.toString(contextArguments.get(each), "").isEmpty()).toList();
-    }
-    
-    private String createNearestResourceUri(final String argumentName, final Map<String, String> contextArguments) {
+    private String createNearestResourceUri(final MetadataCompletionTarget target, final Map<String, String> contextArguments) {
         String database = Objects.toString(contextArguments.get("database"), "");
         String schema = Objects.toString(contextArguments.get("schema"), "");
-        if ("database".equals(argumentName)) {
-            return "shardingsphere://databases";
-        }
-        if ("schema".equals(argumentName) && !database.isEmpty()) {
-            return String.format("shardingsphere://databases/%s/schemas", encode(database));
-        }
-        if ("table".equals(argumentName) && !database.isEmpty() && !schema.isEmpty()) {
-            return String.format("shardingsphere://databases/%s/schemas/%s/tables", encode(database), encode(schema));
-        }
-        if ("sequence".equals(argumentName) && !database.isEmpty() && !schema.isEmpty()) {
-            return String.format("shardingsphere://databases/%s/schemas/%s/sequences", encode(database), encode(schema));
-        }
-        if ("storageUnit".equals(argumentName) && !database.isEmpty()) {
-            return String.format("shardingsphere://databases/%s/storage-units", encode(database));
-        }
         String table = Objects.toString(contextArguments.get("table"), "");
-        if ("column".equals(argumentName) && !database.isEmpty() && !schema.isEmpty() && !table.isEmpty()) {
-            return String.format("shardingsphere://databases/%s/schemas/%s/tables/%s/columns", encode(database), encode(schema), encode(table));
-        }
-        if ("index".equals(argumentName) && !database.isEmpty() && !schema.isEmpty() && !table.isEmpty()) {
-            return String.format("shardingsphere://databases/%s/schemas/%s/tables/%s/indexes", encode(database), encode(schema), encode(table));
-        }
-        return "";
+        return switch (target) {
+            case DATABASE -> "shardingsphere://databases";
+            case SCHEMA -> database.isEmpty() ? "" : String.format("shardingsphere://databases/%s/schemas", encode(database));
+            case TABLE -> database.isEmpty() || schema.isEmpty() ? "" : String.format("shardingsphere://databases/%s/schemas/%s/tables", encode(database), encode(schema));
+            case SEQUENCE -> database.isEmpty() || schema.isEmpty()
+                    ? "" : String.format("shardingsphere://databases/%s/schemas/%s/sequences", encode(database), encode(schema));
+            case STORAGE_UNIT -> database.isEmpty() ? "" : String.format("shardingsphere://databases/%s/storage-units", encode(database));
+            case COLUMN -> database.isEmpty() || schema.isEmpty() || table.isEmpty()
+                    ? "" : String.format("shardingsphere://databases/%s/schemas/%s/tables/%s/columns", encode(database), encode(schema), encode(table));
+            case INDEX -> database.isEmpty() || schema.isEmpty() || table.isEmpty()
+                    ? "" : String.format("shardingsphere://databases/%s/schemas/%s/tables/%s/indexes", encode(database), encode(schema), encode(table));
+            case UNKNOWN -> "";
+        };
     }
     
     private String encode(final String value) {
         return MCPUriPathSegmentUtils.encodePathSegment(value);
     }
+    
+    private enum MetadataCompletionTarget {
+        
+        DATABASE("database"),
+        SCHEMA("schema", "database"),
+        TABLE("table", "database", "schema"),
+        COLUMN("column", "database", "schema", "table"),
+        INDEX("index", "database", "schema", "table"),
+        SEQUENCE("sequence", "database", "schema"),
+        STORAGE_UNIT("storageUnit", "database"),
+        UNKNOWN("");
+        
+        private final String argumentName;
+        
+        private final List<String> requiredContextArguments;
+        
+        MetadataCompletionTarget(final String argumentName, final String... requiredContextArguments) {
+            this.argumentName = argumentName;
+            this.requiredContextArguments = List.of(requiredContextArguments);
+        }
+        
+        private static MetadataCompletionTarget from(final String argumentName) {
+            String canonicalArgumentName = STORAGE_UNIT_ARGUMENTS.contains(argumentName) ? "storageUnit" : argumentName;
+            for (MetadataCompletionTarget each : values()) {
+                if (each.argumentName.equals(canonicalArgumentName)) {
+                    return each;
+                }
+            }
+            return UNKNOWN;
+        }
+        
+        private boolean requires(final String argumentName) {
+            return requiredContextArguments.contains(argumentName);
+        }
+    }
 }
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPWorkflowRecoveryPayloadFactory.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPWorkflowRecoveryPayloadFactory.java
index 2fac38a4472..63e747633af 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPWorkflowRecoveryPayloadFactory.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPWorkflowRecoveryPayloadFactory.java
@@ -23,10 +23,12 @@ import org.apache.shardingsphere.mcp.core.protocol.exception.MCPExecutionModeReq
 import org.apache.shardingsphere.mcp.core.protocol.exception.MCPInvalidApprovedStepsException;
 import org.apache.shardingsphere.mcp.core.protocol.exception.MCPInvalidExecutionModeException;
 import org.apache.shardingsphere.mcp.core.protocol.exception.MCPWorkflowStateException;
+import org.apache.shardingsphere.mcp.support.protocol.MCPCompletionAction;
 import org.apache.shardingsphere.mcp.support.protocol.MCPNextActionUtils;
 import org.apache.shardingsphere.mcp.support.protocol.MCPPayloadFieldNames;
 import org.apache.shardingsphere.mcp.support.workflow.descriptor.WorkflowToolDescriptors;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowFieldNames;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 
 import java.util.List;
 import java.util.Map;
@@ -49,7 +51,8 @@ final class MCPWorkflowRecoveryPayloadFactory {
         Map<String, Object> result = MCPRecoveryPayloadSupport.createBaseRecovery(
                 "invalid_enum_value", "Retry database_gateway_apply_workflow with execution_mode=preview, review the returned preview_artifacts, "
                         + "then pass explicit approved_steps copied from visible preview_artifacts.approval_step values.");
-        Map<String, Object> suggestedArguments = MCPRecoveryPayloadSupport.getSuggestedArguments(cause.getSuggestedArguments(), Map.of(WorkflowFieldNames.EXECUTION_MODE, "preview"));
+        Map<String, Object> suggestedArguments = MCPRecoveryPayloadSupport.getSuggestedArguments(cause.getSuggestedArguments(),
+                Map.of(WorkflowFieldNames.EXECUTION_MODE, WorkflowLifecycle.EXECUTION_MODE_PREVIEW));
         result.put(MCPPayloadFieldNames.FIELD, WorkflowFieldNames.APPROVED_STEPS);
         result.put(MCPPayloadFieldNames.ALLOWED_VALUES, cause.getAllowedValues());
         result.put("suggested_arguments", suggestedArguments);
@@ -67,8 +70,14 @@ final class MCPWorkflowRecoveryPayloadFactory {
                 "shardingsphere://capabilities", "capability", "Read workflow-capable MCP tools before re-planning."));
         result.put("completion_first", Map.of("argument", WorkflowFieldNames.PLAN_ID, "scope", "current MCP session"));
         result.put(MCPPayloadFieldNames.NEXT_ACTIONS, MCPNextActionUtils.ordered(
-                MCPNextActionUtils.completeArgument("resource", "shardingsphere://workflows/{plan_id}", WorkflowFieldNames.PLAN_ID, "", Map.of(), List.of(), "resource",
-                        "shardingsphere://workflows/{plan_id}", Map.of(), "Use MCP completion for plan_id to pick an available current-session workflow plan."),
+                MCPNextActionUtils.completeArgument(MCPCompletionAction.builder()
+                        .referenceType("resource")
+                        .reference("shardingsphere://workflows/{plan_id}")
+                        .argumentName(WorkflowFieldNames.PLAN_ID)
+                        .resumeTargetType("resource")
+                        .resumeTarget("shardingsphere://workflows/{plan_id}")
+                        .reason("Use MCP completion for plan_id to pick an available current-session workflow plan.")
+                        .build()),
                 MCPNextActionUtils.dependsOn(MCPNextActionUtils.readResource(
                         "shardingsphere://capabilities", "Read current workflow tools, then re-run the matching planning tool if completion has no usable plan."), 1)));
         result.put("ask_user_when_uncertain", false);
@@ -109,7 +118,8 @@ final class MCPWorkflowRecoveryPayloadFactory {
         result.put("source_tool", toolName);
         result.put("tool_name", toolName);
         result.put(MCPPayloadFieldNames.ALLOWED_VALUES, allowedValues);
-        Map<String, Object> suggestedArguments = MCPRecoveryPayloadSupport.getSuggestedArguments(sourceSuggestedArguments, Map.of(WorkflowFieldNames.EXECUTION_MODE, "preview"));
+        Map<String, Object> suggestedArguments = MCPRecoveryPayloadSupport.getSuggestedArguments(sourceSuggestedArguments,
+                Map.of(WorkflowFieldNames.EXECUTION_MODE, WorkflowLifecycle.EXECUTION_MODE_PREVIEW));
         result.put("suggested_arguments", suggestedArguments);
         result.put(MCPPayloadFieldNames.NEXT_ACTIONS, List.of(retryTool
                 ? MCPNextActionUtils.retryTool(toolName, retryReason, suggestedArguments)
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/ResourceDefinitionRegistry.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/ResourceDefinitionRegistry.java
index 8e74886afeb..426016968b8 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/ResourceDefinitionRegistry.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/ResourceDefinitionRegistry.java
@@ -84,7 +84,7 @@ public final class ResourceDefinitionRegistry {
             Entry<MCPUriPattern, MCPResourceHandler<?>> current = entries.get(i);
             for (int j = i + 1; j < entries.size(); j++) {
                 Entry<MCPUriPattern, MCPResourceHandler<?>> other = entries.get(j);
-                ShardingSpherePreconditions.checkState(!current.getKey().isOverlaps(other.getKey()), () -> new IllegalArgumentException(
+                ShardingSpherePreconditions.checkState(!current.getKey().overlaps(other.getKey()), () -> new IllegalArgumentException(
                         String.format("Overlapping resource URI templates `%s` with `%s` and `%s`.",
                                 current.getKey().getPattern(), current.getValue().getClass().getName(), other.getValue().getClass().getName())));
             }
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/RuntimeStatusHandler.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/RuntimeStatusHandler.java
index 57c8f7d2a3d..1f63042b15c 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/RuntimeStatusHandler.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/RuntimeStatusHandler.java
@@ -183,7 +183,7 @@ public final class RuntimeStatusHandler implements MCPResourceHandler<MCPDatabas
     private Map<String, Object> createCapabilityStatus(final MCPDatabaseCapability capability) {
         Map<String, Object> result = new LinkedHashMap<>(4, 1F);
         result.put("available", true);
-        result.put("supports_explain", capability.isSupportsExplain());
+        result.put("supports_explain", capability.supportsExplain());
         result.put("supported_statement_classes", capability.getSupportedStatementClasses().stream().map(Enum::name).toList());
         result.put("supported_metadata_object_types", capability.getSupportedMetadataObjectTypes().stream().map(Enum::name).toList());
         return result;
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceHandler.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceHandler.java
index a67a86233a9..c206be37f9f 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceHandler.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceHandler.java
@@ -25,22 +25,9 @@ import org.apache.shardingsphere.mcp.api.resource.descriptor.MCPResourceDescript
 import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
 import org.apache.shardingsphere.mcp.support.descriptor.MCPDescriptorCatalogIndex;
 import org.apache.shardingsphere.mcp.support.descriptor.ShardingSphereMCPResourceMetadata;
-import org.apache.shardingsphere.mcp.support.diagnostic.MCPDiagnosticCategory;
-import org.apache.shardingsphere.mcp.support.protocol.MCPNextActionUtils;
-import org.apache.shardingsphere.mcp.support.protocol.MCPPayloadFieldNames;
-import org.apache.shardingsphere.mcp.support.protocol.MCPResourceHintUtils;
-import org.apache.shardingsphere.mcp.support.protocol.MCPResponseMode;
-import org.apache.shardingsphere.mcp.support.protocol.response.MCPItemsResponse;
-import org.apache.shardingsphere.mcp.support.protocol.response.MCPMapResponse;
-import org.apache.shardingsphere.mcp.support.resource.MCPUriTemplate;
 
-import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.Optional;
 import java.util.function.BiFunction;
-import java.util.stream.Stream;
 
 /**
  * Metadata resource handler backed by one metadata loader function.
@@ -48,8 +35,6 @@ import java.util.stream.Stream;
 @RequiredArgsConstructor
 public final class MetadataResourceHandler implements MCPResourceHandler<MCPDatabaseHandlerContext> {
     
-    private static final int LARGE_RESULT_THRESHOLD = 100;
-    
     private final String uriTemplate;
     
     private final BiFunction<MCPDatabaseHandlerContext, MCPUriVariables, List<?>> metadataLoader;
@@ -69,290 +54,9 @@ public final class MetadataResourceHandler implements MCPResourceHandler<MCPData
         List<?> items = metadataLoader.apply(databaseContext, uriVariables);
         MCPResourceDescriptor descriptor = MCPDescriptorCatalogIndex.getRequiredResourceDescriptor(uriTemplate);
         ShardingSphereMCPResourceMetadata metadata = MCPDescriptorCatalogIndex.getRequiredShardingSphereResourceMetadata(descriptor.getUriTemplate());
-        boolean detailResource = isDetailResource(metadata);
+        boolean detailResource = "detail".equals(metadata.getResourceKind());
         List<?> payloadItems = new MetadataResourcePayloadMapper(databaseContext.getMetadataQueryFacade(), uriVariables, detailResource).map(metadata, items);
-        Map<String, Object> navigationPayload = createNavigationPayload(descriptor, uriVariables);
-        if (detailResource) {
-            if (payloadItems.isEmpty()) {
-                appendEmptyStateGuidance(navigationPayload, metadata, databaseContext, uriVariables);
-            }
-            return new MCPMapResponse(createDetailPayload(metadata, payloadItems, navigationPayload));
-        }
-        List<?> returnedItems = capListItems(payloadItems);
-        appendListSizeMetadata(navigationPayload, payloadItems.size(), returnedItems.size());
-        navigationPayload.put(MCPPayloadFieldNames.SUMMARY, createListSummary(metadata, payloadItems.size(), returnedItems.size()));
-        if (payloadItems.isEmpty()) {
-            appendEmptyStateGuidance(navigationPayload, metadata, databaseContext, uriVariables);
-        } else if (isTruncated(payloadItems, returnedItems)) {
-            appendLargeResultGuidance(navigationPayload, metadata, uriVariables, payloadItems.size());
-        }
-        return new MCPItemsResponse(returnedItems, navigationPayload);
-    }
-    
-    private boolean isDetailResource(final ShardingSphereMCPResourceMetadata descriptor) {
-        return "detail".equals(descriptor.getResourceKind());
-    }
-    
-    private List<?> capListItems(final List<?> items) {
-        return items.size() <= LARGE_RESULT_THRESHOLD ? items : items.subList(0, LARGE_RESULT_THRESHOLD);
-    }
-    
-    private void appendListSizeMetadata(final Map<String, Object> payload, final int totalCount, final int returnedCount) {
-        payload.put("total_count", totalCount);
-        payload.put("returned_count", returnedCount);
-        payload.put("truncated", returnedCount < totalCount);
-    }
-    
-    private boolean isTruncated(final List<?> items, final List<?> returnedItems) {
-        return returnedItems.size() < items.size();
-    }
-    
-    private Map<String, Object> createDetailPayload(final ShardingSphereMCPResourceMetadata descriptor, final List<?> items, final Map<String, Object> navigationPayload) {
-        Map<String, Object> result = new LinkedHashMap<>(navigationPayload.size() + 7, 1F);
-        result.put("response_mode", MCPResponseMode.DETAIL);
-        result.put(MCPPayloadFieldNames.SUMMARY, createDetailSummary(descriptor, items));
-        result.put(MCPPayloadFieldNames.RESOURCE_KIND, "detail");
-        if (null != descriptor.getObjectScope()) {
-            result.put("object_scope", descriptor.getObjectScope());
-        }
-        result.put("found", !items.isEmpty());
-        result.put(MCPPayloadFieldNames.ITEMS, items);
-        result.put("count", items.size());
-        if (!items.isEmpty()) {
-            result.put("item", items.getFirst());
-        }
-        result.putAll(navigationPayload);
-        return result;
-    }
-    
-    private String createListSummary(final ShardingSphereMCPResourceMetadata descriptor, final int totalCount, final int returnedCount) {
-        return String.format("Returned %d of %d %s metadata entries.", returnedCount, totalCount, resolveSummaryScope(descriptor));
-    }
-    
-    private String createDetailSummary(final ShardingSphereMCPResourceMetadata descriptor, final List<?> items) {
-        return items.isEmpty()
-                ? String.format("No %s detail item matched this resource URI.", resolveSummaryScope(descriptor))
-                : String.format("Returned %s detail for this resource URI.", resolveSummaryScope(descriptor));
-    }
-    
-    private String resolveSummaryScope(final ShardingSphereMCPResourceMetadata descriptor) {
-        return null == descriptor.getObjectScope() ? descriptor.getResourceKind() : descriptor.getObjectScope().replace('_', '-');
-    }
-    
-    private void appendEmptyStateGuidance(final Map<String, Object> payload, final ShardingSphereMCPResourceMetadata descriptor,
-                                          final MCPDatabaseHandlerContext databaseContext, final MCPUriVariables uriVariables) {
-        Map<String, Object> emptyState = new LinkedHashMap<>(4, 1F);
-        String resourceKind = null == descriptor.getObjectScope() ? "metadata" : descriptor.getObjectScope();
-        String recoveryCategory = resolveEmptyStateCategory(descriptor, databaseContext, uriVariables);
-        if (MCPDiagnosticCategory.NOT_FOUND.equals(recoveryCategory)) {
-            emptyState.put("state", "not_found");
-            emptyState.put("category", recoveryCategory);
-        } else {
-            emptyState.put("state", "no_items");
-            emptyState.put("category", recoveryCategory);
-        }
-        String reason = createEmptyStateReason(recoveryCategory, resourceKind);
-        emptyState.put(MCPPayloadFieldNames.REASON, reason);
-        emptyState.put(MCPPayloadFieldNames.RESOURCE_KIND, resourceKind);
-        payload.put("empty_state", emptyState);
-        String parentUri = getResourceHintUri(payload.get(MCPPayloadFieldNames.PARENT_RESOURCE));
-        payload.put(MCPPayloadFieldNames.RECOVERY, createRecovery(recoveryCategory, resourceKind, parentUri, uriVariables));
-        payload.put(MCPPayloadFieldNames.NEXT_ACTIONS, parentUri.isEmpty()
-                ? List.of(MCPNextActionUtils.stop(reason))
-                : List.of(MCPNextActionUtils.readResource(parentUri, "Read the parent metadata resource before broadening or correcting the request.")));
-    }
-    
-    private String resolveEmptyStateCategory(final ShardingSphereMCPResourceMetadata descriptor, final MCPDatabaseHandlerContext databaseContext, final MCPUriVariables uriVariables) {
-        if ("shardingsphere://databases".equals(uriTemplate)) {
-            return MCPDiagnosticCategory.NO_RUNTIME_DATABASE;
-        }
-        if (uriVariables.containsVariable("database") && !isKnownDatabase(databaseContext, uriVariables.getValue("database"))) {
-            return MCPDiagnosticCategory.UNKNOWN_DATABASE;
-        }
-        if (isDetailResource(descriptor)) {
-            return resolveDetailEmptyStateCategory(descriptor, uriVariables);
-        }
-        return MCPDiagnosticCategory.EMPTY_SCOPE;
-    }
-    
-    private String resolveDetailEmptyStateCategory(final ShardingSphereMCPResourceMetadata descriptor, final MCPUriVariables uriVariables) {
-        if ("schema".equals(descriptor.getObjectScope()) && uriVariables.containsVariable("schema")) {
-            return MCPDiagnosticCategory.SCHEMA_NOT_VISIBLE;
-        }
-        if (containsObjectToken(uriVariables)) {
-            return MCPDiagnosticCategory.OBJECT_NOT_VISIBLE;
-        }
-        if ("logical-database".equals(descriptor.getObjectScope()) && uriVariables.containsVariable("database")) {
-            return MCPDiagnosticCategory.DATABASE_NOT_VISIBLE;
-        }
-        return MCPDiagnosticCategory.NOT_FOUND;
-    }
-    
-    private boolean containsObjectToken(final MCPUriVariables uriVariables) {
-        return Stream.of("column", "index", "sequence", "view", "storageUnit", "table").anyMatch(uriVariables::containsVariable);
+        return new MetadataResourceResponseFactory().create(databaseContext, uriVariables, descriptor, metadata, payloadItems);
     }
     
-    private boolean isKnownDatabase(final MCPDatabaseHandlerContext databaseContext, final String databaseName) {
-        return Optional.ofNullable(databaseContext.getCapabilityFacade()).flatMap(capabilityFacade -> capabilityFacade.findDatabaseProfile(databaseName)).isPresent();
-    }
-    
-    private String createEmptyStateReason(final String category, final String resourceKind) {
-        switch (category) {
-            case MCPDiagnosticCategory.NO_RUNTIME_DATABASE:
-                return "No ShardingSphere-Proxy logical database is available to MCP. Configure runtimeDatabases before reading metadata.";
-            case MCPDiagnosticCategory.UNKNOWN_DATABASE:
-                return "The requested logical database is not visible to MCP. Check runtimeDatabases and ShardingSphere-Proxy connectivity.";
-            case MCPDiagnosticCategory.DATABASE_NOT_VISIBLE:
-                return "The requested logical database is configured but not visible through the current runtime metadata scope.";
-            case MCPDiagnosticCategory.SCHEMA_NOT_VISIBLE:
-                return "The requested schema is not visible in the current metadata scope.";
-            case MCPDiagnosticCategory.OBJECT_NOT_VISIBLE:
-                return "The requested metadata object is not visible in the current metadata scope.";
-            case MCPDiagnosticCategory.NOT_FOUND:
-                return String.format("%s detail resource was not found for this URI.", resourceKind);
-            default:
-                return "No metadata items are visible in this scope. Check metadata permissions if objects are expected.";
-        }
-    }
-    
-    private Map<String, Object> createRecovery(final String category, final String resourceKind, final String parentUri, final MCPUriVariables uriVariables) {
-        Map<String, Object> result = new LinkedHashMap<>(6, 1F);
-        result.put("response_mode", MCPResponseMode.RECOVERY);
-        result.put("recovery_category", category);
-        result.put("category", category);
-        result.put(MCPPayloadFieldNames.RESOURCE_KIND, resourceKind);
-        if (!parentUri.isEmpty()) {
-            result.put("parent_resource_uri", parentUri);
-        }
-        String requestedToken = createRequestedToken(uriVariables);
-        if (!requestedToken.isEmpty()) {
-            result.put("requested_token", requestedToken);
-            result.put("retry_arguments", Map.of("query", requestedToken));
-        }
-        return result;
-    }
-    
-    private String createRequestedToken(final MCPUriVariables uriVariables) {
-        return Stream.of("column", "index", "sequence", "view", "storageUnit", "table", "schema", "database")
-                .filter(uriVariables::containsVariable).findFirst().map(uriVariables::getValue).orElse("");
-    }
-    
-    private void appendLargeResultGuidance(final Map<String, Object> payload, final ShardingSphereMCPResourceMetadata descriptor, final MCPUriVariables uriVariables, final int itemCount) {
-        Map<String, Object> largeResult = new LinkedHashMap<>(4, 1F);
-        largeResult.put("state", "broad_metadata_list");
-        largeResult.put("count", itemCount);
-        largeResult.put("threshold", LARGE_RESULT_THRESHOLD);
-        largeResult.put(MCPPayloadFieldNames.REASON,
-                "This metadata resource returned many items. Use database_gateway_search_metadata with an explicit query or scope before reading many detail resources.");
-        largeResult.put("search_arguments", createNarrowSearchArguments(descriptor, uriVariables));
-        payload.put("continuation_mode", "metadata_search");
-        payload.put("large_result_guidance", largeResult);
-        payload.put(MCPPayloadFieldNames.NEXT_ACTIONS, List.of(MCPNextActionUtils.callTool("database_gateway_search_metadata",
-                String.format("Narrow the broad %s metadata list before reading detail resources.", resolveGuidanceScope(descriptor)),
-                createNarrowSearchArguments(descriptor, uriVariables))));
-    }
-    
-    private Map<String, Object> createNarrowSearchArguments(final ShardingSphereMCPResourceMetadata descriptor, final MCPUriVariables uriVariables) {
-        Map<String, Object> result = new LinkedHashMap<>(4, 1F);
-        if (uriVariables.containsVariable("database")) {
-            result.put("database", uriVariables.getValue("database"));
-        }
-        if (uriVariables.containsVariable("schema")) {
-            result.put("schema", uriVariables.getValue("schema"));
-        }
-        result.put("object_types", Collections.singletonList(resolveSearchObjectType(descriptor)));
-        return result;
-    }
-    
-    private String resolveSearchObjectType(final ShardingSphereMCPResourceMetadata descriptor) {
-        String objectScope = descriptor.getObjectScope();
-        if ("logical-database".equals(objectScope)) {
-            return "database";
-        }
-        if ("logical-table".equals(objectScope)) {
-            return "table";
-        }
-        return null == objectScope ? "database" : objectScope;
-    }
-    
-    private String resolveGuidanceScope(final ShardingSphereMCPResourceMetadata descriptor) {
-        return null == descriptor.getObjectScope() ? "logical" : descriptor.getObjectScope();
-    }
-    
-    private String getResourceHintUri(final Object value) {
-        if (!(value instanceof Map)) {
-            return "";
-        }
-        Object uri = ((Map<?, ?>) value).get(MCPPayloadFieldNames.URI);
-        return null == uri ? "" : uri.toString();
-    }
-    
-    private Map<String, Object> createNavigationPayload(final MCPResourceDescriptor descriptor, final MCPUriVariables uriVariables) {
-        Map<String, Object> result = new LinkedHashMap<>(4, 1F);
-        String uriTemplate = descriptor.getUriTemplate();
-        Optional<String> selfUri = new MCPUriTemplate(uriTemplate).expandIfComplete(uriVariables);
-        selfUri.ifPresent(uri -> {
-            result.put("self_uri", uri);
-            result.put(MCPPayloadFieldNames.SELF_RESOURCE,
-                    MCPResourceHintUtils.create(uri, resolveResourceKind(uri), "inspect_self", "Read this metadata resource.", MCPPayloadFieldNames.SELF_RESOURCE));
-        });
-        String parentUri = createParentUri(selfUri.orElse(""));
-        if (!parentUri.isEmpty()) {
-            result.put(MCPPayloadFieldNames.PARENT_RESOURCE, MCPResourceHintUtils.create(parentUri, resolveResourceKind(parentUri), "inspect_parent",
-                    "Read the parent metadata resource before broadening or correcting the request.", MCPPayloadFieldNames.PARENT_RESOURCE));
-        }
-        List<Map<String, Object>> nextResources = MCPDescriptorCatalogIndex.getResourceNavigationDescriptors(uriTemplate).stream()
-                .filter(each -> each.getTo().startsWith("shardingsphere://"))
-                .map(each -> createNextResourceHint(each.getTo(), each.getDescription(), uriVariables)).flatMap(Optional::stream).toList();
-        if (!nextResources.isEmpty()) {
-            result.put(MCPPayloadFieldNames.NEXT_RESOURCES, nextResources);
-        }
-        return result;
-    }
-    
-    private Optional<Map<String, Object>> createNextResourceHint(final String uriTemplate, final String description, final MCPUriVariables variables) {
-        return new MCPUriTemplate(uriTemplate).expandIfComplete(variables)
-                .map(optional -> MCPResourceHintUtils.create(optional, resolveResourceKind(optional), "inspect_detail", description, MCPPayloadFieldNames.NEXT_RESOURCES));
-    }
-    
-    private String resolveResourceKind(final String uri) {
-        if (uri.contains("/columns")) {
-            return "column";
-        }
-        if (uri.contains("/indexes")) {
-            return "index";
-        }
-        if (uri.contains("/storage-units")) {
-            return "storage-unit";
-        }
-        if (uri.contains("/single-tables") || uri.contains("/single-table/default-storage-unit")) {
-            return "single-table";
-        }
-        if (uri.contains("/tables")) {
-            return "logical-table";
-        }
-        if (uri.contains("/views")) {
-            return "view";
-        }
-        if (uri.contains("/sequences")) {
-            return "sequence";
-        }
-        if (uri.contains("/schemas")) {
-            return "schema";
-        }
-        return "logical-database";
-    }
-    
-    private String createParentUri(final String selfUri) {
-        if (selfUri.isEmpty()) {
-            return "";
-        }
-        String prefix = "shardingsphere://";
-        if (!selfUri.startsWith(prefix)) {
-            return "";
-        }
-        String path = selfUri.substring(prefix.length());
-        int lastSeparatorIndex = path.lastIndexOf('/');
-        return 0 > lastSeparatorIndex ? "" : prefix + path.substring(0, lastSeparatorIndex);
-    }
 }
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceHandler.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceResponseFactory.java
similarity index 70%
copy from mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceHandler.java
copy to mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceResponseFactory.java
index a67a86233a9..dd90f0082da 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceHandler.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceResponseFactory.java
@@ -17,9 +17,7 @@
 
 package org.apache.shardingsphere.mcp.core.resource.handler.metadata;
 
-import lombok.RequiredArgsConstructor;
 import org.apache.shardingsphere.mcp.api.protocol.response.MCPResponse;
-import org.apache.shardingsphere.mcp.api.resource.MCPResourceHandler;
 import org.apache.shardingsphere.mcp.api.resource.MCPUriVariables;
 import org.apache.shardingsphere.mcp.api.resource.descriptor.MCPResourceDescriptor;
 import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
@@ -39,58 +37,47 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.function.BiFunction;
 import java.util.stream.Stream;
 
 /**
- * Metadata resource handler backed by one metadata loader function.
+ * Factory for metadata resource responses.
  */
-@RequiredArgsConstructor
-public final class MetadataResourceHandler implements MCPResourceHandler<MCPDatabaseHandlerContext> {
+public final class MetadataResourceResponseFactory {
     
     private static final int LARGE_RESULT_THRESHOLD = 100;
     
-    private final String uriTemplate;
-    
-    private final BiFunction<MCPDatabaseHandlerContext, MCPUriVariables, List<?>> metadataLoader;
-    
-    @Override
-    public Class<MCPDatabaseHandlerContext> getContextType() {
-        return MCPDatabaseHandlerContext.class;
-    }
-    
-    @Override
-    public String getResourceUriTemplate() {
-        return uriTemplate;
-    }
-    
-    @Override
-    public MCPResponse handle(final MCPDatabaseHandlerContext databaseContext, final MCPUriVariables uriVariables) {
-        List<?> items = metadataLoader.apply(databaseContext, uriVariables);
-        MCPResourceDescriptor descriptor = MCPDescriptorCatalogIndex.getRequiredResourceDescriptor(uriTemplate);
-        ShardingSphereMCPResourceMetadata metadata = MCPDescriptorCatalogIndex.getRequiredShardingSphereResourceMetadata(descriptor.getUriTemplate());
-        boolean detailResource = isDetailResource(metadata);
-        List<?> payloadItems = new MetadataResourcePayloadMapper(databaseContext.getMetadataQueryFacade(), uriVariables, detailResource).map(metadata, items);
+    /**
+     * Create metadata resource response.
+     *
+     * @param databaseContext database handler context
+     * @param uriVariables URI variables
+     * @param descriptor resource descriptor
+     * @param metadata ShardingSphere resource metadata
+     * @param items mapped metadata items
+     * @return metadata resource response
+     */
+    public MCPResponse create(final MCPDatabaseHandlerContext databaseContext, final MCPUriVariables uriVariables, final MCPResourceDescriptor descriptor,
+                              final ShardingSphereMCPResourceMetadata metadata, final List<?> items) {
         Map<String, Object> navigationPayload = createNavigationPayload(descriptor, uriVariables);
-        if (detailResource) {
-            if (payloadItems.isEmpty()) {
-                appendEmptyStateGuidance(navigationPayload, metadata, databaseContext, uriVariables);
+        if (isDetailResource(metadata)) {
+            if (items.isEmpty()) {
+                appendEmptyStateGuidance(navigationPayload, metadata, databaseContext, uriVariables, descriptor.getUriTemplate());
             }
-            return new MCPMapResponse(createDetailPayload(metadata, payloadItems, navigationPayload));
+            return new MCPMapResponse(createDetailPayload(metadata, items, navigationPayload));
         }
-        List<?> returnedItems = capListItems(payloadItems);
-        appendListSizeMetadata(navigationPayload, payloadItems.size(), returnedItems.size());
-        navigationPayload.put(MCPPayloadFieldNames.SUMMARY, createListSummary(metadata, payloadItems.size(), returnedItems.size()));
-        if (payloadItems.isEmpty()) {
-            appendEmptyStateGuidance(navigationPayload, metadata, databaseContext, uriVariables);
-        } else if (isTruncated(payloadItems, returnedItems)) {
-            appendLargeResultGuidance(navigationPayload, metadata, uriVariables, payloadItems.size());
+        List<?> returnedItems = capListItems(items);
+        appendListSizeMetadata(navigationPayload, items.size(), returnedItems.size());
+        navigationPayload.put(MCPPayloadFieldNames.SUMMARY, createListSummary(metadata, items.size(), returnedItems.size()));
+        if (items.isEmpty()) {
+            appendEmptyStateGuidance(navigationPayload, metadata, databaseContext, uriVariables, descriptor.getUriTemplate());
+        } else if (isTruncated(items, returnedItems)) {
+            appendLargeResultGuidance(navigationPayload, metadata, uriVariables, items.size());
         }
         return new MCPItemsResponse(returnedItems, navigationPayload);
     }
     
-    private boolean isDetailResource(final ShardingSphereMCPResourceMetadata descriptor) {
-        return "detail".equals(descriptor.getResourceKind());
+    private boolean isDetailResource(final ShardingSphereMCPResourceMetadata metadata) {
+        return "detail".equals(metadata.getResourceKind());
     }
     
     private List<?> capListItems(final List<?> items) {
@@ -107,13 +94,13 @@ public final class MetadataResourceHandler implements MCPResourceHandler<MCPData
         return returnedItems.size() < items.size();
     }
     
-    private Map<String, Object> createDetailPayload(final ShardingSphereMCPResourceMetadata descriptor, final List<?> items, final Map<String, Object> navigationPayload) {
+    private Map<String, Object> createDetailPayload(final ShardingSphereMCPResourceMetadata metadata, final List<?> items, final Map<String, Object> navigationPayload) {
         Map<String, Object> result = new LinkedHashMap<>(navigationPayload.size() + 7, 1F);
         result.put("response_mode", MCPResponseMode.DETAIL);
-        result.put(MCPPayloadFieldNames.SUMMARY, createDetailSummary(descriptor, items));
+        result.put(MCPPayloadFieldNames.SUMMARY, createDetailSummary(metadata, items));
         result.put(MCPPayloadFieldNames.RESOURCE_KIND, "detail");
-        if (null != descriptor.getObjectScope()) {
-            result.put("object_scope", descriptor.getObjectScope());
+        if (null != metadata.getObjectScope()) {
+            result.put("object_scope", metadata.getObjectScope());
         }
         result.put("found", !items.isEmpty());
         result.put(MCPPayloadFieldNames.ITEMS, items);
@@ -125,25 +112,25 @@ public final class MetadataResourceHandler implements MCPResourceHandler<MCPData
         return result;
     }
     
-    private String createListSummary(final ShardingSphereMCPResourceMetadata descriptor, final int totalCount, final int returnedCount) {
-        return String.format("Returned %d of %d %s metadata entries.", returnedCount, totalCount, resolveSummaryScope(descriptor));
+    private String createListSummary(final ShardingSphereMCPResourceMetadata metadata, final int totalCount, final int returnedCount) {
+        return String.format("Returned %d of %d %s metadata entries.", returnedCount, totalCount, resolveSummaryScope(metadata));
     }
     
-    private String createDetailSummary(final ShardingSphereMCPResourceMetadata descriptor, final List<?> items) {
+    private String createDetailSummary(final ShardingSphereMCPResourceMetadata metadata, final List<?> items) {
         return items.isEmpty()
-                ? String.format("No %s detail item matched this resource URI.", resolveSummaryScope(descriptor))
-                : String.format("Returned %s detail for this resource URI.", resolveSummaryScope(descriptor));
+                ? String.format("No %s detail item matched this resource URI.", resolveSummaryScope(metadata))
+                : String.format("Returned %s detail for this resource URI.", resolveSummaryScope(metadata));
     }
     
-    private String resolveSummaryScope(final ShardingSphereMCPResourceMetadata descriptor) {
-        return null == descriptor.getObjectScope() ? descriptor.getResourceKind() : descriptor.getObjectScope().replace('_', '-');
+    private String resolveSummaryScope(final ShardingSphereMCPResourceMetadata metadata) {
+        return null == metadata.getObjectScope() ? metadata.getResourceKind() : metadata.getObjectScope().replace('_', '-');
     }
     
-    private void appendEmptyStateGuidance(final Map<String, Object> payload, final ShardingSphereMCPResourceMetadata descriptor,
-                                          final MCPDatabaseHandlerContext databaseContext, final MCPUriVariables uriVariables) {
+    private void appendEmptyStateGuidance(final Map<String, Object> payload, final ShardingSphereMCPResourceMetadata metadata,
+                                          final MCPDatabaseHandlerContext databaseContext, final MCPUriVariables uriVariables, final String uriTemplate) {
         Map<String, Object> emptyState = new LinkedHashMap<>(4, 1F);
-        String resourceKind = null == descriptor.getObjectScope() ? "metadata" : descriptor.getObjectScope();
-        String recoveryCategory = resolveEmptyStateCategory(descriptor, databaseContext, uriVariables);
+        String resourceKind = null == metadata.getObjectScope() ? "metadata" : metadata.getObjectScope();
+        String recoveryCategory = resolveEmptyStateCategory(metadata, databaseContext, uriVariables, uriTemplate);
         if (MCPDiagnosticCategory.NOT_FOUND.equals(recoveryCategory)) {
             emptyState.put("state", "not_found");
             emptyState.put("category", recoveryCategory);
@@ -162,27 +149,28 @@ public final class MetadataResourceHandler implements MCPResourceHandler<MCPData
                 : List.of(MCPNextActionUtils.readResource(parentUri, "Read the parent metadata resource before broadening or correcting the request.")));
     }
     
-    private String resolveEmptyStateCategory(final ShardingSphereMCPResourceMetadata descriptor, final MCPDatabaseHandlerContext databaseContext, final MCPUriVariables uriVariables) {
+    private String resolveEmptyStateCategory(final ShardingSphereMCPResourceMetadata metadata, final MCPDatabaseHandlerContext databaseContext,
+                                             final MCPUriVariables uriVariables, final String uriTemplate) {
         if ("shardingsphere://databases".equals(uriTemplate)) {
             return MCPDiagnosticCategory.NO_RUNTIME_DATABASE;
         }
         if (uriVariables.containsVariable("database") && !isKnownDatabase(databaseContext, uriVariables.getValue("database"))) {
             return MCPDiagnosticCategory.UNKNOWN_DATABASE;
         }
-        if (isDetailResource(descriptor)) {
-            return resolveDetailEmptyStateCategory(descriptor, uriVariables);
+        if (isDetailResource(metadata)) {
+            return resolveDetailEmptyStateCategory(metadata, uriVariables);
         }
         return MCPDiagnosticCategory.EMPTY_SCOPE;
     }
     
-    private String resolveDetailEmptyStateCategory(final ShardingSphereMCPResourceMetadata descriptor, final MCPUriVariables uriVariables) {
-        if ("schema".equals(descriptor.getObjectScope()) && uriVariables.containsVariable("schema")) {
+    private String resolveDetailEmptyStateCategory(final ShardingSphereMCPResourceMetadata metadata, final MCPUriVariables uriVariables) {
+        if ("schema".equals(metadata.getObjectScope()) && uriVariables.containsVariable("schema")) {
             return MCPDiagnosticCategory.SCHEMA_NOT_VISIBLE;
         }
         if (containsObjectToken(uriVariables)) {
             return MCPDiagnosticCategory.OBJECT_NOT_VISIBLE;
         }
-        if ("logical-database".equals(descriptor.getObjectScope()) && uriVariables.containsVariable("database")) {
+        if ("logical-database".equals(metadata.getObjectScope()) && uriVariables.containsVariable("database")) {
             return MCPDiagnosticCategory.DATABASE_NOT_VISIBLE;
         }
         return MCPDiagnosticCategory.NOT_FOUND;
@@ -197,22 +185,24 @@ public final class MetadataResourceHandler implements MCPResourceHandler<MCPData
     }
     
     private String createEmptyStateReason(final String category, final String resourceKind) {
-        switch (category) {
-            case MCPDiagnosticCategory.NO_RUNTIME_DATABASE:
-                return "No ShardingSphere-Proxy logical database is available to MCP. Configure runtimeDatabases before reading metadata.";
-            case MCPDiagnosticCategory.UNKNOWN_DATABASE:
-                return "The requested logical database is not visible to MCP. Check runtimeDatabases and ShardingSphere-Proxy connectivity.";
-            case MCPDiagnosticCategory.DATABASE_NOT_VISIBLE:
-                return "The requested logical database is configured but not visible through the current runtime metadata scope.";
-            case MCPDiagnosticCategory.SCHEMA_NOT_VISIBLE:
-                return "The requested schema is not visible in the current metadata scope.";
-            case MCPDiagnosticCategory.OBJECT_NOT_VISIBLE:
-                return "The requested metadata object is not visible in the current metadata scope.";
-            case MCPDiagnosticCategory.NOT_FOUND:
-                return String.format("%s detail resource was not found for this URI.", resourceKind);
-            default:
-                return "No metadata items are visible in this scope. Check metadata permissions if objects are expected.";
+        if (MCPDiagnosticCategory.NO_RUNTIME_DATABASE.equals(category)) {
+            return "No ShardingSphere-Proxy logical database is available to MCP. Configure runtimeDatabases before reading metadata.";
+        }
+        if (MCPDiagnosticCategory.UNKNOWN_DATABASE.equals(category)) {
+            return "The requested logical database is not visible to MCP. Check runtimeDatabases and ShardingSphere-Proxy connectivity.";
+        }
+        if (MCPDiagnosticCategory.DATABASE_NOT_VISIBLE.equals(category)) {
+            return "The requested logical database is configured but not visible through the current runtime metadata scope.";
+        }
+        if (MCPDiagnosticCategory.SCHEMA_NOT_VISIBLE.equals(category)) {
+            return "The requested schema is not visible in the current metadata scope.";
+        }
+        if (MCPDiagnosticCategory.OBJECT_NOT_VISIBLE.equals(category)) {
+            return "The requested metadata object is not visible in the current metadata scope.";
         }
+        return MCPDiagnosticCategory.NOT_FOUND.equals(category)
+                ? String.format("%s detail resource was not found for this URI.", resourceKind)
+                : "No metadata items are visible in this scope. Check metadata permissions if objects are expected.";
     }
     
     private Map<String, Object> createRecovery(final String category, final String resourceKind, final String parentUri, final MCPUriVariables uriVariables) {
@@ -237,22 +227,23 @@ public final class MetadataResourceHandler implements MCPResourceHandler<MCPData
                 .filter(uriVariables::containsVariable).findFirst().map(uriVariables::getValue).orElse("");
     }
     
-    private void appendLargeResultGuidance(final Map<String, Object> payload, final ShardingSphereMCPResourceMetadata descriptor, final MCPUriVariables uriVariables, final int itemCount) {
+    private void appendLargeResultGuidance(final Map<String, Object> payload, final ShardingSphereMCPResourceMetadata metadata,
+                                           final MCPUriVariables uriVariables, final int itemCount) {
         Map<String, Object> largeResult = new LinkedHashMap<>(4, 1F);
         largeResult.put("state", "broad_metadata_list");
         largeResult.put("count", itemCount);
         largeResult.put("threshold", LARGE_RESULT_THRESHOLD);
         largeResult.put(MCPPayloadFieldNames.REASON,
                 "This metadata resource returned many items. Use database_gateway_search_metadata with an explicit query or scope before reading many detail resources.");
-        largeResult.put("search_arguments", createNarrowSearchArguments(descriptor, uriVariables));
+        largeResult.put("search_arguments", createNarrowSearchArguments(metadata, uriVariables));
         payload.put("continuation_mode", "metadata_search");
         payload.put("large_result_guidance", largeResult);
         payload.put(MCPPayloadFieldNames.NEXT_ACTIONS, List.of(MCPNextActionUtils.callTool("database_gateway_search_metadata",
-                String.format("Narrow the broad %s metadata list before reading detail resources.", resolveGuidanceScope(descriptor)),
-                createNarrowSearchArguments(descriptor, uriVariables))));
+                String.format("Narrow the broad %s metadata list before reading detail resources.", resolveGuidanceScope(metadata)),
+                createNarrowSearchArguments(metadata, uriVariables))));
     }
     
-    private Map<String, Object> createNarrowSearchArguments(final ShardingSphereMCPResourceMetadata descriptor, final MCPUriVariables uriVariables) {
+    private Map<String, Object> createNarrowSearchArguments(final ShardingSphereMCPResourceMetadata metadata, final MCPUriVariables uriVariables) {
         Map<String, Object> result = new LinkedHashMap<>(4, 1F);
         if (uriVariables.containsVariable("database")) {
             result.put("database", uriVariables.getValue("database"));
@@ -260,12 +251,12 @@ public final class MetadataResourceHandler implements MCPResourceHandler<MCPData
         if (uriVariables.containsVariable("schema")) {
             result.put("schema", uriVariables.getValue("schema"));
         }
-        result.put("object_types", Collections.singletonList(resolveSearchObjectType(descriptor)));
+        result.put("object_types", Collections.singletonList(resolveSearchObjectType(metadata)));
         return result;
     }
     
-    private String resolveSearchObjectType(final ShardingSphereMCPResourceMetadata descriptor) {
-        String objectScope = descriptor.getObjectScope();
+    private String resolveSearchObjectType(final ShardingSphereMCPResourceMetadata metadata) {
+        String objectScope = metadata.getObjectScope();
         if ("logical-database".equals(objectScope)) {
             return "database";
         }
@@ -275,8 +266,8 @@ public final class MetadataResourceHandler implements MCPResourceHandler<MCPData
         return null == objectScope ? "database" : objectScope;
     }
     
-    private String resolveGuidanceScope(final ShardingSphereMCPResourceMetadata descriptor) {
-        return null == descriptor.getObjectScope() ? "logical" : descriptor.getObjectScope();
+    private String resolveGuidanceScope(final ShardingSphereMCPResourceMetadata metadata) {
+        return null == metadata.getObjectScope() ? "logical" : metadata.getObjectScope();
     }
     
     private String getResourceHintUri(final Object value) {
@@ -312,7 +303,7 @@ public final class MetadataResourceHandler implements MCPResourceHandler<MCPData
     
     private Optional<Map<String, Object>> createNextResourceHint(final String uriTemplate, final String description, final MCPUriVariables variables) {
         return new MCPUriTemplate(uriTemplate).expandIfComplete(variables)
-                .map(optional -> MCPResourceHintUtils.create(optional, resolveResourceKind(optional), "inspect_detail", description, MCPPayloadFieldNames.NEXT_RESOURCES));
+                .map(uri -> MCPResourceHintUtils.create(uri, resolveResourceKind(uri), "inspect_detail", description, MCPPayloadFieldNames.NEXT_RESOURCES));
     }
     
     private String resolveResourceKind(final String uri) {
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/uri/MCPUriPattern.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/uri/MCPUriPattern.java
index d3c8aab1d8f..7a86ecadc31 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/uri/MCPUriPattern.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/uri/MCPUriPattern.java
@@ -153,7 +153,7 @@ public final class MCPUriPattern {
      * @param other other pattern
      * @return overlap or not
      */
-    public boolean isOverlaps(final MCPUriPattern other) {
+    public boolean overlaps(final MCPUriPattern other) {
         List<String> leftSegments = extractPathSegments(pattern, SCHEME.length() + SCHEME_SEPARATOR.length());
         List<String> rightSegments = extractPathSegments(other.getPattern(), SCHEME.length() + SCHEME_SEPARATOR.length());
         if (leftSegments.size() != rightSegments.size()) {
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/MCPToolController.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/MCPToolController.java
index 8197a710881..e491e53231f 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/MCPToolController.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/MCPToolController.java
@@ -52,11 +52,7 @@ public final class MCPToolController {
      * @throws UnsupportedToolException unsupported tool exception
      */
     public MCPResponse handle(final String sessionId, final String toolName, final Map<String, Object> arguments) {
-        try {
-            return handle(sessionId, ToolDefinitionRegistry.getToolDefinition(toolName), arguments);
-        } catch (final UnsupportedToolException ignored) {
-            throw new UnsupportedToolException(toolName);
-        }
+        return handle(sessionId, ToolDefinitionRegistry.getToolDefinition(toolName), arguments);
     }
     
     /**
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcStatementExecutor.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcStatementExecutor.java
index 0fbfe47818e..098d86d017f 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcStatementExecutor.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcStatementExecutor.java
@@ -73,23 +73,12 @@ public final class MCPJdbcStatementExecutor {
      * @throws MCPUnavailableException when the runtime database configuration is unavailable
      */
     public SQLExecutionResponse execute(final SQLExecutionRequest executionRequest, final ClassificationResult classificationResult, final MCPDatabaseCapability databaseCapability) {
-        Connection connection = null;
-        boolean needCloseConnection = false;
-        boolean transactionConnectionInUse = false;
         try {
-            try {
-                Optional<Connection> transactionConnection = transactionResourceManager.findTransactionConnection(executionRequest.getSessionId(), executionRequest.getDatabase());
-                if (transactionConnection.isPresent()) {
-                    connection = transactionConnection.get();
-                    transactionConnectionInUse = true;
-                } else {
-                    connection = openConnection(executionRequest.getDatabase());
-                    needCloseConnection = true;
-                }
-            } catch (final IllegalStateException ex) {
-                throw new MCPTransactionStateException(ex.getMessage(), ex);
+            Optional<Connection> transactionConnection = findTransactionConnection(executionRequest);
+            if (transactionConnection.isPresent()) {
+                return executeWithBorrowedConnection(transactionConnection.get(), executionRequest, classificationResult, databaseCapability);
             }
-            return executeWithConnection(connection, executionRequest, classificationResult, databaseCapability, transactionConnectionInUse);
+            return executeWithOwnedConnection(openOwnedConnection(executionRequest.getDatabase()), executionRequest, classificationResult, databaseCapability);
         } catch (final SQLTimeoutException ex) {
             throw new MCPTimeoutException(ex.getMessage(), ex);
         } catch (final SQLFeatureNotSupportedException ex) {
@@ -101,29 +90,58 @@ public final class MCPJdbcStatementExecutor {
             throw new MCPInvalidRequestException(ex.getMessage(), ex);
         } catch (final SQLException ex) {
             throw new MCPQueryFailedException(ex.getMessage(), ex);
-        } finally {
-            if (needCloseConnection && null != connection) {
-                try {
-                    connection.close();
-                } catch (final SQLException ignored) {
-                }
-            }
         }
     }
     
-    private SQLExecutionResponse executeWithConnection(final Connection connection, final SQLExecutionRequest executionRequest,
-                                                       final ClassificationResult classificationResult, final MCPDatabaseCapability databaseCapability,
-                                                       final boolean transactionConnectionInUse) throws SQLException {
+    private Optional<Connection> findTransactionConnection(final SQLExecutionRequest executionRequest) {
+        try {
+            return transactionResourceManager.findTransactionConnection(executionRequest.getSessionId(), executionRequest.getDatabase());
+        } catch (final IllegalStateException ex) {
+            throw new MCPTransactionStateException(ex.getMessage(), ex);
+        }
+    }
+    
+    private Connection openOwnedConnection(final String databaseName) throws SQLException {
+        try {
+            return openConnection(databaseName);
+        } catch (final IllegalStateException ex) {
+            throw new MCPTransactionStateException(ex.getMessage(), ex);
+        }
+    }
+    
+    private SQLExecutionResponse executeWithBorrowedConnection(final Connection connection, final SQLExecutionRequest executionRequest,
+                                                               final ClassificationResult classificationResult, final MCPDatabaseCapability databaseCapability) throws SQLException {
         applySchema(connection, executionRequest.getSchema(), databaseCapability.getSchemaExecutionSemantics());
-        if (executionRequest.isReadOnlyExecution() && !transactionConnectionInUse) {
-            return executeWithReadOnlyConnection(connection, executionRequest, classificationResult);
+        return executeWithStatement(connection, executionRequest, classificationResult);
+    }
+    
+    private SQLExecutionResponse executeWithOwnedConnection(final Connection connection, final SQLExecutionRequest executionRequest,
+                                                            final ClassificationResult classificationResult, final MCPDatabaseCapability databaseCapability) throws SQLException {
+        try {
+            applySchema(connection, executionRequest.getSchema(), databaseCapability.getSchemaExecutionSemantics());
+            return executionRequest.isReadOnlyExecution()
+                    ? executeWithReadOnlyConnection(connection, executionRequest, classificationResult)
+                    : executeWithStatement(connection, executionRequest, classificationResult);
+        } finally {
+            closeOwnedConnection(connection);
         }
+    }
+    
+    private SQLExecutionResponse executeWithStatement(final Connection connection, final SQLExecutionRequest executionRequest,
+                                                      final ClassificationResult classificationResult) throws SQLException {
         try (Statement statement = connection.createStatement()) {
             configureStatement(statement, executionRequest);
             return executeStatement(statement, executionRequest, classificationResult);
         }
     }
     
+    private void closeOwnedConnection(final Connection connection) {
+        try {
+            connection.close();
+        } catch (final SQLException ignored) {
+        }
+    }
+    
     private SQLExecutionResponse executeWithReadOnlyConnection(final Connection connection, final SQLExecutionRequest executionRequest,
                                                                final ClassificationResult classificationResult) throws SQLException {
         boolean originalReadOnly = connection.isReadOnly();
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionStatementExecutor.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionStatementExecutor.java
index 4110a62b5f2..4f762772a72 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionStatementExecutor.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionStatementExecutor.java
@@ -53,26 +53,17 @@ public final class MCPJdbcTransactionStatementExecutor {
         String statementType = classificationResult.getStatementType();
         try {
             ShardingSpherePreconditions.checkState(sessionManager.hasSession(sessionId), MCPSessionNotExistedException::new);
+            SQLExecutionResponse result;
             if ("BEGIN".equals(statementType) || "START TRANSACTION".equals(statementType)) {
-                return executeBeginTransaction(sessionId, databaseName, databaseCapability, statementType).withNormalizedSql(classificationResult.getNormalizedSql());
+                result = executeBeginTransaction(sessionId, databaseName, databaseCapability, statementType);
+            } else if ("COMMIT".equals(statementType)) {
+                result = executeCommit(sessionId, databaseCapability);
+            } else if ("ROLLBACK".equals(statementType)) {
+                result = executeRollback(sessionId, databaseCapability);
+            } else {
+                result = executeSavepointStatement(sessionId, databaseCapability, classificationResult);
             }
-            if ("COMMIT".equals(statementType)) {
-                return executeCommit(sessionId, databaseCapability).withNormalizedSql(classificationResult.getNormalizedSql());
-            }
-            if ("ROLLBACK".equals(statementType)) {
-                return executeRollback(sessionId, databaseCapability).withNormalizedSql(classificationResult.getNormalizedSql());
-            }
-            String savepointName = classificationResult.getSavepointName().orElse("");
-            if ("SAVEPOINT".equals(statementType)) {
-                return executeSavepoint(sessionId, databaseCapability, getRequiredSavepointName(savepointName)).withNormalizedSql(classificationResult.getNormalizedSql());
-            }
-            if ("ROLLBACK TO SAVEPOINT".equals(statementType)) {
-                return executeRollbackSavepoint(sessionId, databaseCapability, getRequiredSavepointName(savepointName)).withNormalizedSql(classificationResult.getNormalizedSql());
-            }
-            if ("RELEASE SAVEPOINT".equals(statementType)) {
-                return executeReleaseSavepoint(sessionId, databaseCapability, getRequiredSavepointName(savepointName)).withNormalizedSql(classificationResult.getNormalizedSql());
-            }
-            throw new MCPInvalidRequestException("Statement is not a transaction command.");
+            return result.withNormalizedSql(classificationResult.getNormalizedSql());
         } catch (final IllegalArgumentException ex) {
             throw new MCPInvalidRequestException(ex.getMessage(), ex);
         } catch (final IllegalStateException ex) {
@@ -80,6 +71,22 @@ public final class MCPJdbcTransactionStatementExecutor {
         }
     }
     
+    private SQLExecutionResponse executeSavepointStatement(final String sessionId, final MCPDatabaseCapability databaseCapability,
+                                                           final ClassificationResult classificationResult) {
+        String statementType = classificationResult.getStatementType();
+        String savepointName = classificationResult.getSavepointName().orElse("");
+        if ("SAVEPOINT".equals(statementType)) {
+            return executeSavepoint(sessionId, databaseCapability, getRequiredSavepointName(savepointName));
+        }
+        if ("ROLLBACK TO SAVEPOINT".equals(statementType)) {
+            return executeRollbackSavepoint(sessionId, databaseCapability, getRequiredSavepointName(savepointName));
+        }
+        if ("RELEASE SAVEPOINT".equals(statementType)) {
+            return executeReleaseSavepoint(sessionId, databaseCapability, getRequiredSavepointName(savepointName));
+        }
+        throw new MCPInvalidRequestException("Statement is not a transaction command.");
+    }
+    
     private String getRequiredSavepointName(final String savepointName) {
         String result = savepointName.trim();
         ShardingSpherePreconditions.checkNotEmpty(result, () -> new IllegalArgumentException("Savepoint name is required."));
@@ -87,37 +94,37 @@ public final class MCPJdbcTransactionStatementExecutor {
     }
     
     private SQLExecutionResponse executeBeginTransaction(final String sessionId, final String databaseName, final MCPDatabaseCapability databaseCapability, final String statementType) {
-        ShardingSpherePreconditions.checkState(databaseCapability.isSupportsTransactionControl(), () -> new MCPUnsupportedException("Transaction control is not supported."));
+        ShardingSpherePreconditions.checkState(databaseCapability.supportsTransactionControl(), () -> new MCPUnsupportedException("Transaction control is not supported."));
         sessionManager.getTransactionResourceManager().beginTransaction(sessionId, databaseName);
         return SQLExecutionResponse.statementAck(classifyTransactionStatement(statementType), statementType, "Transaction started.");
     }
     
     private SQLExecutionResponse executeCommit(final String sessionId, final MCPDatabaseCapability databaseCapability) {
-        ShardingSpherePreconditions.checkState(databaseCapability.isSupportsTransactionControl(), () -> new MCPUnsupportedException("Transaction control is not supported."));
+        ShardingSpherePreconditions.checkState(databaseCapability.supportsTransactionControl(), () -> new MCPUnsupportedException("Transaction control is not supported."));
         sessionManager.getTransactionResourceManager().commitTransaction(sessionId);
         return SQLExecutionResponse.statementAck(classifyTransactionStatement("COMMIT"), "COMMIT", "Transaction committed.");
     }
     
     private SQLExecutionResponse executeRollback(final String sessionId, final MCPDatabaseCapability databaseCapability) {
-        ShardingSpherePreconditions.checkState(databaseCapability.isSupportsTransactionControl(), () -> new MCPUnsupportedException("Transaction control is not supported."));
+        ShardingSpherePreconditions.checkState(databaseCapability.supportsTransactionControl(), () -> new MCPUnsupportedException("Transaction control is not supported."));
         sessionManager.getTransactionResourceManager().rollbackTransaction(sessionId);
         return SQLExecutionResponse.statementAck(classifyTransactionStatement("ROLLBACK"), "ROLLBACK", "Transaction rolled back.");
     }
     
     private SQLExecutionResponse executeSavepoint(final String sessionId, final MCPDatabaseCapability databaseCapability, final String savepointName) {
-        ShardingSpherePreconditions.checkState(databaseCapability.isSupportsSavepoint(), () -> new MCPUnsupportedException("Savepoint is not supported."));
+        ShardingSpherePreconditions.checkState(databaseCapability.supportsSavepoint(), () -> new MCPUnsupportedException("Savepoint is not supported."));
         sessionManager.getTransactionResourceManager().createSavepoint(sessionId, savepointName);
         return SQLExecutionResponse.statementAck(classifyTransactionStatement("SAVEPOINT"), "SAVEPOINT", "Savepoint created.");
     }
     
     private SQLExecutionResponse executeRollbackSavepoint(final String sessionId, final MCPDatabaseCapability databaseCapability, final String savepointName) {
-        ShardingSpherePreconditions.checkState(databaseCapability.isSupportsSavepoint(), () -> new MCPUnsupportedException("Savepoint is not supported."));
+        ShardingSpherePreconditions.checkState(databaseCapability.supportsSavepoint(), () -> new MCPUnsupportedException("Savepoint is not supported."));
         sessionManager.getTransactionResourceManager().rollbackToSavepoint(sessionId, savepointName);
         return SQLExecutionResponse.statementAck(classifyTransactionStatement("ROLLBACK TO SAVEPOINT"), "ROLLBACK TO SAVEPOINT", "Savepoint rolled back.");
     }
     
     private SQLExecutionResponse executeReleaseSavepoint(final String sessionId, final MCPDatabaseCapability databaseCapability, final String savepointName) {
-        ShardingSpherePreconditions.checkState(databaseCapability.isSupportsSavepoint(), () -> new MCPUnsupportedException("Savepoint is not supported."));
+        ShardingSpherePreconditions.checkState(databaseCapability.supportsSavepoint(), () -> new MCPUnsupportedException("Savepoint is not supported."));
         sessionManager.getTransactionResourceManager().releaseSavepoint(sessionId, savepointName);
         return SQLExecutionResponse.statementAck(classifyTransactionStatement("RELEASE SAVEPOINT"), "RELEASE SAVEPOINT", "Savepoint released.");
     }
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/MetadataSearchCollector.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/MetadataSearchCollector.java
index 140a5bf8f14..0595ec0c78a 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/MetadataSearchCollector.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/MetadataSearchCollector.java
@@ -34,6 +34,7 @@ import org.apache.shardingsphere.mcp.support.database.spi.MCPMetadataQueryFacade
 
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -58,40 +59,41 @@ final class MetadataSearchCollector {
     private List<MetadataSearchHit> collect(final String databaseName, final String schemaName, final Set<SupportedMCPMetadataObjectType> searchObjectTypes) {
         List<MetadataSearchHit> result = new LinkedList<>();
         for (SupportedMCPMetadataObjectType each : searchObjectTypes) {
-            if (SupportedMCPMetadataObjectType.DATABASE == each) {
-                metadataQueryFacade.queryDatabase(databaseName).ifPresent(optional -> result.add(createSearchHit(optional)));
-                continue;
-            }
-            if (SupportedMCPMetadataObjectType.STORAGE_UNIT == each) {
-                result.addAll(queryStorageUnitSearchHits(databaseName));
-                continue;
-            }
-            if (metadataQueryFacade.isSupportedMetadataObjectType(databaseName, each)) {
-                result.addAll(querySearchHits(databaseName, each, schemaName));
-            }
+            result.addAll(collect(databaseName, schemaName, each));
         }
         return result;
     }
     
+    private List<MetadataSearchHit> collect(final String databaseName, final String schemaName, final SupportedMCPMetadataObjectType objectType) {
+        return switch (objectType) {
+            case DATABASE -> metadataQueryFacade.queryDatabase(databaseName).map(each -> List.of(createSearchHit(each))).orElseGet(List::of);
+            case STORAGE_UNIT -> queryStorageUnitSearchHits(databaseName);
+            case SCHEMA, TABLE, VIEW, COLUMN, INDEX, SEQUENCE -> metadataQueryFacade.isSupportedMetadataObjectType(databaseName, objectType)
+                    ? querySearchHits(databaseName, objectType, schemaName)
+                    : List.of();
+            case MATERIALIZED_VIEW, ROUTINE, TRIGGER, EVENT, SYNONYM, DATABASE_SPECIFIC -> List.of();
+        };
+    }
+    
     List<MetadataSearchHit> collectDatabases() {
         return metadataQueryFacade.queryDatabases().stream().map(this::createSearchHit).toList();
     }
     
     private List<MetadataSearchHit> querySearchHits(final String databaseName, final SupportedMCPMetadataObjectType objectType, final String schemaName) {
+        return switch (objectType) {
+            case SCHEMA -> querySchemaSearchHits(databaseName, schemaName);
+            case TABLE -> queryTables(databaseName, schemaName).stream().map(each -> createTableSearchHit(databaseName, each)).toList();
+            case VIEW -> queryViews(databaseName, schemaName).stream().map(each -> createViewSearchHit(databaseName, each)).toList();
+            case COLUMN -> queryColumnSearchHits(databaseName, schemaName);
+            case INDEX -> queryIndexSearchHits(databaseName, schemaName);
+            case SEQUENCE -> querySequenceSearchHits(databaseName, schemaName);
+            case DATABASE, STORAGE_UNIT, MATERIALIZED_VIEW, ROUTINE, TRIGGER, EVENT, SYNONYM, DATABASE_SPECIFIC -> List.of();
+        };
+    }
+    
+    private List<MetadataSearchHit> querySchemaSearchHits(final String databaseName, final String schemaName) {
         List<MetadataSearchHit> result = new LinkedList<>();
-        if (SupportedMCPMetadataObjectType.SCHEMA == objectType) {
-            addSchemaSearchHits(result, databaseName, schemaName);
-        } else if (SupportedMCPMetadataObjectType.TABLE == objectType) {
-            queryTables(databaseName, schemaName).forEach(each -> result.add(createTableSearchHit(databaseName, each)));
-        } else if (SupportedMCPMetadataObjectType.VIEW == objectType) {
-            queryViews(databaseName, schemaName).forEach(each -> result.add(createViewSearchHit(databaseName, each)));
-        } else if (SupportedMCPMetadataObjectType.COLUMN == objectType) {
-            result.addAll(queryColumnSearchHits(databaseName, schemaName));
-        } else if (SupportedMCPMetadataObjectType.INDEX == objectType) {
-            result.addAll(queryIndexSearchHits(databaseName, schemaName));
-        } else {
-            result.addAll(querySequenceSearchHits(databaseName, schemaName));
-        }
+        addSchemaSearchHits(result, databaseName, schemaName);
         return result;
     }
     
@@ -160,44 +162,59 @@ final class MetadataSearchCollector {
         for (Map<String, Object> each : governanceMetadataQueryService.queryStorageUnits(queryFacade, databaseName)) {
             String storageUnitName = Objects.toString(each.get("name"), "");
             if (!storageUnitName.isEmpty()) {
-                result.add(createSearchHit(databaseName, "", "storage_unit", "", "", storageUnitName));
+                result.add(createSearchHit(databaseName, "", SupportedMCPMetadataObjectType.STORAGE_UNIT, "", "", storageUnitName));
             }
         }
         return result;
     }
     
     private MetadataSearchHit createSearchHit(final RuntimeDatabaseProfile databaseProfile) {
-        return createSearchHit(databaseProfile.getDatabase(), "", "database", "", "", databaseProfile.getDatabase());
+        return createSearchHit(databaseProfile.getDatabase(), "", SupportedMCPMetadataObjectType.DATABASE, "", "", databaseProfile.getDatabase());
     }
     
     private MetadataSearchHit createSearchHit(final String database, final ShardingSphereSchema schema) {
-        return createSearchHit(database, schema.getName(), "schema", "", "", schema.getName());
+        return createSearchHit(database, schema.getName(), SupportedMCPMetadataObjectType.SCHEMA, "", "", schema.getName());
     }
     
     private MetadataSearchHit createSearchHit(final String database, final String schema, final ShardingSphereSequence sequence) {
-        return createSearchHit(database, schema, "sequence", "", "", sequence.getName());
+        return createSearchHit(database, schema, SupportedMCPMetadataObjectType.SEQUENCE, "", "", sequence.getName());
     }
     
-    private MetadataSearchHit createSearchHit(final String database, final String schema, final String objectType, final String table, final String view, final String name) {
+    private MetadataSearchHit createSearchHit(final String database, final String schema, final SupportedMCPMetadataObjectType objectType,
+                                              final String table, final String view, final String name) {
         MetadataSearchResourceUriFactory.MetadataResourceUris resourceUris = resourceUriFactory.create(database, schema, objectType, table, view, name);
-        return new MetadataSearchHit(database, schema, objectType, table, view, name, resourceUris.resource(), resourceUris.parentResource(), resourceUris.nextResources(),
-                resourceUris.derivationStatus(), resourceUris.derivationReason(), "", List.of(), "");
+        return MetadataSearchHit.builder()
+                .database(database)
+                .schema(schema)
+                .objectType(objectType.name().toLowerCase(Locale.ENGLISH))
+                .table(table)
+                .view(view)
+                .name(name)
+                .resource(resourceUris.resource())
+                .parentResource(resourceUris.parentResource())
+                .nextResources(resourceUris.nextResources())
+                .derivationStatus(resourceUris.derivationStatus())
+                .derivationReason(resourceUris.derivationReason())
+                .matchKind("")
+                .matchedFields(List.of())
+                .matchedValue("")
+                .build();
     }
     
     private MetadataSearchHit createTableSearchHit(final String database, final TableSearchScope table) {
-        return createSearchHit(database, table.schema, "table", table.table.getName(), "", table.table.getName());
+        return createSearchHit(database, table.schema, SupportedMCPMetadataObjectType.TABLE, table.table.getName(), "", table.table.getName());
     }
     
     private MetadataSearchHit createViewSearchHit(final String database, final TableSearchScope view) {
-        return createSearchHit(database, view.schema, "view", "", view.table.getName(), view.table.getName());
+        return createSearchHit(database, view.schema, SupportedMCPMetadataObjectType.VIEW, "", view.table.getName(), view.table.getName());
     }
     
     private MetadataSearchHit createColumnSearchHit(final String database, final String schema, final String table, final String view, final ShardingSphereColumn column) {
-        return createSearchHit(database, schema, "column", table, view, column.getName());
+        return createSearchHit(database, schema, SupportedMCPMetadataObjectType.COLUMN, table, view, column.getName());
     }
     
     private MetadataSearchHit createIndexSearchHit(final String database, final String schema, final String table, final ShardingSphereIndex index) {
-        return createSearchHit(database, schema, "index", table, "", index.getName());
+        return createSearchHit(database, schema, SupportedMCPMetadataObjectType.INDEX, table, "", index.getName());
     }
     
     @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/MetadataSearchResourceUriFactory.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/MetadataSearchResourceUriFactory.java
index fe24614fa3a..3175a3927e6 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/MetadataSearchResourceUriFactory.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/MetadataSearchResourceUriFactory.java
@@ -17,6 +17,7 @@
 
 package org.apache.shardingsphere.mcp.core.tool.handler.metadata;
 
+import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPMetadataObjectType;
 import org.apache.shardingsphere.mcp.support.protocol.MCPPayloadFieldNames;
 import org.apache.shardingsphere.mcp.support.protocol.MCPResourceHintUtils;
 import org.apache.shardingsphere.mcp.support.resource.MCPUriPathSegmentUtils;
@@ -29,31 +30,20 @@ final class MetadataSearchResourceUriFactory {
     
     private static final String DATABASES_RESOURCE_URI = "shardingsphere://databases";
     
-    MetadataResourceUris create(final String database, final String schema, final String objectType, final String table, final String view, final String name) {
-        if ("database".equals(objectType)) {
-            return createDatabaseResourceUris(database);
-        }
-        if ("schema".equals(objectType)) {
-            return createSchemaResourceUris(database, schema);
-        }
-        if ("table".equals(objectType)) {
-            return createTableResourceUris(database, schema, table);
-        }
-        if ("view".equals(objectType)) {
-            return createViewResourceUris(database, schema, view);
-        }
-        if ("column".equals(objectType)) {
-            return createColumnResourceUris(database, schema, table, view, name);
-        }
-        if ("index".equals(objectType)) {
-            return createIndexResourceUris(database, schema, table, name);
-        }
-        if ("storage_unit".equals(objectType)) {
-            return createStorageUnitResourceUris(database, name);
-        }
-        return "sequence".equals(objectType)
-                ? createSequenceResourceUris(database, schema, name)
-                : notSafe("Metadata hit object type is not backed by a descriptor resource pattern.");
+    MetadataResourceUris create(final String database, final String schema, final SupportedMCPMetadataObjectType objectType,
+                                final String table, final String view, final String name) {
+        return switch (objectType) {
+            case DATABASE -> createDatabaseResourceUris(database);
+            case SCHEMA -> createSchemaResourceUris(database, schema);
+            case TABLE -> createTableResourceUris(database, schema, table);
+            case VIEW -> createViewResourceUris(database, schema, view);
+            case COLUMN -> createColumnResourceUris(database, schema, table, view, name);
+            case INDEX -> createIndexResourceUris(database, schema, table, name);
+            case STORAGE_UNIT -> createStorageUnitResourceUris(database, name);
+            case SEQUENCE -> createSequenceResourceUris(database, schema, name);
+            case MATERIALIZED_VIEW, ROUTINE, TRIGGER, EVENT, SYNONYM, DATABASE_SPECIFIC ->
+                    notSafe("Metadata hit object type is not backed by a descriptor resource pattern.");
+        };
     }
     
     private MetadataResourceUris createDatabaseResourceUris(final String database) {
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/workflow/WorkflowExecutionToolHandler.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/workflow/WorkflowExecutionToolHandler.java
index 831ab6ac8e4..787dc2061a3 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/workflow/WorkflowExecutionToolHandler.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/workflow/WorkflowExecutionToolHandler.java
@@ -20,7 +20,6 @@ package org.apache.shardingsphere.mcp.core.tool.handler.workflow;
 import org.apache.shardingsphere.mcp.api.protocol.response.MCPResponse;
 import org.apache.shardingsphere.mcp.api.tool.MCPToolCall;
 import org.apache.shardingsphere.mcp.core.protocol.exception.MCPExecutionModeRequiredException;
-import org.apache.shardingsphere.mcp.core.protocol.exception.MCPWorkflowStateException;
 import org.apache.shardingsphere.mcp.core.tool.request.MCPToolArguments;
 import org.apache.shardingsphere.mcp.core.workflow.WorkflowExecutionService;
 import org.apache.shardingsphere.mcp.core.workflow.WorkflowRuntimeDefinitionRegistry;
@@ -33,6 +32,7 @@ import org.apache.shardingsphere.mcp.support.workflow.descriptor.WorkflowToolDes
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowFieldNames;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowKind;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.spi.WorkflowRuntimeDefinition;
 
 import java.util.LinkedHashMap;
@@ -68,30 +68,24 @@ public final class WorkflowExecutionToolHandler implements MCPToolHandler<MCPWor
         MCPToolArguments toolArguments = new MCPToolArguments(toolCall.getArguments());
         String executionMode = toolArguments.getStringArgument(WorkflowFieldNames.EXECUTION_MODE);
         if (executionMode.isEmpty()) {
-            throw new MCPExecutionModeRequiredException(WorkflowToolDescriptors.APPLY_TOOL_NAME, List.of("preview", "review-then-execute", "manual-only"),
+            throw new MCPExecutionModeRequiredException(WorkflowToolDescriptors.APPLY_TOOL_NAME, List.of(WorkflowLifecycle.EXECUTION_MODE_PREVIEW,
+                    WorkflowLifecycle.EXECUTION_MODE_REVIEW_THEN_EXECUTE, WorkflowLifecycle.EXECUTION_MODE_MANUAL_ONLY),
                     createPreviewSuggestedArguments(toolCall.getArguments()));
         }
         MCPDatabaseHandlerContext databaseContext = workflowContext.getDatabaseContext();
         WorkflowContextSnapshot snapshot = WorkflowSessionSnapshotResolver.getRequired(workflowContext.getWorkflowSessionContext(), toolCall.getSessionId(),
                 toolArguments.getStringArgument(WorkflowFieldNames.PLAN_ID));
-        WorkflowKind workflowKind = getRequiredWorkflowKind(snapshot);
+        WorkflowKind workflowKind = WorkflowSessionSnapshotResolver.getRequiredWorkflowKind(snapshot);
         WorkflowRuntimeDefinition workflowRuntimeDefinition = workflowRuntimeDefinitionRegistry.getRequired(workflowKind);
         return new MCPMapResponse(executionService.apply(workflowContext.getWorkflowSessionContext(), databaseContext.getMetadataQueryFacade(), databaseContext.getQueryFacade(),
                 databaseContext.getExecutionFacade(), workflowRuntimeDefinition.getApplySynchronizationHandler(), workflowRuntimeDefinition.getApplyArtifactValidator(), toolCall.getSessionId(),
                 snapshot, toolArguments.getStringCollectionArgument(WorkflowFieldNames.APPROVED_STEPS), executionMode));
     }
     
-    private WorkflowKind getRequiredWorkflowKind(final WorkflowContextSnapshot snapshot) {
-        if (null != snapshot.getWorkflowKind()) {
-            return snapshot.getWorkflowKind();
-        }
-        throw new MCPWorkflowStateException(String.format("Workflow kind is required for plan_id `%s`.", snapshot.getPlanId()), snapshot.getPlanId());
-    }
-    
     private static Map<String, Object> createPreviewSuggestedArguments(final Map<String, Object> arguments) {
         Map<String, Object> result = new LinkedHashMap<>(arguments);
         result.remove(WorkflowFieldNames.EXECUTION_MODE);
-        result.put(WorkflowFieldNames.EXECUTION_MODE, "preview");
+        result.put(WorkflowFieldNames.EXECUTION_MODE, WorkflowLifecycle.EXECUTION_MODE_PREVIEW);
         return result;
     }
 }
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/workflow/WorkflowValidationToolHandler.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/workflow/WorkflowValidationToolHandler.java
index a2fc03b73b0..9c069ef7e8c 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/workflow/WorkflowValidationToolHandler.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/workflow/WorkflowValidationToolHandler.java
@@ -20,7 +20,6 @@ package org.apache.shardingsphere.mcp.core.tool.handler.workflow;
 import lombok.RequiredArgsConstructor;
 import org.apache.shardingsphere.mcp.api.protocol.response.MCPResponse;
 import org.apache.shardingsphere.mcp.api.tool.MCPToolCall;
-import org.apache.shardingsphere.mcp.core.protocol.exception.MCPWorkflowStateException;
 import org.apache.shardingsphere.mcp.core.tool.request.MCPToolArguments;
 import org.apache.shardingsphere.mcp.core.workflow.WorkflowRuntimeDefinitionRegistry;
 import org.apache.shardingsphere.mcp.core.workflow.WorkflowSessionSnapshotResolver;
@@ -57,15 +56,9 @@ public final class WorkflowValidationToolHandler implements MCPToolHandler<MCPWo
         MCPDatabaseHandlerContext databaseContext = workflowContext.getDatabaseContext();
         WorkflowContextSnapshot snapshot = WorkflowSessionSnapshotResolver.getRequired(workflowContext.getWorkflowSessionContext(), toolCall.getSessionId(),
                 toolArguments.getStringArgument(WorkflowFieldNames.PLAN_ID));
-        WorkflowKind workflowKind = getRequiredWorkflowKind(snapshot);
+        WorkflowKind workflowKind = WorkflowSessionSnapshotResolver.getRequiredWorkflowKind(snapshot);
         return new MCPMapResponse(workflowRuntimeDefinitionRegistry.getRequired(workflowKind).getValidationHandler().validate(workflowContext.getWorkflowSessionContext(),
                 databaseContext.getMetadataQueryFacade(), databaseContext.getQueryFacade(), databaseContext.getExecutionFacade(), toolCall.getSessionId(), snapshot));
     }
     
-    private WorkflowKind getRequiredWorkflowKind(final WorkflowContextSnapshot snapshot) {
-        if (null != snapshot.getWorkflowKind()) {
-            return snapshot.getWorkflowKind();
-        }
-        throw new MCPWorkflowStateException(String.format("Workflow kind is required for plan_id `%s`.", snapshot.getPlanId()), snapshot.getPlanId());
-    }
 }
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/response/MetadataSearchHit.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/response/MetadataSearchHit.java
index 4c996b18a29..3ef328c02d1 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/response/MetadataSearchHit.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/response/MetadataSearchHit.java
@@ -20,8 +20,10 @@ package org.apache.shardingsphere.mcp.core.tool.response;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
 import org.apache.shardingsphere.mcp.support.protocol.MCPPayloadFieldNames;
 
 import java.util.List;
@@ -30,7 +32,8 @@ import java.util.Map;
 /**
  * Search hit for metadata discovery.
  */
-@RequiredArgsConstructor
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Builder(toBuilder = true)
 @Getter
 public final class MetadataSearchHit {
     
@@ -85,7 +88,6 @@ public final class MetadataSearchHit {
      * @return search hit with match explanation
      */
     public MetadataSearchHit withMatch(final String matchKind, final List<String> matchedFields, final String matchedValue) {
-        return new MetadataSearchHit(database, schema, objectType, table, view, name, resource, parentResource, nextResources, derivationStatus, derivationReason,
-                matchKind, matchedFields, matchedValue);
+        return toBuilder().matchKind(matchKind).matchedFields(matchedFields).matchedValue(matchedValue).build();
     }
 }
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowApplyOutcome.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowApplyOutcome.java
index b2e1c530d38..6ca448619ab 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowApplyOutcome.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowApplyOutcome.java
@@ -59,7 +59,7 @@ public final class WorkflowApplyOutcome {
     
     void addFailedArtifact(final String issueCode, final String artifactType, final String artifactSql, final String errorMessage) {
         stepResults.add(createStepResult(artifactType, WorkflowLifecycle.STATUS_FAILED, artifactSql));
-        issues.add(new WorkflowIssue(issueCode, "error", "executing", errorMessage, "Fix the failed artifact and retry execution.", true,
+        issues.add(new WorkflowIssue(issueCode, "error", WorkflowLifecycle.STEP_EXECUTING, errorMessage, "Fix the failed artifact and retry execution.", true,
                 Map.of("artifact_type", artifactType, "sql", artifactSql)).toMap());
     }
     
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowApplyResponseBuilder.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowApplyResponseBuilder.java
index f7b35662fad..3378c337c24 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowApplyResponseBuilder.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowApplyResponseBuilder.java
@@ -39,9 +39,7 @@ import java.util.Map;
  */
 public final class WorkflowApplyResponseBuilder {
     
-    private static final String EXECUTION_MODE_PREVIEW = "preview";
-    
-    private static final String EXECUTION_MODE_MANUAL_ONLY = "manual-only";
+    private static final String STATUS_PREVIEW = "preview";
     
     /**
      * Build workflow apply response payload.
@@ -87,6 +85,19 @@ public final class WorkflowApplyResponseBuilder {
         return result;
     }
     
+    /**
+     * Build failed workflow apply response payload.
+     *
+     * @param snapshot workflow snapshot
+     * @param executionMode execution mode
+     * @param issues workflow issues
+     * @return failed workflow apply response payload
+     */
+    Map<String, Object> buildFailureResponse(final WorkflowContextSnapshot snapshot, final String executionMode,
+                                             final Collection<Map<String, Object>> issues) {
+        return build(snapshot, WorkflowLifecycle.STATUS_FAILED, executionMode, issues, List.of(), List.of(), List.of(), List.of(), Map.of());
+    }
+    
     /**
      * Build workflow preview response payload.
      *
@@ -99,7 +110,7 @@ public final class WorkflowApplyResponseBuilder {
     public Map<String, Object> buildPreviewResponse(final WorkflowContextSnapshot snapshot, final Collection<WorkflowArtifactBundle.ExecutableWorkflowArtifact> executableArtifacts,
                                                     final String applyExecutionMode, final Map<String, Object> manualArtifactPackage) {
         List<Map<String, Object>> previewArtifacts = createPreviewArtifacts(executableArtifacts);
-        Map<String, Object> result = build(snapshot, EXECUTION_MODE_PREVIEW, EXECUTION_MODE_PREVIEW,
+        Map<String, Object> result = build(snapshot, STATUS_PREVIEW, WorkflowLifecycle.EXECUTION_MODE_PREVIEW,
                 List.of(), List.of(), List.of(), List.of(), List.of(), manualArtifactPackage);
         result.put("would_apply", false);
         result.put("preview_artifacts", previewArtifacts);
@@ -113,10 +124,10 @@ public final class WorkflowApplyResponseBuilder {
     }
     
     private String resolveResponseMode(final String status, final String executionMode) {
-        if (EXECUTION_MODE_PREVIEW.equals(executionMode)) {
+        if (WorkflowLifecycle.EXECUTION_MODE_PREVIEW.equals(executionMode)) {
             return MCPResponseMode.PREVIEW;
         }
-        if (EXECUTION_MODE_MANUAL_ONLY.equals(executionMode)) {
+        if (WorkflowLifecycle.EXECUTION_MODE_MANUAL_ONLY.equals(executionMode)) {
             return MCPResponseMode.MANUAL_ONLY;
         }
         return WorkflowLifecycle.STATUS_COMPLETED.equals(status) ? MCPResponseMode.EXECUTED : MCPResponseMode.TERMINAL;
@@ -124,7 +135,7 @@ public final class WorkflowApplyResponseBuilder {
     
     private String createSummary(final String planId, final String status, final String executionMode, final Collection<Map<String, Object>> issues,
                                  final Collection<String> executedDdl, final Collection<String> executedDistSql) {
-        if (EXECUTION_MODE_PREVIEW.equals(executionMode)) {
+        if (WorkflowLifecycle.EXECUTION_MODE_PREVIEW.equals(executionMode)) {
             return String.format("Workflow apply preview is ready for plan `%s`.", planId);
         }
         if (WorkflowLifecycle.STATUS_COMPLETED.equals(status)) {
@@ -156,7 +167,7 @@ public final class WorkflowApplyResponseBuilder {
         Map<String, Object> result = new LinkedHashMap<>(5, 1F);
         result.put("artifact_categories", previewArtifacts.stream().map(each -> (String) each.get("artifact_type")).distinct().toList());
         result.put("side_effect_scope", previewArtifacts.stream().map(each -> (String) each.get("side_effect_scope")).distinct().toList());
-        boolean manualOnly = EXECUTION_MODE_MANUAL_ONLY.equals(applyExecutionMode);
+        boolean manualOnly = WorkflowLifecycle.EXECUTION_MODE_MANUAL_ONLY.equals(applyExecutionMode);
         result.put("manual_only", manualOnly);
         if (!manualOnly && !previewArtifacts.isEmpty()) {
             result.put("approval_field", WorkflowFieldNames.APPROVED_STEPS);
@@ -186,7 +197,7 @@ public final class WorkflowApplyResponseBuilder {
         if (previewArtifacts.isEmpty()) {
             return MCPNextActionUtils.ordered(MCPNextActionUtils.stop("Preview has no artifacts to approve."));
         }
-        if (EXECUTION_MODE_MANUAL_ONLY.equals(applyExecutionMode)) {
+        if (WorkflowLifecycle.EXECUTION_MODE_MANUAL_ONLY.equals(applyExecutionMode)) {
             return MCPNextActionUtils.ordered(MCPNextActionUtils.callTool(WorkflowToolDescriptors.APPLY_TOOL_NAME,
                     createPreviewNextActionReason(applyExecutionMode), createExecutionArguments(snapshot, applyExecutionMode)));
         }
@@ -198,7 +209,7 @@ public final class WorkflowApplyResponseBuilder {
     }
     
     private String createPreviewNextActionReason(final String applyExecutionMode) {
-        return EXECUTION_MODE_MANUAL_ONLY.equals(applyExecutionMode)
+        return WorkflowLifecycle.EXECUTION_MODE_MANUAL_ONLY.equals(applyExecutionMode)
                 ? "Export reviewed workflow artifacts without applying runtime side effects."
                 : "Confirm the preview_artifacts.approval_step values to approve before execution.";
     }
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionService.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionService.java
index d2a7b2b56be..45dd484de34 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionService.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionService.java
@@ -54,13 +54,8 @@ import java.util.Map;
  */
 public final class WorkflowExecutionService {
     
-    private static final String EXECUTION_MODE_PREVIEW = "preview";
-    
-    private static final String EXECUTION_MODE_REVIEW_THEN_EXECUTE = "review-then-execute";
-    
-    private static final String EXECUTION_MODE_MANUAL_ONLY = "manual-only";
-    
-    private static final List<String> EXECUTION_MODES = List.of(EXECUTION_MODE_PREVIEW, EXECUTION_MODE_REVIEW_THEN_EXECUTE, EXECUTION_MODE_MANUAL_ONLY);
+    private static final List<String> EXECUTION_MODES = List.of(WorkflowLifecycle.EXECUTION_MODE_PREVIEW,
+            WorkflowLifecycle.EXECUTION_MODE_REVIEW_THEN_EXECUTE, WorkflowLifecycle.EXECUTION_MODE_MANUAL_ONLY);
     
     private static final List<String> ALLOWED_APPROVAL_STEPS = List.of(
             WorkflowArtifactPayloadUtils.STEP_DDL, WorkflowArtifactPayloadUtils.STEP_INDEX_DDL, WorkflowArtifactPayloadUtils.STEP_RULE_DISTSQL);
@@ -95,7 +90,7 @@ public final class WorkflowExecutionService {
             return invalidArtifactResponse;
         }
         WorkflowApplyOutcome applyOutcome = new WorkflowApplyOutcome();
-        if (EXECUTION_MODE_PREVIEW.equalsIgnoreCase(actualExecutionMode)) {
+        if (WorkflowLifecycle.EXECUTION_MODE_PREVIEW.equalsIgnoreCase(actualExecutionMode)) {
             return previewApply(workflowSessionContext, snapshot);
         }
         if (isManualOnly(actualExecutionMode)) {
@@ -142,17 +137,17 @@ public final class WorkflowExecutionService {
                     String.format("Workflow status `%s` cannot enter apply in the current lifecycle.", snapshot.getStatus()),
                     "Plan the workflow again or continue from a reviewable status.");
         }
-        if (EXECUTION_MODE_REVIEW_THEN_EXECUTE.equals(executionMode) && !WorkflowLifecycle.STATUS_PREVIEWED.equalsIgnoreCase(snapshot.getStatus())) {
+        if (WorkflowLifecycle.EXECUTION_MODE_REVIEW_THEN_EXECUTE.equals(executionMode) && !WorkflowLifecycle.STATUS_PREVIEWED.equalsIgnoreCase(snapshot.getStatus())) {
             return createRejectedResponse(snapshot, executionMode, WorkflowIssueCode.WORKFLOW_STATUS_INVALID,
                     "Automatic workflow execution requires an execution_mode=preview call first.",
                     "Call database_gateway_apply_workflow with execution_mode=preview, review preview_artifacts, then pass explicit approved_steps.");
         }
-        if (EXECUTION_MODE_REVIEW_THEN_EXECUTE.equals(executionMode) && (null == approvedSteps || approvedSteps.isEmpty())) {
+        if (WorkflowLifecycle.EXECUTION_MODE_REVIEW_THEN_EXECUTE.equals(executionMode) && (null == approvedSteps || approvedSteps.isEmpty())) {
             return createRejectedResponse(snapshot, executionMode, WorkflowIssueCode.WORKFLOW_STATUS_INVALID,
                     "Automatic workflow execution requires explicit approved_steps.",
                     "Pass the reviewed preview_artifacts.approval_step values in approved_steps.");
         }
-        if (EXECUTION_MODE_REVIEW_THEN_EXECUTE.equals(executionMode) && !areApprovedStepsVisible(snapshot, approvedSteps)) {
+        if (WorkflowLifecycle.EXECUTION_MODE_REVIEW_THEN_EXECUTE.equals(executionMode) && !areApprovedStepsVisible(snapshot, approvedSteps)) {
             return createRejectedResponse(snapshot, executionMode, WorkflowIssueCode.WORKFLOW_STATUS_INVALID,
                     "Automatic workflow execution received approved_steps that are not present in the previewed artifacts.",
                     "Pass only the preview_artifacts.approval_step values from the latest preview response.");
@@ -176,9 +171,8 @@ public final class WorkflowExecutionService {
     
     private Map<String, Object> createRejectedResponse(final WorkflowContextSnapshot snapshot, final String executionMode, final String issueCode, final String message,
                                                        final String userAction) {
-        return new WorkflowApplyResponseBuilder().build(snapshot, WorkflowLifecycle.STATUS_FAILED, executionMode,
-                List.of(new WorkflowIssue(issueCode, "error", WorkflowLifecycle.STEP_REVIEW, message, userAction, false, Map.of()).toMap()),
-                List.of(), List.of(), List.of(), List.of(), Map.of());
+        return new WorkflowApplyResponseBuilder().buildFailureResponse(snapshot, executionMode,
+                List.of(new WorkflowIssue(issueCode, "error", WorkflowLifecycle.STEP_REVIEW, message, userAction, false, Map.of()).toMap()));
     }
     
     private String resolveSnapshotExecutionMode(final WorkflowContextSnapshot snapshot) {
@@ -186,7 +180,7 @@ public final class WorkflowExecutionService {
     }
     
     private boolean isManualOnly(final String executionMode) {
-        return EXECUTION_MODE_MANUAL_ONLY.equalsIgnoreCase(executionMode);
+        return WorkflowLifecycle.EXECUTION_MODE_MANUAL_ONLY.equalsIgnoreCase(executionMode);
     }
     
     private boolean areApprovedStepsVisible(final WorkflowContextSnapshot snapshot, final List<String> approvedSteps) {
@@ -199,9 +193,8 @@ public final class WorkflowExecutionService {
         List<Map<String, Object>> validationIssues = workflowApplyArtifactValidator.validate(snapshot, createExecutableArtifacts(snapshot));
         if (!validationIssues.isEmpty()) {
             persistSnapshot(workflowSessionContext, snapshot, WorkflowLifecycle.STEP_FAILED, WorkflowLifecycle.STATUS_FAILED);
-            Map<String, Object> result = new WorkflowApplyResponseBuilder().build(snapshot, WorkflowLifecycle.STATUS_FAILED, executionMode, validationIssues,
-                    List.of(), List.of(), List.of(), List.of(), Map.of());
-            if (EXECUTION_MODE_PREVIEW.equalsIgnoreCase(executionMode)) {
+            Map<String, Object> result = new WorkflowApplyResponseBuilder().buildFailureResponse(snapshot, executionMode, validationIssues);
+            if (WorkflowLifecycle.EXECUTION_MODE_PREVIEW.equalsIgnoreCase(executionMode)) {
                 result.put("would_apply", false);
                 result.put("preview_artifacts", List.of());
             }
@@ -220,11 +213,13 @@ public final class WorkflowExecutionService {
     }
     
     private Map<String, Object> createPreviewSuggestedArguments(final WorkflowContextSnapshot snapshot) {
-        return Map.of(WorkflowFieldNames.PLAN_ID, snapshot.getPlanId(), WorkflowFieldNames.EXECUTION_MODE, EXECUTION_MODE_PREVIEW);
+        return Map.of(WorkflowFieldNames.PLAN_ID, snapshot.getPlanId(), WorkflowFieldNames.EXECUTION_MODE, WorkflowLifecycle.EXECUTION_MODE_PREVIEW);
     }
     
     private String resolveApplyExecutionMode(final WorkflowContextSnapshot snapshot) {
-        return EXECUTION_MODE_MANUAL_ONLY.equalsIgnoreCase(resolveSnapshotExecutionMode(snapshot)) ? EXECUTION_MODE_MANUAL_ONLY : EXECUTION_MODE_REVIEW_THEN_EXECUTE;
+        return WorkflowLifecycle.EXECUTION_MODE_MANUAL_ONLY.equalsIgnoreCase(resolveSnapshotExecutionMode(snapshot))
+                ? WorkflowLifecycle.EXECUTION_MODE_MANUAL_ONLY
+                : WorkflowLifecycle.EXECUTION_MODE_REVIEW_THEN_EXECUTE;
     }
     
     private Map<String, Object> applyManualOnly(final WorkflowSessionContext workflowSessionContext, final WorkflowContextSnapshot snapshot,
@@ -233,7 +228,8 @@ public final class WorkflowExecutionService {
         applyOutcome.addIssue(new WorkflowIssue(WorkflowIssueCode.MANUAL_EXECUTION_PENDING, "warning", WorkflowLifecycle.STEP_REVIEW,
                 "Artifacts are generated in manual-only mode and will not be executed automatically.",
                 "Execute artifacts manually and run validation afterwards.", true, Map.of()).toMap());
-        return applyOutcome.createResponse(WorkflowLifecycle.STATUS_AWAITING_MANUAL_EXECUTION, snapshot, "manual-only", createArtifactPayload(snapshot));
+        return applyOutcome.createResponse(WorkflowLifecycle.STATUS_AWAITING_MANUAL_EXECUTION, snapshot,
+                WorkflowLifecycle.EXECUTION_MODE_MANUAL_ONLY, createArtifactPayload(snapshot));
     }
     
     private Map<String, Object> applyAutomatically(final WorkflowSessionContext workflowSessionContext, final MCPMetadataQueryFacade metadataQueryFacade,
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowSessionSnapshotResolver.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowSessionSnapshotResolver.java
index 2363f865f8b..4ffc88ac392 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowSessionSnapshotResolver.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowSessionSnapshotResolver.java
@@ -19,9 +19,11 @@ package org.apache.shardingsphere.mcp.core.workflow;
 
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
+import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
 import org.apache.shardingsphere.mcp.core.protocol.exception.MCPWorkflowStateException;
 import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowKind;
 
 import java.util.Objects;
 
@@ -48,4 +50,17 @@ public final class WorkflowSessionSnapshotResolver {
         }
         return result;
     }
+    
+    /**
+     * Get required workflow kind.
+     *
+     * @param snapshot workflow snapshot
+     * @return workflow kind
+     * @throws MCPWorkflowStateException when the workflow kind is unavailable
+     */
+    public static WorkflowKind getRequiredWorkflowKind(final WorkflowContextSnapshot snapshot) {
+        ShardingSpherePreconditions.checkNotNull(snapshot.getWorkflowKind(),
+                () -> new MCPWorkflowStateException(String.format("Workflow kind is required for plan_id `%s`.", snapshot.getPlanId()), snapshot.getPlanId()));
+        return snapshot.getWorkflowKind();
+    }
 }
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/completion/MCPCompletionServiceTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/completion/MCPCompletionServiceTest.java
index b87e625318c..c4c1e700c64 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/completion/MCPCompletionServiceTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/completion/MCPCompletionServiceTest.java
@@ -17,6 +17,8 @@
 
 package org.apache.shardingsphere.mcp.core.completion;
 
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
+
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicyFactory;
 import org.apache.shardingsphere.infra.spi.ShardingSphereServiceLoader;
 import org.apache.shardingsphere.mcp.api.protocol.exception.MCPInvalidRequestException;
@@ -63,7 +65,7 @@ class MCPCompletionServiceTest {
         MCPCompletionResult actual = complete(new InMemoryWorkflowSessionContext(), createDescriptor("inspect_metadata", "database", 1), "database", "", new LinkedHashMap<>());
         assertThat(actual.getValues(), is(List.of("logic_db")));
         assertThat(actual.getTotal(), is(2));
-        assertTrue(actual.isHasMore());
+        assertTrue(actual.hasMore());
         assertThat(actual.getMeta().get(MCPShardingSphereMetadataKeys.DIAGNOSTIC), is("ok"));
         assertThat(actual.getMeta().get(MCPShardingSphereMetadataKeys.RETURNED_CANDIDATE_COUNT), is(1));
     }
@@ -106,7 +108,7 @@ class MCPCompletionServiceTest {
                 List.of(new SizedCompletionProvider()));
         assertThat(actual.getValues().size(), is(100));
         assertThat(actual.getTotal(), is(101));
-        assertTrue(actual.isHasMore());
+        assertTrue(actual.hasMore());
     }
     
     @Test
@@ -145,8 +147,8 @@ class MCPCompletionServiceTest {
     private MCPRuntimeContext createRuntimeContext(final WorkflowSessionContext workflowSessionContext) {
         MCPDatabaseCapabilityProvider databaseCapabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
         when(databaseCapabilityProvider.getDatabaseProfiles()).thenReturn(List.of(
-                new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet()),
-                new RuntimeDatabaseProfile("warehouse", "FixtureWarehouseDB", "2.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet())));
+                new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet()),
+                new RuntimeDatabaseProfile("warehouse", "FixtureWarehouseDB", "2.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet())));
         MCPRuntimeContext result = mock(MCPRuntimeContext.class, RETURNS_DEEP_STUBS);
         when(result.getDatabaseCapabilityProvider()).thenReturn(databaseCapabilityProvider);
         when(result.getWorkflowSessionContext()).thenReturn(workflowSessionContext);
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/completion/provider/MetadataCompletionProviderTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/completion/provider/MetadataCompletionProviderTest.java
index 48a44a21857..132ce8cc814 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/completion/provider/MetadataCompletionProviderTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/completion/provider/MetadataCompletionProviderTest.java
@@ -30,6 +30,7 @@ import org.apache.shardingsphere.mcp.support.completion.MCPCompletionProviderRes
 import org.apache.shardingsphere.mcp.support.completion.MCPCompletionRequestContext;
 import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
 import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseProfile;
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
 import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureCapabilityFacade;
 import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
 import org.apache.shardingsphere.mcp.support.database.spi.MCPMetadataQueryFacade;
@@ -261,11 +262,11 @@ class MetadataCompletionProviderTest {
     }
     
     private RuntimeDatabaseProfile createDatabaseProfile(final String database) {
-        return new RuntimeDatabaseProfile(database, "FixtureDB", "1.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet());
+        return new RuntimeDatabaseProfile(database, "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet());
     }
     
     private RuntimeDatabaseProfile createDatabaseMetadata() {
-        return new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet());
+        return new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet());
     }
     
     private ShardingSphereSchema createSchemaMetadata() {
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceHandlerTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceHandlerTest.java
index aa169fdbcd1..6986c266c6a 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceHandlerTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceHandlerTest.java
@@ -23,11 +23,11 @@ import org.apache.shardingsphere.mcp.api.resource.MCPUriVariables;
 import org.apache.shardingsphere.mcp.api.resource.descriptor.MCPResourceDescriptor;
 import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
 import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseProfile;
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
 import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureCapabilityFacade;
 import org.apache.shardingsphere.mcp.support.descriptor.MCPDescriptorCatalogIndex;
 import org.junit.jupiter.api.Test;
 
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -35,7 +35,6 @@ import java.util.Optional;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -70,27 +69,6 @@ class MetadataResourceHandlerTest {
         assertFalse((Boolean) actualPayload.get("truncated"));
     }
     
-    @Test
-    void assertHandleBroadListResourceGuidesSearch() {
-        MetadataResourceHandler handler = new MetadataResourceHandler("shardingsphere://databases", (requestContext, uriVariables) -> createDatabases(101));
-        MCPResponse actual = handler.handle(mock(MCPDatabaseHandlerContext.class), new MCPUriVariables(Map.of()));
-        Map<?, ?> actualPayload = actual.toPayload();
-        assertThat(((List<?>) actualPayload.get("items")).size(), is(100));
-        assertThat(actualPayload.get("count"), is(100));
-        assertThat(actualPayload.get("total_count"), is(101));
-        assertThat(actualPayload.get("returned_count"), is(100));
-        assertThat(actualPayload.get("summary"), is("Returned 100 of 101 logical-database metadata entries."));
-        assertTrue((Boolean) actualPayload.get("truncated"));
-        assertTrue((Boolean) actualPayload.get("has_more"));
-        assertThat(actualPayload.get("continuation_mode"), is("metadata_search"));
-        assertThat(((Map<?, ?>) actualPayload.get("large_result_guidance")).get("state"), is("broad_metadata_list"));
-        assertThat(((Map<?, ?>) actualPayload.get("large_result_guidance")).get("threshold"), is(100));
-        Map<?, ?> actualNextAction = (Map<?, ?>) ((List<?>) actualPayload.get("next_actions")).getFirst();
-        assertThat(actualNextAction.get("tool_name"), is("database_gateway_search_metadata"));
-        assertFalse(((Map<?, ?>) actualNextAction.get("arguments")).containsKey("page_size"));
-        assertThat(((Map<?, ?>) actualNextAction.get("arguments")).get("object_types"), is(List.of("database")));
-    }
-    
     @Test
     void assertHandleRootListResourceWithoutRuntimeDatabase() {
         MetadataResourceHandler handler = new MetadataResourceHandler("shardingsphere://databases", (requestContext, uriVariables) -> List.of());
@@ -115,7 +93,7 @@ class MetadataResourceHandlerTest {
     void assertHandleListResourceWithEmptyScope() {
         MetadataResourceHandler handler = new MetadataResourceHandler("shardingsphere://databases/{database}/schemas", (requestContext, uriVariables) -> List.of());
         MCPResponse actual = handler.handle(createDatabaseContext(Optional.of(
-                new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet()))),
+                new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet()))),
                 new MCPUriVariables(Map.of("database", "logic_db")));
         Map<?, ?> actualEmptyState = (Map<?, ?>) actual.toPayload().get("empty_state");
         assertThat(actualEmptyState.get("category"), is("empty_scope"));
@@ -127,7 +105,7 @@ class MetadataResourceHandlerTest {
     void assertHandleSchemaDetailResourceNotVisible() {
         MetadataResourceHandler handler = new MetadataResourceHandler("shardingsphere://databases/{database}/schemas/{schema}", (requestContext, uriVariables) -> List.of());
         MCPResponse actual = handler.handle(createDatabaseContext(Optional.of(
-                new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet()))),
+                new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet()))),
                 new MCPUriVariables(Map.of("database", "logic_db", "schema", "missing_schema")));
         Map<?, ?> actualEmptyState = (Map<?, ?>) actual.toPayload().get("empty_state");
         assertThat(actualEmptyState.get("category"), is("schema_not_visible"));
@@ -140,7 +118,7 @@ class MetadataResourceHandlerTest {
     void assertHandleObjectDetailResourceNotVisible() {
         MetadataResourceHandler handler = new MetadataResourceHandler("shardingsphere://databases/{database}/schemas/{schema}/tables/{table}", (requestContext, uriVariables) -> List.of());
         MCPResponse actual = handler.handle(createDatabaseContext(Optional.of(
-                new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet()))),
+                new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet()))),
                 new MCPUriVariables(Map.of("database", "logic_db", "schema", "public", "table", "missing_table")));
         Map<?, ?> actualEmptyState = (Map<?, ?>) actual.toPayload().get("empty_state");
         assertThat(actualEmptyState.get("category"), is("object_not_visible"));
@@ -148,49 +126,6 @@ class MetadataResourceHandlerTest {
         assertThat(((Map<?, ?>) actual.toPayload().get("recovery")).get("requested_token"), is("missing_table"));
     }
     
-    @Test
-    void assertHandleDetailResource() {
-        MetadataResourceHandler handler = new MetadataResourceHandler("shardingsphere://databases/{database}",
-                (requestContext, uriVariables) -> List.of(Map.of("database", uriVariables.getValue("database"))));
-        MCPUriVariables uriVariables = new MCPUriVariables(Map.of("database", "逻辑 库/2026?"));
-        MCPResponse actual = handler.handle(mock(MCPDatabaseHandlerContext.class), uriVariables);
-        Map<String, Object> actualPayload = actual.toPayload();
-        assertThat(actualPayload.get("response_mode"), is("detail"));
-        assertThat(actualPayload.get("summary"), is("Returned logical-database detail for this resource URI."));
-        assertThat(actualPayload.get("resource_kind"), is("detail"));
-        assertThat(actualPayload.get("object_scope"), is("logical-database"));
-        assertTrue((Boolean) actualPayload.get("found"));
-        assertThat(actualPayload.get("items"), is(List.of(Map.of("database", "逻辑 库/2026?"))));
-        assertThat(actualPayload.get("count"), is(1));
-        assertThat(actualPayload.get("item"), is(Map.of("database", "逻辑 库/2026?")));
-        String expectedSelfUri = "shardingsphere://databases/%E9%80%BB%E8%BE%91%20%E5%BA%93%2F2026%3F";
-        assertThat(actualPayload.get("self_uri"), is(expectedSelfUri));
-        assertThat(((Map<?, ?>) actualPayload.get("self_resource")).get("uri"), is(expectedSelfUri));
-        Map<?, ?> actualParentResource = (Map<?, ?>) actualPayload.get("parent_resource");
-        assertThat(actualParentResource.get("uri"), is("shardingsphere://databases"));
-        assertThat(actualParentResource.get("source_field"), is("parent_resource"));
-        List<?> actualNextResources = (List<?>) actualPayload.get("next_resources");
-        assertThat(actualNextResources.size(), is(4));
-        List<String> actualNextResourceUris = actualNextResources.stream().map(each -> (String) ((Map<?, ?>) each).get("uri")).toList();
-        assertThat(actualNextResourceUris, is(List.of(
-                expectedSelfUri + "/schemas",
-                expectedSelfUri + "/storage-units",
-                expectedSelfUri + "/single-tables",
-                expectedSelfUri + "/single-table/default-storage-unit")));
-    }
-    
-    @Test
-    void assertHandleMissingDetailResource() {
-        MetadataResourceHandler handler = new MetadataResourceHandler("shardingsphere://databases/{database}", (requestContext, uriVariables) -> List.of());
-        MCPResponse actual = handler.handle(mock(MCPDatabaseHandlerContext.class), mock(MCPUriVariables.class));
-        assertThat(actual.toPayload().get("summary"), is("No logical-database detail item matched this resource URI."));
-        assertFalse((Boolean) actual.toPayload().get("found"));
-        assertThat(((Map<?, ?>) actual.toPayload().get("empty_state")).get("reason"), is("logical-database detail resource was not found for this URI."));
-        assertThat(((Map<?, ?>) actual.toPayload().get("recovery")).get("recovery_category"), is("not_found"));
-        assertThat(((Map<?, ?>) actual.toPayload().get("recovery")).get("category"), is("not_found"));
-        assertThat(((Map<?, ?>) ((List<?>) actual.toPayload().get("next_actions")).getFirst()).get("type"), is("terminal"));
-    }
-    
     private MCPDatabaseHandlerContext createDatabaseContext(final Optional<RuntimeDatabaseProfile> databaseProfile) {
         MCPFeatureCapabilityFacade capabilityFacade = mock(MCPFeatureCapabilityFacade.class);
         when(capabilityFacade.findDatabaseProfile("logic_db")).thenReturn(databaseProfile);
@@ -200,11 +135,4 @@ class MetadataResourceHandlerTest {
         return result;
     }
     
-    private List<Map<String, String>> createDatabases(final int count) {
-        List<Map<String, String>> result = new LinkedList<>();
-        for (int i = 0; i < count; i++) {
-            result.add(Map.of("database", String.format("logic_db_%03d", i)));
-        }
-        return result;
-    }
 }
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourcePayloadMapperTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourcePayloadMapperTest.java
index fdf2505fe88..32e83722f53 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourcePayloadMapperTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourcePayloadMapperTest.java
@@ -17,6 +17,8 @@
 
 package org.apache.shardingsphere.mcp.core.resource.handler.metadata;
 
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
+
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicyFactory;
 import org.apache.shardingsphere.database.connector.core.metadata.database.enums.TableType;
 import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
@@ -48,7 +50,7 @@ class MetadataResourcePayloadMapperTest {
         when(metadataQueryFacade.querySchemas("logic_db")).thenReturn(List.of(createSchemaMetadata()));
         List<?> actual = new MetadataResourcePayloadMapper(metadataQueryFacade, new MCPUriVariables(Map.of()), true)
                 .map(createMetadata("logical-database"), List.of(
-                        new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet())));
+                        new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet())));
         Map<?, ?> actualDatabase = (Map<?, ?>) actual.getFirst();
         assertThat(actualDatabase.get("database"), is("logic_db"));
         assertThat(actualDatabase.get("databaseType"), is("FixtureDB"));
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceResponseFactoryTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceResponseFactoryTest.java
new file mode 100644
index 00000000000..89497a7bee4
--- /dev/null
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceResponseFactoryTest.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.core.resource.handler.metadata;
+
+import org.apache.shardingsphere.mcp.api.protocol.response.MCPResponse;
+import org.apache.shardingsphere.mcp.api.resource.MCPUriVariables;
+import org.apache.shardingsphere.mcp.api.resource.descriptor.MCPResourceDescriptor;
+import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
+import org.apache.shardingsphere.mcp.support.descriptor.MCPDescriptorCatalogIndex;
+import org.apache.shardingsphere.mcp.support.descriptor.ShardingSphereMCPResourceMetadata;
+import org.junit.jupiter.api.Test;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+
+class MetadataResourceResponseFactoryTest {
+    
+    @Test
+    void assertCreateBroadListResponseGuidesSearch() {
+        Map<String, Object> actual = createResponse("shardingsphere://databases", new MCPUriVariables(Map.of()), createDatabases(101)).toPayload();
+        assertThat(((List<?>) actual.get("items")).size(), is(100));
+        assertThat(actual.get("count"), is(100));
+        assertThat(actual.get("total_count"), is(101));
+        assertThat(actual.get("returned_count"), is(100));
+        assertThat(actual.get("summary"), is("Returned 100 of 101 logical-database metadata entries."));
+        assertTrue((Boolean) actual.get("truncated"));
+        assertTrue((Boolean) actual.get("has_more"));
+        assertThat(actual.get("continuation_mode"), is("metadata_search"));
+        assertThat(((Map<?, ?>) actual.get("large_result_guidance")).get("state"), is("broad_metadata_list"));
+        assertThat(((Map<?, ?>) actual.get("large_result_guidance")).get("threshold"), is(100));
+        Map<?, ?> nextAction = (Map<?, ?>) ((List<?>) actual.get("next_actions")).getFirst();
+        assertThat(nextAction.get("tool_name"), is("database_gateway_search_metadata"));
+        assertFalse(((Map<?, ?>) nextAction.get("arguments")).containsKey("page_size"));
+        assertThat(((Map<?, ?>) nextAction.get("arguments")).get("object_types"), is(List.of("database")));
+    }
+    
+    @Test
+    void assertCreateDetailResponse() {
+        MCPUriVariables uriVariables = new MCPUriVariables(Map.of("database", "逻辑 库/2026?"));
+        Map<String, Object> actual = createResponse("shardingsphere://databases/{database}", uriVariables,
+                List.of(Map.of("database", uriVariables.getValue("database")))).toPayload();
+        assertThat(actual.get("response_mode"), is("detail"));
+        assertThat(actual.get("summary"), is("Returned logical-database detail for this resource URI."));
+        assertThat(actual.get("resource_kind"), is("detail"));
+        assertThat(actual.get("object_scope"), is("logical-database"));
+        assertTrue((Boolean) actual.get("found"));
+        assertThat(actual.get("item"), is(Map.of("database", "逻辑 库/2026?")));
+        String expectedSelfUri = "shardingsphere://databases/%E9%80%BB%E8%BE%91%20%E5%BA%93%2F2026%3F";
+        assertThat(actual.get("self_uri"), is(expectedSelfUri));
+        assertThat(((Map<?, ?>) actual.get("parent_resource")).get("uri"), is("shardingsphere://databases"));
+        List<String> nextResourceUris = ((List<?>) actual.get("next_resources")).stream().map(each -> (String) ((Map<?, ?>) each).get("uri")).toList();
+        assertThat(nextResourceUris, is(List.of(
+                expectedSelfUri + "/schemas",
+                expectedSelfUri + "/storage-units",
+                expectedSelfUri + "/single-tables",
+                expectedSelfUri + "/single-table/default-storage-unit")));
+    }
+    
+    @Test
+    void assertCreateMissingDetailResponse() {
+        Map<String, Object> actual = createResponse("shardingsphere://databases/{database}", mock(MCPUriVariables.class), List.of()).toPayload();
+        assertThat(actual.get("summary"), is("No logical-database detail item matched this resource URI."));
+        assertFalse((Boolean) actual.get("found"));
+        assertThat(((Map<?, ?>) actual.get("empty_state")).get("reason"), is("logical-database detail resource was not found for this URI."));
+        assertThat(((Map<?, ?>) actual.get("recovery")).get("recovery_category"), is("not_found"));
+        assertThat(((Map<?, ?>) ((List<?>) actual.get("next_actions")).getFirst()).get("type"), is("terminal"));
+    }
+    
+    private MCPResponse createResponse(final String uriTemplate, final MCPUriVariables uriVariables, final List<?> items) {
+        MCPResourceDescriptor descriptor = MCPDescriptorCatalogIndex.getRequiredResourceDescriptor(uriTemplate);
+        ShardingSphereMCPResourceMetadata metadata = MCPDescriptorCatalogIndex.getRequiredShardingSphereResourceMetadata(uriTemplate);
+        return new MetadataResourceResponseFactory().create(mock(MCPDatabaseHandlerContext.class), uriVariables, descriptor, metadata, items);
+    }
+    
+    private List<Map<String, String>> createDatabases(final int count) {
+        List<Map<String, String>> result = new LinkedList<>();
+        for (int i = 0; i < count; i++) {
+            result.add(Map.of("database", String.format("logic_db_%03d", i)));
+        }
+        return result;
+    }
+}
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/uri/MCPUriPatternTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/uri/MCPUriPatternTest.java
index 4ebe8ee0cb2..b142972e231 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/uri/MCPUriPatternTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/uri/MCPUriPatternTest.java
@@ -94,7 +94,7 @@ class MCPUriPatternTest {
     
     @Test
     void assertIsOverlaps() {
-        boolean actual = new MCPUriPattern("shardingsphere://databases/{database}").isOverlaps(new MCPUriPattern("shardingsphere://databases/default_db"));
+        boolean actual = new MCPUriPattern("shardingsphere://databases/{database}").overlaps(new MCPUriPattern("shardingsphere://databases/default_db"));
         assertTrue(actual);
     }
     
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/MCPToolControllerTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/MCPToolControllerTest.java
index 1025acbf7d9..f24390f181a 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/MCPToolControllerTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/MCPToolControllerTest.java
@@ -73,7 +73,7 @@ class MCPToolControllerTest {
     @Test
     void assertHandleWithUnsupportedTool() {
         try (MockedStatic<ToolDefinitionRegistry> mocked = mockStatic(ToolDefinitionRegistry.class)) {
-            mocked.when(() -> ToolDefinitionRegistry.getToolDefinition("unsupported_tool")).thenThrow(UnsupportedToolException.class);
+            mocked.when(() -> ToolDefinitionRegistry.getToolDefinition("unsupported_tool")).thenThrow(new UnsupportedToolException("unsupported_tool"));
             UnsupportedToolException actual =
                     assertThrows(UnsupportedToolException.class, () -> new MCPToolController(ResourceTestDataFactory.createRuntimeContext()).handle("session-1", "unsupported_tool", Map.of()));
             assertThat(actual.getToolName(), is("unsupported_tool"));
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/ToolDefinitionRegistryTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/ToolDefinitionRegistryTest.java
index 904f0fa9bcd..f2fe4560a6a 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/ToolDefinitionRegistryTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/ToolDefinitionRegistryTest.java
@@ -253,13 +253,19 @@ class ToolDefinitionRegistryTest {
     private static MCPToolDescriptor createNestedFixtureToolDescriptor() {
         Map<String, Object> optionSchema = Map.of("type", "object", "properties", Map.of("mode", Map.of("type", "string")), "required", List.of(), "additionalProperties", false);
         return new MCPToolDescriptor("fixture_tool", "Fixture Tool", "Fixture tool.", Map.of("type", "object", "properties", Map.of("options", optionSchema), "required", List.of(),
-                "additionalProperties", false), Collections.emptyMap(), new MCPToolAnnotations("Fixture Tool", true, false, true, true), Collections.emptyMap());
+                "additionalProperties", false), Collections.emptyMap(),
+                MCPToolAnnotations.builder()
+                        .title("Fixture Tool").readOnlyHint(true).destructiveHint(false).idempotentHint(true).openWorldHint(true).build(),
+                Collections.emptyMap());
     }
     
     private static MCPToolDescriptor createAdditionalPropertiesFixtureToolDescriptor() {
         Map<String, Object> optionSchema = Map.of("type", "object", "additionalProperties", Map.of("type", "string"));
         return new MCPToolDescriptor("fixture_tool", "Fixture Tool", "Fixture tool.", Map.of("type", "object", "properties", Map.of("options", optionSchema), "required", List.of(),
-                "additionalProperties", false), Collections.emptyMap(), new MCPToolAnnotations("Fixture Tool", true, false, true, true), Collections.emptyMap());
+                "additionalProperties", false), Collections.emptyMap(),
+                MCPToolAnnotations.builder()
+                        .title("Fixture Tool").readOnlyHint(true).destructiveHint(false).idempotentHint(true).openWorldHint(true).build(),
+                Collections.emptyMap());
     }
     
     private static void assertToolFields(final MCPToolDescriptor descriptor, final List<String> expectedFieldNames) {
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcStatementExecutorTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcStatementExecutorTest.java
index 37a64d299fb..d5e97c08911 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcStatementExecutorTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcStatementExecutorTest.java
@@ -102,8 +102,9 @@ class MCPJdbcStatementExecutorTest {
         SQLExecutionResponse actual = statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", "public", "UPDATE orders SET status = 'DONE'", 10, 1000),
                 new ClassificationResult(SupportedMCPStatement.DML, "UPDATE", "UPDATE orders SET status = 'DONE'", "", List.of()),
-                createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE));
+                createDatabaseCapability(SchemaExecutionSemantics.BEST_EFFORT));
         assertThat(actual.getAffectedRows(), is(2));
+        verify(connection).setSchema("public");
         verify(connection, never()).close();
     }
     
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionStatementExecutorTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionStatementExecutorTest.java
index dc8a9137244..10595118777 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionStatementExecutorTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionStatementExecutorTest.java
@@ -17,6 +17,8 @@
 
 package org.apache.shardingsphere.mcp.core.tool.handler.execute;
 
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
+
 import org.apache.shardingsphere.mcp.api.protocol.exception.MCPInvalidRequestException;
 import org.apache.shardingsphere.mcp.api.protocol.exception.MCPTransactionStateException;
 import org.apache.shardingsphere.mcp.api.protocol.exception.MCPUnsupportedException;
@@ -24,7 +26,6 @@ import org.apache.shardingsphere.mcp.core.session.MCPSessionManager;
 import org.apache.shardingsphere.mcp.core.session.MCPSessionNotExistedException;
 import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapability;
 import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPStatement;
-import org.apache.shardingsphere.mcp.support.database.capability.TransactionCapability;
 import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
 import org.apache.shardingsphere.mcp.support.database.tool.response.SQLExecutionResponse;
 import org.junit.jupiter.api.Test;
@@ -65,6 +66,7 @@ class MCPJdbcTransactionStatementExecutorTest {
         SQLExecutionResponse actual = executor.execute("session-1", "logic_db", createCapability(), new StatementClassifier().classify(sql));
         assertThat(actual.getStatementType(), is(expectedStatementType));
         assertThat(actual.getMessage(), is(expectedMessage));
+        assertThat(actual.getNormalizedSql(), is(sql));
         assertDatabaseExecution(sql, sessionManager, runtimeDatabaseConfig, connection, savepoint);
     }
     
@@ -136,8 +138,8 @@ class MCPJdbcTransactionStatementExecutorTest {
     
     private MCPDatabaseCapability createCapability(final TransactionCapability transactionCapability) {
         MCPDatabaseCapability result = mock(MCPDatabaseCapability.class);
-        when(result.isSupportsTransactionControl()).thenReturn(TransactionCapability.NONE != transactionCapability);
-        when(result.isSupportsSavepoint()).thenReturn(TransactionCapability.LOCAL_WITH_SAVEPOINT == transactionCapability);
+        when(result.supportsTransactionControl()).thenReturn(TransactionCapability.NONE != transactionCapability);
+        when(result.supportsSavepoint()).thenReturn(TransactionCapability.LOCAL_WITH_SAVEPOINT == transactionCapability);
         return result;
     }
     
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/SearchMetadataPayloadBuilderTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/SearchMetadataPayloadBuilderTest.java
index e1c659c32aa..4034f462f66 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/SearchMetadataPayloadBuilderTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/SearchMetadataPayloadBuilderTest.java
@@ -17,6 +17,8 @@
 
 package org.apache.shardingsphere.mcp.core.tool.handler.metadata;
 
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
+
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicyFactory;
 import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereSchema;
 import org.apache.shardingsphere.mcp.core.tool.request.MetadataSearchRequest;
@@ -93,7 +95,10 @@ class SearchMetadataPayloadBuilderTest {
     }
     
     private MetadataSearchHit createHit(final String database, final String schema, final String name, final List<String> matchedFields) {
-        return new MetadataSearchHit(database, schema, "table", name, "", name, Map.of(), Map.of(), List.of(), "complete", "", "exact", matchedFields, name);
+        return MetadataSearchHit.builder()
+                .database(database).schema(schema).objectType("table").table(name).view("").name(name)
+                .resource(Map.of()).parentResource(Map.of()).nextResources(List.of()).derivationStatus("complete").derivationReason("")
+                .matchKind("exact").matchedFields(matchedFields).matchedValue(name).build();
     }
     
     private MCPDatabaseHandlerContext createDatabaseContext() {
@@ -103,7 +108,7 @@ class SearchMetadataPayloadBuilderTest {
         when(result.getMetadataQueryFacade()).thenReturn(metadataQueryFacade);
         when(result.getCapabilityFacade()).thenReturn(capabilityFacade);
         when(metadataQueryFacade.queryDatabases()).thenReturn(
-                List.of(new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet())));
+                List.of(new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet())));
         when(metadataQueryFacade.querySchema("logic_db", "public")).thenReturn(Optional.of(mock(ShardingSphereSchema.class)));
         when(capabilityFacade.findDatabaseProfile("logic_db")).thenReturn(Optional.of(mock(RuntimeDatabaseProfile.class)));
         return result;
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/SearchMetadataToolServiceTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/SearchMetadataToolServiceTest.java
index e37b2f22dbd..1d7bc4af617 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/SearchMetadataToolServiceTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/SearchMetadataToolServiceTest.java
@@ -27,6 +27,7 @@ import org.apache.shardingsphere.mcp.core.tool.response.MetadataSearchHit;
 import org.apache.shardingsphere.mcp.core.tool.response.MetadataSearchResult;
 import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPMetadataObjectType;
 import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPMetadataQueryFacade;
 import org.junit.jupiter.api.Test;
 
 import java.util.LinkedList;
@@ -279,6 +280,15 @@ class SearchMetadataToolServiceTest {
         assertThat(actual.getItems().size(), is(0));
     }
     
+    @Test
+    void assertExecuteSearchWithUnsearchableObjectType() {
+        MCPMetadataQueryFacade metadataQueryFacade = mock(MCPMetadataQueryFacade.class);
+        MetadataSearchResult actual = new SearchMetadataToolService(metadataQueryFacade, mock(MCPFeatureQueryFacade.class)).execute(
+                new MetadataSearchRequest("logic_db", "", "", Set.of(SupportedMCPMetadataObjectType.MATERIALIZED_VIEW)));
+        assertThat(actual.getItems(), is(List.of()));
+        verifyNoInteractions(metadataQueryFacade);
+    }
+    
     private MetadataSearchResult execute(final List<DatabaseMetadataFixture> databaseMetadata, final MetadataSearchRequest request) {
         try (RequestScopeFixture requestScopeFixture = ResourceTestDataFactory.createRequestScopeFixture(databaseMetadata)) {
             MCPRequestScope requestScope = requestScopeFixture.getRequestScope();
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowSessionSnapshotResolverTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowSessionSnapshotResolverTest.java
new file mode 100644
index 00000000000..5545e4d0be6
--- /dev/null
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowSessionSnapshotResolverTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.core.workflow;
+
+import org.apache.shardingsphere.mcp.core.protocol.exception.MCPWorkflowStateException;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowKind;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class WorkflowSessionSnapshotResolverTest {
+    
+    @Test
+    void assertGetRequiredWorkflowKind() {
+        WorkflowContextSnapshot snapshot = mock(WorkflowContextSnapshot.class);
+        WorkflowKind expected = WorkflowKind.valueOf("fixture.rule");
+        when(snapshot.getWorkflowKind()).thenReturn(expected);
+        assertThat(WorkflowSessionSnapshotResolver.getRequiredWorkflowKind(snapshot), is(expected));
+    }
+    
+    @Test
+    void assertGetRequiredWorkflowKindWithoutKind() {
+        WorkflowContextSnapshot snapshot = mock(WorkflowContextSnapshot.class);
+        when(snapshot.getPlanId()).thenReturn("plan-1");
+        MCPWorkflowStateException actual = assertThrows(MCPWorkflowStateException.class, () -> WorkflowSessionSnapshotResolver.getRequiredWorkflowKind(snapshot));
+        assertThat(actual.getMessage(), is("Workflow kind is required for plan_id `plan-1`."));
+    }
+}
diff --git a/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastRuleDistSQLPlanningService.java b/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastRuleDistSQLPlanningService.java
index f921bb78c13..f0a0ce6661d 100644
--- a/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastRuleDistSQLPlanningService.java
+++ b/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastRuleDistSQLPlanningService.java
@@ -18,6 +18,7 @@
 package org.apache.shardingsphere.mcp.feature.broadcast.tool.service;
 
 import org.apache.shardingsphere.mcp.support.workflow.model.RuleArtifact;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
 
 import java.util.List;
@@ -35,7 +36,7 @@ public final class BroadcastRuleDistSQLPlanningService {
      * @return rule artifact
      */
     public RuleArtifact planCreateRule(final List<String> tableNames) {
-        return new RuleArtifact("create", String.format("CREATE BROADCAST TABLE RULE %s", formatTableNames(tableNames)));
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_CREATE, String.format("CREATE BROADCAST TABLE RULE %s", formatTableNames(tableNames)));
     }
     
     /**
@@ -45,7 +46,7 @@ public final class BroadcastRuleDistSQLPlanningService {
      * @return rule artifact
      */
     public RuleArtifact planDropRule(final List<String> tableNames) {
-        return new RuleArtifact("drop", String.format("DROP BROADCAST TABLE RULE %s", formatTableNames(tableNames)));
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, String.format("DROP BROADCAST TABLE RULE %s", formatTableNames(tableNames)));
     }
     
     private String formatTableNames(final List<String> tableNames) {
diff --git a/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningService.java b/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningService.java
index 20c7c4bde29..15e6203944e 100644
--- a/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningService.java
+++ b/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningService.java
@@ -97,19 +97,19 @@ public final class BroadcastWorkflowPlanningService {
     private boolean ensurePlanningContext(final BroadcastWorkflowRequest request, final ClarifiedIntent clarifiedIntent, final WorkflowContextSnapshot snapshot) {
         if (request.getDatabase().isEmpty()) {
             clarifiedIntent.getClarificationMessages().add("Please provide logical database first.");
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DATABASE_REQUIRED, "error", "intaking",
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DATABASE_REQUIRED, "error", WorkflowLifecycle.STEP_INTAKING,
                     "Database is required before planning broadcast rule DistSQL.", "Provide the logical database name.", true, Map.of()));
             snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
             return false;
         }
-        if (!planningSupport.ensureSupportedIdentifiers("database", List.of(request.getDatabase()), snapshot, "discovering")
-                || !planningSupport.ensureSupportedIdentifiers("tables", request.getTargetTables(), snapshot, "discovering")) {
+        if (!planningSupport.ensureSupportedIdentifiers("database", List.of(request.getDatabase()), snapshot, WorkflowLifecycle.STEP_DISCOVERING)
+                || !planningSupport.ensureSupportedIdentifiers("tables", request.getTargetTables(), snapshot, WorkflowLifecycle.STEP_DISCOVERING)) {
             snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
             return false;
         }
         if (request.getTargetTables().isEmpty()) {
             clarifiedIntent.getClarificationMessages().add("Please provide one or more logical table names for broadcast rule planning.");
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_INPUT_REQUIRED, "error", "intaking",
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_INPUT_REQUIRED, "error", WorkflowLifecycle.STEP_INTAKING,
                     "Broadcast rule DistSQL requires at least one logical table.", "Provide tables as a comma-separated list or table as a single value.", true,
                     Map.of("missing_inputs", List.of(BroadcastFeatureDefinition.TABLES_FIELD))));
             snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
@@ -134,7 +134,7 @@ public final class BroadcastWorkflowPlanningService {
         if (!ruleExists) {
             return true;
         }
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", "discovering",
+        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", WorkflowLifecycle.STEP_DISCOVERING,
                 String.format("Broadcast rule already contains table `%s`.", tableName), "Remove existing table from the create request or choose drop.", false, Map.of("table", tableName)));
         return false;
     }
@@ -143,7 +143,7 @@ public final class BroadcastWorkflowPlanningService {
         if (ruleExists) {
             return true;
         }
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DROP_TARGET_RULE_NOT_FOUND, "error", "discovering",
+        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DROP_TARGET_RULE_NOT_FOUND, "error", WorkflowLifecycle.STEP_DISCOVERING,
                 String.format("Broadcast rule does not contain table `%s`.", tableName), "Confirm target table or skip the drop request.", false, Map.of("table", tableName)));
         return false;
     }
diff --git a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptAlgorithmRecommendationService.java b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptAlgorithmRecommendationService.java
index 3d032d6b8c4..615977a4bd3 100644
--- a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptAlgorithmRecommendationService.java
+++ b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptAlgorithmRecommendationService.java
@@ -22,6 +22,7 @@ import org.apache.shardingsphere.mcp.feature.encrypt.tool.model.EncryptWorkflowR
 import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowAlgorithmUtils;
 
 import java.util.LinkedList;
@@ -79,7 +80,7 @@ public final class EncryptAlgorithmRecommendationService {
     private String resolveSpecifiedPrimaryEncryptAlgorithm(final EncryptWorkflowRequest request, final List<Map<String, Object>> encryptAlgorithms,
                                                            final List<WorkflowIssue> issues, final String algorithmType) {
         if (!WorkflowAlgorithmUtils.containsAlgorithm(encryptAlgorithms, algorithmType)) {
-            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM,
                     String.format("Encrypt algorithm `%s` is not visible from the current Proxy.", algorithmType), "Choose an available encrypt algorithm.", false, Map.of()));
             return "";
         }
@@ -120,7 +121,7 @@ public final class EncryptAlgorithmRecommendationService {
         if (!specifiedAlgorithmType.isEmpty()) {
             return;
         }
-        issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_CAPABILITY_CONFLICT, "error", "selecting-algorithm", message, userAction, false, Map.of()));
+        issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_CAPABILITY_CONFLICT, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM, message, userAction, false, Map.of()));
     }
     
     private String resolveAssistedQueryAlgorithm(final EncryptWorkflowRequest request, final List<Map<String, Object>> encryptAlgorithms,
@@ -134,7 +135,7 @@ public final class EncryptAlgorithmRecommendationService {
     
     private String resolveSpecifiedAssistedQueryAlgorithm(final List<Map<String, Object>> encryptAlgorithms, final List<WorkflowIssue> issues, final String algorithmType) {
         if (!WorkflowAlgorithmUtils.containsAlgorithm(encryptAlgorithms, algorithmType)) {
-            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM,
                     String.format("Assisted-query algorithm `%s` is not visible from the current Proxy.", algorithmType),
                     "Choose an available assisted-query algorithm.", false, Map.of()));
             return "";
@@ -164,7 +165,7 @@ public final class EncryptAlgorithmRecommendationService {
     
     private String resolveSpecifiedLikeQueryAlgorithm(final List<Map<String, Object>> encryptAlgorithms, final List<WorkflowIssue> issues, final String algorithmType) {
         if (!WorkflowAlgorithmUtils.containsAlgorithm(encryptAlgorithms, algorithmType)) {
-            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM,
                     String.format("LIKE-query algorithm `%s` is not visible from the current Proxy.", algorithmType),
                     "Choose an available LIKE-query algorithm.", false, Map.of()));
             return "";
@@ -187,14 +188,21 @@ public final class EncryptAlgorithmRecommendationService {
     }
     
     private void addSpecifiedCapabilityConflictIssue(final List<WorkflowIssue> issues, final String message, final String userAction) {
-        issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_CAPABILITY_CONFLICT, "error", "selecting-algorithm", message, userAction, false, Map.of()));
+        issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_CAPABILITY_CONFLICT, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM, message, userAction, false, Map.of()));
     }
     
     private AlgorithmCandidate createEncryptCandidate(final String role, final String algorithmType, final EncryptWorkflowRequest request) {
         Map<String, Boolean> capability = findEncryptCapability(algorithmType);
-        return new AlgorithmCandidate(role, algorithmType, capability.get(EncryptFeatureDefinition.ALGORITHM_CAPABILITY_DECRYPT),
-                capability.get(EncryptFeatureDefinition.ALGORITHM_CAPABILITY_EQUIVALENT_FILTER), capability.get(EncryptFeatureDefinition.ALGORITHM_CAPABILITY_LIKE),
-                calculateEncryptScore(role, capability), createEncryptReason(role, algorithmType, request), createEncryptRisk(capability));
+        return AlgorithmCandidate.builder()
+                .algorithmRole(role)
+                .algorithmType(algorithmType)
+                .supportsDecrypt(capability.get(EncryptFeatureDefinition.ALGORITHM_CAPABILITY_DECRYPT))
+                .supportsEquivalentFilter(capability.get(EncryptFeatureDefinition.ALGORITHM_CAPABILITY_EQUIVALENT_FILTER))
+                .supportsLike(capability.get(EncryptFeatureDefinition.ALGORITHM_CAPABILITY_LIKE))
+                .recommendationScore(calculateEncryptScore(role, capability))
+                .recommendationReason(createEncryptReason(role, algorithmType, request))
+                .riskNotes(createEncryptRisk(capability))
+                .build();
     }
     
     private int calculateEncryptScore(final String role, final Map<String, Boolean> capability) {
diff --git a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleDistSQLPlanningService.java b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleDistSQLPlanningService.java
index 000547eb58c..33c850c00d9 100644
--- a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleDistSQLPlanningService.java
+++ b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleDistSQLPlanningService.java
@@ -19,6 +19,7 @@ package org.apache.shardingsphere.mcp.feature.encrypt.tool.service;
 
 import org.apache.shardingsphere.mcp.feature.encrypt.tool.model.EncryptWorkflowRequest;
 import org.apache.shardingsphere.mcp.support.workflow.model.RuleArtifact;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
 
 import java.util.List;
@@ -36,7 +37,7 @@ public final class EncryptRuleDistSQLPlanningService {
      */
     public List<RuleArtifact> planEncryptRule(final EncryptWorkflowRequest request) {
         validateEncryptIdentifiers(request);
-        return List.of(new RuleArtifact("create", createEncryptRuleSql(request.getTable(), List.of(createTargetEncryptColumnSegment(request)))));
+        return List.of(new RuleArtifact(WorkflowLifecycle.OPERATION_CREATE, createEncryptRuleSql(request.getTable(), List.of(createTargetEncryptColumnSegment(request)))));
     }
     
     /**
@@ -47,7 +48,7 @@ public final class EncryptRuleDistSQLPlanningService {
      */
     public List<RuleArtifact> planEncryptDropRule(final EncryptWorkflowRequest request) {
         validateEncryptDropIdentifiers(request);
-        return List.of(new RuleArtifact("drop", createDropRuleSql(request.getTable())));
+        return List.of(new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, createDropRuleSql(request.getTable())));
     }
     
     private String createDropRuleSql(final String tableName) {
diff --git a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowIntentResolver.java b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowIntentResolver.java
index 1f961ccf8a9..08e854a38b6 100644
--- a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowIntentResolver.java
+++ b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowIntentResolver.java
@@ -19,6 +19,7 @@ package org.apache.shardingsphere.mcp.feature.encrypt.tool.service;
 
 import org.apache.shardingsphere.mcp.feature.encrypt.tool.model.EncryptWorkflowRequest;
 import org.apache.shardingsphere.mcp.support.workflow.model.ClarifiedIntent;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowIntentResolverSupport;
 
 import java.util.Locale;
@@ -94,7 +95,7 @@ final class EncryptWorkflowIntentResolver {
     }
     
     private boolean isDropWorkflow(final ClarifiedIntent clarifiedIntent) {
-        return "drop".equalsIgnoreCase(clarifiedIntent.getOperationType());
+        return WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(clarifiedIntent.getOperationType());
     }
     
     private Boolean inferOption(final ClarifiedIntent clarifiedIntent, final String fieldName, final boolean value) {
diff --git a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningService.java b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningService.java
index f2bf6e951a1..87184e7ace8 100644
--- a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningService.java
+++ b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningService.java
@@ -45,7 +45,7 @@ import java.util.Map;
  */
 public final class EncryptWorkflowPlanningService {
     
-    private static final List<String> SUPPORTED_OPERATION_TYPES = List.of("create", WorkflowLifecycle.OPERATION_DROP);
+    private static final List<String> SUPPORTED_OPERATION_TYPES = List.of(WorkflowLifecycle.OPERATION_CREATE, WorkflowLifecycle.OPERATION_DROP);
     
     private static final List<String> INTERACTION_STEPS = List.of(
             "Confirm database, table, column and target operation",
@@ -179,7 +179,7 @@ public final class EncryptWorkflowPlanningService {
         snapshot.getClarifiedIntent().getClarificationMessages().add(
                 "Current Proxy DistSQL cannot automatically rewrite an existing encrypt table rule with a partial column set. "
                         + "Recreate the rule manually during a maintenance window.");
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.ENCRYPT_RULE_REWRITE_LIMITED, "error", "planning-artifacts",
+        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.ENCRYPT_RULE_REWRITE_LIMITED, "error", WorkflowLifecycle.STEP_PLANNING_ARTIFACTS,
                 "Encrypt planning cannot automatically rewrite an existing table rule while preserving other columns.",
                 "Manually recreate the encrypt rule with the complete column set after reviewing data impact.", true, Map.of("requires_table_rule_rewrite", true)));
         return false;
@@ -190,7 +190,7 @@ public final class EncryptWorkflowPlanningService {
     }
     
     private void addDropLifecycleWarnings(final WorkflowContextSnapshot snapshot) {
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.ENCRYPT_DROP_SCOPE_LIMITED, "warning", "planning-artifacts",
+        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.ENCRYPT_DROP_SCOPE_LIMITED, "warning", WorkflowLifecycle.STEP_PLANNING_ARTIFACTS,
                 "Encrypt drop only removes the rule. MCP will not restore historical plaintext data.", "Review business impact before execution.", true, Map.of()));
     }
     
@@ -253,7 +253,7 @@ public final class EncryptWorkflowPlanningService {
         for (String each : missingInputs) {
             clarifiedIntent.getClarificationMessages().add(String.format("Please provide `%s` for encrypt rule DistSQL.", each));
         }
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_INPUT_REQUIRED, "error", "collecting-rule-inputs",
+        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_INPUT_REQUIRED, "error", WorkflowLifecycle.STEP_COLLECTING_RULE_INPUTS,
                 "Encrypt rule DistSQL requires explicit rule column and query algorithm inputs.", "Provide the missing rule inputs and retry planning.", true,
                 Map.of("missing_inputs", missingInputs)));
         return false;
diff --git a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationService.java b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationService.java
index e4b5ff909ea..9a92f09e20d 100644
--- a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationService.java
+++ b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationService.java
@@ -39,7 +39,6 @@ import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowArtifactBu
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowLifecycleUtils;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowRuleValueUtils;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSecretReferenceUtils;
-import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSynchronizationSupport;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowValidationSupport;
 import org.apache.shardingsphere.mcp.support.workflow.spi.MCPWorkflowApplyArtifactValidator;
@@ -299,8 +298,8 @@ public final class EncryptWorkflowValidationService implements MCPWorkflowRuntim
     
     private void addPropertyMismatch(final List<Map<String, Object>> mismatches, final WorkflowContextSnapshot snapshot, final String fieldName,
                                      final Object expected, final Object actual, final String impact) {
-        Map<String, String> expectedProperties = WorkflowSQLUtils.createPropertyMap(expected);
-        Map<String, String> actualProperties = WorkflowSQLUtils.createPropertyMap(actual);
+        Map<String, String> expectedProperties = WorkflowAlgorithmUtils.createPropertyMap(expected);
+        Map<String, String> actualProperties = WorkflowAlgorithmUtils.createPropertyMap(actual);
         String algorithmRole = getAlgorithmRole(fieldName);
         if (WorkflowSecretReferenceUtils.matchesManualPlaceholderProperties(expectedProperties, actualProperties, snapshot.getRequest(), algorithmRole)) {
             return;
@@ -318,12 +317,12 @@ public final class EncryptWorkflowValidationService implements MCPWorkflowRuntim
         List<Map<String, Object>> result = new LinkedList<>();
         for (Map<String, Object> each : rules) {
             Map<String, Object> rule = new LinkedHashMap<>(each);
-            rule.put("encryptor_props", WorkflowArtifactMaskUtils.maskPropertyMap(WorkflowSQLUtils.createPropertyMap(each.get("encryptor_props")), snapshot.getPropertyRequirements(),
+            rule.put("encryptor_props", WorkflowArtifactMaskUtils.maskPropertyMap(WorkflowAlgorithmUtils.createPropertyMap(each.get("encryptor_props")), snapshot.getPropertyRequirements(),
                     snapshot.getRequest(), EncryptFeatureDefinition.ALGORITHM_ROLE_PRIMARY));
             rule.put("assisted_query_props",
-                    WorkflowArtifactMaskUtils.maskPropertyMap(WorkflowSQLUtils.createPropertyMap(each.get("assisted_query_props")), snapshot.getPropertyRequirements(),
+                    WorkflowArtifactMaskUtils.maskPropertyMap(WorkflowAlgorithmUtils.createPropertyMap(each.get("assisted_query_props")), snapshot.getPropertyRequirements(),
                             snapshot.getRequest(), EncryptFeatureDefinition.ALGORITHM_ROLE_ASSISTED_QUERY));
-            rule.put("like_query_props", WorkflowArtifactMaskUtils.maskPropertyMap(WorkflowSQLUtils.createPropertyMap(each.get("like_query_props")), snapshot.getPropertyRequirements(),
+            rule.put("like_query_props", WorkflowArtifactMaskUtils.maskPropertyMap(WorkflowAlgorithmUtils.createPropertyMap(each.get("like_query_props")), snapshot.getPropertyRequirements(),
                     snapshot.getRequest(), EncryptFeatureDefinition.ALGORITHM_ROLE_LIKE_QUERY));
             result.add(rule);
         }
diff --git a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningServiceTest.java b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningServiceTest.java
index b80d9c1ddb0..68ac6992046 100644
--- a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningServiceTest.java
+++ b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningServiceTest.java
@@ -17,6 +17,8 @@
 
 package org.apache.shardingsphere.mcp.feature.encrypt.tool.service;
 
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
+
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicyFactory;
 import org.apache.shardingsphere.database.connector.core.metadata.database.enums.TableType;
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierScope;
@@ -318,7 +320,9 @@ class EncryptWorkflowPlanningServiceTest {
     
     private EncryptAlgorithmRecommendationService createPrimaryCandidateRecommendation() {
         EncryptAlgorithmRecommendationService result = mock(EncryptAlgorithmRecommendationService.class);
-        when(result.recommendEncryptAlgorithms(any(), any(), any())).thenReturn(List.of(new AlgorithmCandidate("primary", "AES", true, true, false, 100, "reason", "")));
+        when(result.recommendEncryptAlgorithms(any(), any(), any())).thenReturn(List.of(AlgorithmCandidate.builder()
+                .algorithmRole("primary").algorithmType("AES").supportsDecrypt(true).supportsEquivalentFilter(true).supportsLike(false)
+                .recommendationScore(100).recommendationReason("reason").riskNotes("").build()));
         return result;
     }
     
@@ -374,7 +378,7 @@ class EncryptWorkflowPlanningServiceTest {
     }
     
     private RuntimeDatabaseProfile createDatabaseMetadata() {
-        return new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet());
+        return new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet());
     }
     
     private ShardingSphereSchema createSchemaMetadata() {
diff --git a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationServiceTest.java b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationServiceTest.java
index 2ea9e0611a5..091fbea2d72 100644
--- a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationServiceTest.java
+++ b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationServiceTest.java
@@ -36,7 +36,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnaps
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowArtifactBundle.ExecutableWorkflowArtifact;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowArtifactPayloadUtils;
-import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
+import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowAlgorithmUtils;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -138,7 +138,7 @@ class EncryptWorkflowValidationServiceTest {
         snapshot.setRequest(request);
         String sql = "CREATE ENCRYPT RULE `t_user` (COLUMNS((NAME=`phone`, CIPHER=`phone_cipher`, "
                 + "ENCRYPT_ALGORITHM(TYPE(NAME='aes', PROPERTIES('aes-key-value'='******', 'digest-algorithm-name'='SHA-1'))))))";
-        typedSPILoader.when(() -> TypedSPILoader.checkService(EncryptAlgorithm.class, "AES", WorkflowSQLUtils.createProperties(request.getPrimaryAlgorithmProperties())))
+        typedSPILoader.when(() -> TypedSPILoader.checkService(EncryptAlgorithm.class, "AES", WorkflowAlgorithmUtils.createProperties(request.getPrimaryAlgorithmProperties())))
                 .thenThrow(new IllegalArgumentException("raw-secret"));
         List<Map<String, Object>> actual = new EncryptWorkflowValidationService().validate(snapshot, List.of(createRuleDistSQLArtifact(sql, sql)));
         assertThat(actual.size(), is(1));
diff --git a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskAlgorithmRecommendationService.java b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskAlgorithmRecommendationService.java
index 97d2939dd78..f2a83f723c5 100644
--- a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskAlgorithmRecommendationService.java
+++ b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskAlgorithmRecommendationService.java
@@ -21,6 +21,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
 import org.apache.shardingsphere.mcp.support.workflow.model.ClarifiedIntent;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowAlgorithmUtils;
 
@@ -47,20 +48,21 @@ public final class MaskAlgorithmRecommendationService {
         String actualAlgorithmType = WorkflowAlgorithmUtils.normalizeAlgorithmType(request.getAlgorithmType());
         if (!actualAlgorithmType.isEmpty()) {
             if (WorkflowAlgorithmUtils.containsAlgorithm(maskAlgorithms, actualAlgorithmType)) {
-                return List.of(new AlgorithmCandidate("primary", actualAlgorithmType, null, null, null, 100, "User specified algorithm.", ""));
+                return List.of(AlgorithmCandidate.builder().algorithmRole("primary").algorithmType(actualAlgorithmType).recommendationScore(100)
+                        .recommendationReason("User specified algorithm.").riskNotes("").build());
             }
-            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM,
                     String.format("Mask algorithm `%s` is not visible from the current Proxy.", actualAlgorithmType), "Choose an available mask algorithm.", false, Map.of()));
             return List.of();
         }
         String recommendedType = resolveRecommendedAlgorithm(intent, request, maskAlgorithms);
         if (recommendedType.isEmpty()) {
-            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM,
                     "No mask algorithm is available from the current Proxy.", "Install or register at least one visible mask algorithm.", false, Map.of()));
             return List.of();
         }
-        return List.of(new AlgorithmCandidate("primary", recommendedType, null, null, null, 100,
-                "Recommended by field semantics and current algorithm availability.", ""));
+        return List.of(AlgorithmCandidate.builder().algorithmRole("primary").algorithmType(recommendedType).recommendationScore(100)
+                .recommendationReason("Recommended by field semantics and current algorithm availability.").riskNotes("").build());
     }
     
     private String resolveRecommendedAlgorithm(final ClarifiedIntent intent, final WorkflowRequest request, final List<Map<String, Object>> maskAlgorithms) {
diff --git a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleDistSQLPlanningService.java b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleDistSQLPlanningService.java
index 35e4ee256b9..2b72acbbeaf 100644
--- a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleDistSQLPlanningService.java
+++ b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleDistSQLPlanningService.java
@@ -18,6 +18,7 @@
 package org.apache.shardingsphere.mcp.feature.mask.tool.service;
 
 import org.apache.shardingsphere.mcp.support.workflow.model.RuleArtifact;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
 
@@ -36,7 +37,7 @@ public final class MaskRuleDistSQLPlanningService {
      */
     public RuleArtifact planMaskRule(final WorkflowRequest request) {
         validateMaskIdentifiers(request);
-        return new RuleArtifact("create", createMaskRuleSql("CREATE MASK RULE", request.getTable(), List.of(createTargetMaskColumnSegment(request))));
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_CREATE, createMaskRuleSql("CREATE MASK RULE", request.getTable(), List.of(createTargetMaskColumnSegment(request))));
     }
     
     /**
@@ -47,7 +48,7 @@ public final class MaskRuleDistSQLPlanningService {
      */
     public RuleArtifact planMaskDropRule(final WorkflowRequest request) {
         validateMaskIdentifiers(request);
-        return new RuleArtifact("drop", String.format("DROP MASK RULE %s", WorkflowSQLUtils.formatGeneratedRuleDistSQLIdentifier(request.getTable())));
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, String.format("DROP MASK RULE %s", WorkflowSQLUtils.formatGeneratedRuleDistSQLIdentifier(request.getTable())));
     }
     
     private void validateMaskIdentifiers(final WorkflowRequest request) {
diff --git a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningService.java b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningService.java
index c031fdd1462..5ba2f9815d6 100644
--- a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningService.java
+++ b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningService.java
@@ -44,7 +44,7 @@ import java.util.Map;
  */
 public final class MaskWorkflowPlanningService {
     
-    private static final List<String> SUPPORTED_OPERATION_TYPES = List.of("create", WorkflowLifecycle.OPERATION_DROP);
+    private static final List<String> SUPPORTED_OPERATION_TYPES = List.of(WorkflowLifecycle.OPERATION_CREATE, WorkflowLifecycle.OPERATION_DROP);
     
     private static final List<String> INTERACTION_STEPS = List.of(
             "Confirm database, table, column and target lifecycle",
@@ -150,7 +150,7 @@ public final class MaskWorkflowPlanningService {
                                                     final List<Map<String, Object>> maskRules, final WorkflowContextSnapshot snapshot) {
         snapshot.getClarifiedIntent().getClarificationMessages().add(
                 "Current Proxy DistSQL cannot automatically mutate an existing mask table rule. Recreate the mask rule manually with the complete column set during a maintenance window.");
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.MASK_RULE_REWRITE_LIMITED, "error", "planning-artifacts",
+        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.MASK_RULE_REWRITE_LIMITED, "error", WorkflowLifecycle.STEP_PLANNING_ARTIFACTS,
                 "Mask planning cannot automatically rewrite an existing table rule or shrink it while preserving remaining columns.",
                 "Manually recreate the mask rule with the complete column set after reviewing data impact.", true,
                 Map.of("operation_type", clarifiedIntent.getOperationType(), "target_column", request.getColumn(), "existing_columns", createExistingRuleColumns(maskRules))));
diff --git a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationService.java b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationService.java
index 3e8c36a9063..fb83907564f 100644
--- a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationService.java
+++ b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationService.java
@@ -36,7 +36,6 @@ import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowArtifactMa
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowArtifactBundle.ExecutableWorkflowArtifact;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowLifecycleUtils;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowRuleValueUtils;
-import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSecretReferenceUtils;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSynchronizationSupport;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowValidationSupport;
@@ -229,8 +228,8 @@ public final class MaskWorkflowValidationService implements MCPWorkflowRuntimeHa
     }
     
     private void addPropertyMismatch(final List<Map<String, Object>> mismatches, final WorkflowContextSnapshot snapshot, final Object expected, final Object actual) {
-        Map<String, String> expectedProperties = WorkflowSQLUtils.createPropertyMap(expected);
-        Map<String, String> actualProperties = WorkflowSQLUtils.createPropertyMap(actual);
+        Map<String, String> expectedProperties = WorkflowAlgorithmUtils.createPropertyMap(expected);
+        Map<String, String> actualProperties = WorkflowAlgorithmUtils.createPropertyMap(actual);
         if (WorkflowSecretReferenceUtils.matchesManualPlaceholderProperties(expectedProperties, actualProperties, snapshot.getRequest(), "primary")) {
             return;
         }
@@ -244,7 +243,7 @@ public final class MaskWorkflowValidationService implements MCPWorkflowRuntimeHa
         List<Map<String, Object>> result = new LinkedList<>();
         for (Map<String, Object> each : rules) {
             Map<String, Object> rule = new LinkedHashMap<>(each);
-            rule.put("algorithm_props", WorkflowArtifactMaskUtils.maskPropertyMap(WorkflowSQLUtils.createPropertyMap(each.get("algorithm_props")), snapshot.getPropertyRequirements(),
+            rule.put("algorithm_props", WorkflowArtifactMaskUtils.maskPropertyMap(WorkflowAlgorithmUtils.createPropertyMap(each.get("algorithm_props")), snapshot.getPropertyRequirements(),
                     snapshot.getRequest(), "primary"));
             result.add(rule);
         }
diff --git a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningServiceTest.java b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningServiceTest.java
index 0d6f99cf9e7..24492cea878 100644
--- a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningServiceTest.java
+++ b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningServiceTest.java
@@ -17,6 +17,8 @@
 
 package org.apache.shardingsphere.mcp.feature.mask.tool.service;
 
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
+
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicyFactory;
 import org.apache.shardingsphere.database.connector.core.metadata.database.enums.TableType;
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierScope;
@@ -249,7 +251,8 @@ class MaskWorkflowPlanningServiceTest {
     
     private MaskAlgorithmRecommendationService createPrimaryCandidateRecommendation() {
         MaskAlgorithmRecommendationService result = mock(MaskAlgorithmRecommendationService.class);
-        when(result.recommendMaskAlgorithms(any(), any(), any(), any())).thenReturn(List.of(new AlgorithmCandidate("primary", "MASK_FROM_X_TO_Y", null, null, null, 100, "reason", "")));
+        when(result.recommendMaskAlgorithms(any(), any(), any(), any())).thenReturn(List.of(AlgorithmCandidate.builder()
+                .algorithmRole("primary").algorithmType("MASK_FROM_X_TO_Y").recommendationScore(100).recommendationReason("reason").riskNotes("").build()));
         return result;
     }
     
@@ -303,7 +306,7 @@ class MaskWorkflowPlanningServiceTest {
     }
     
     private RuntimeDatabaseProfile createDatabaseMetadata() {
-        return new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet());
+        return new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet());
     }
     
     private ShardingSphereSchema createSchemaMetadata() {
diff --git a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationServiceTest.java b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationServiceTest.java
index 998bc3d5ec2..9e3d20f02b8 100644
--- a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationServiceTest.java
+++ b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationServiceTest.java
@@ -34,7 +34,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnaps
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowArtifactBundle.ExecutableWorkflowArtifact;
-import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
+import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowAlgorithmUtils;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -115,7 +115,7 @@ class MaskWorkflowValidationServiceTest {
         snapshot.setRequest(request);
         String sql = "CREATE MASK RULE `orders` (COLUMNS((NAME=`phone`, TYPE(NAME='mask_from_x_to_y', PROPERTIES('replace-char'='******')))))";
         typedSPILoader.when(() -> TypedSPILoader.checkService(MaskAlgorithm.class, "MASK_FROM_X_TO_Y",
-                WorkflowSQLUtils.createProperties(request.getPrimaryAlgorithmProperties()))).thenThrow(new IllegalArgumentException("raw-secret"));
+                WorkflowAlgorithmUtils.createProperties(request.getPrimaryAlgorithmProperties()))).thenThrow(new IllegalArgumentException("raw-secret"));
         List<Map<String, Object>> actual = new MaskWorkflowValidationService().validate(snapshot, List.of(createRuleDistSQLArtifact(sql)));
         assertThat(actual.size(), is(1));
         assertThat(actual.getFirst().get("code"), is(WorkflowIssueCode.SQL_EXECUTABILITY_FAILED));
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/PlanReadwriteSplittingRuleToolHandler.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/PlanReadwriteSplittingRuleToolHandler.java
index 20264f12df9..c63ee4b984e 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/PlanReadwriteSplittingRuleToolHandler.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/PlanReadwriteSplittingRuleToolHandler.java
@@ -63,37 +63,23 @@ public final class PlanReadwriteSplittingRuleToolHandler implements MCPToolHandl
     }
     
     private void bindFeatureArguments(final ReadwriteSplittingRuleWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
-        applyStringArgument(workflowPlanningArguments, ReadwriteSplittingFeatureDefinition.RULE_FIELD, request::setRuleName);
-        applyStringArgument(workflowPlanningArguments, ReadwriteSplittingFeatureDefinition.WRITE_STORAGE_UNIT_FIELD, request::setWriteStorageUnit);
-        applyStringArgument(workflowPlanningArguments, ReadwriteSplittingFeatureDefinition.READ_STORAGE_UNITS_FIELD, request::setReadStorageUnits);
-        applyStringArgument(workflowPlanningArguments, ReadwriteSplittingFeatureDefinition.TRANSACTIONAL_READ_QUERY_STRATEGY_FIELD, request::setTransactionalReadQueryStrategy);
-        applyStringArgument(workflowPlanningArguments, ReadwriteSplittingFeatureDefinition.LOAD_BALANCER_TYPE_FIELD, request::setLoadBalancerType);
+        workflowPlanningArguments.applyStringArgument(ReadwriteSplittingFeatureDefinition.RULE_FIELD, request::setRuleName);
+        workflowPlanningArguments.applyStringArgument(ReadwriteSplittingFeatureDefinition.WRITE_STORAGE_UNIT_FIELD, request::setWriteStorageUnit);
+        workflowPlanningArguments.applyStringArgument(ReadwriteSplittingFeatureDefinition.READ_STORAGE_UNITS_FIELD, request::setReadStorageUnits);
+        workflowPlanningArguments.applyStringArgument(ReadwriteSplittingFeatureDefinition.TRANSACTIONAL_READ_QUERY_STRATEGY_FIELD, request::setTransactionalReadQueryStrategy);
+        workflowPlanningArguments.applyStringArgument(ReadwriteSplittingFeatureDefinition.LOAD_BALANCER_TYPE_FIELD, request::setLoadBalancerType);
         request.putLoadBalancerProperties(workflowPlanningArguments.getMapArgument(ReadwriteSplittingFeatureDefinition.LOAD_BALANCER_PROPERTIES_FIELD));
     }
     
     private void applyStructuredIntentEvidence(final ReadwriteSplittingRuleWorkflowRequest request, final Map<String, Object> structuredIntentEvidence) {
-        applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.RULE_FIELD, request::setRuleName);
-        applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.WRITE_STORAGE_UNIT_FIELD, request::setWriteStorageUnit);
-        applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.READ_STORAGE_UNITS_FIELD, request::setReadStorageUnits);
-        applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.TRANSACTIONAL_READ_QUERY_STRATEGY_FIELD, request::setTransactionalReadQueryStrategy);
-        applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.LOAD_BALANCER_TYPE_FIELD, request::setLoadBalancerType);
+        WorkflowRequestBinder.applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.RULE_FIELD, request::setRuleName);
+        WorkflowRequestBinder.applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.WRITE_STORAGE_UNIT_FIELD, request::setWriteStorageUnit);
+        WorkflowRequestBinder.applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.READ_STORAGE_UNITS_FIELD, request::setReadStorageUnits);
+        WorkflowRequestBinder.applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.TRANSACTIONAL_READ_QUERY_STRATEGY_FIELD, request::setTransactionalReadQueryStrategy);
+        WorkflowRequestBinder.applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.LOAD_BALANCER_TYPE_FIELD, request::setLoadBalancerType);
         applyMapField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.LOAD_BALANCER_PROPERTIES_FIELD, request::putLoadBalancerProperties);
     }
     
-    private void applyStringField(final Map<String, Object> values, final String fieldName, final Consumer<String> consumer) {
-        Object value = values.get(fieldName);
-        if (null != value) {
-            consumer.accept(String.valueOf(value));
-        }
-    }
-    
-    private void applyStringArgument(final WorkflowPlanningArguments workflowPlanningArguments, final String fieldName, final Consumer<String> consumer) {
-        String value = workflowPlanningArguments.getStringArgument(fieldName);
-        if (!value.isEmpty()) {
-            consumer.accept(value);
-        }
-    }
-    
     private void applyMapField(final Map<String, Object> values, final String fieldName, final Consumer<Map<String, String>> consumer) {
         Object value = values.get(fieldName);
         if (value instanceof Map) {
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/PlanReadwriteSplittingStatusToolHandler.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/PlanReadwriteSplittingStatusToolHandler.java
index 1ca662723d2..15f8f89971c 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/PlanReadwriteSplittingStatusToolHandler.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/PlanReadwriteSplittingStatusToolHandler.java
@@ -35,7 +35,6 @@ import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.function.Consumer;
 
 /**
  * Tool handler for readwrite-splitting storage-unit status workflow planning.
@@ -143,28 +142,15 @@ public final class PlanReadwriteSplittingStatusToolHandler implements MCPToolHan
     }
     
     private void bindFeatureArguments(final ReadwriteSplittingStatusWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
-        applyStringArgument(workflowPlanningArguments, ReadwriteSplittingFeatureDefinition.RULE_FIELD, request::setRuleName);
-        applyStringArgument(workflowPlanningArguments, ReadwriteSplittingFeatureDefinition.STORAGE_UNIT_FIELD, request::setStorageUnit);
-        applyStringArgument(workflowPlanningArguments, ReadwriteSplittingFeatureDefinition.TARGET_STATUS_FIELD, request::setTargetStatus);
+        workflowPlanningArguments.applyStringArgument(ReadwriteSplittingFeatureDefinition.RULE_FIELD, request::setRuleName);
+        workflowPlanningArguments.applyStringArgument(ReadwriteSplittingFeatureDefinition.STORAGE_UNIT_FIELD, request::setStorageUnit);
+        workflowPlanningArguments.applyStringArgument(ReadwriteSplittingFeatureDefinition.TARGET_STATUS_FIELD, request::setTargetStatus);
     }
     
     private void applyStructuredIntentEvidence(final ReadwriteSplittingStatusWorkflowRequest request, final Map<String, Object> structuredIntentEvidence) {
-        applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.RULE_FIELD, request::setRuleName);
-        applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.STORAGE_UNIT_FIELD, request::setStorageUnit);
-        applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.TARGET_STATUS_FIELD, request::setTargetStatus);
+        WorkflowRequestBinder.applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.RULE_FIELD, request::setRuleName);
+        WorkflowRequestBinder.applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.STORAGE_UNIT_FIELD, request::setStorageUnit);
+        WorkflowRequestBinder.applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.TARGET_STATUS_FIELD, request::setTargetStatus);
     }
     
-    private void applyStringField(final Map<String, Object> values, final String fieldName, final Consumer<String> consumer) {
-        Object value = values.get(fieldName);
-        if (null != value) {
-            consumer.accept(String.valueOf(value));
-        }
-    }
-    
-    private void applyStringArgument(final WorkflowPlanningArguments workflowPlanningArguments, final String fieldName, final Consumer<String> consumer) {
-        String value = workflowPlanningArguments.getStringArgument(fieldName);
-        if (!value.isEmpty()) {
-            consumer.accept(value);
-        }
-    }
 }
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmRecommendationService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmRecommendationService.java
index e9871ceb923..b9bdffbe3bb 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmRecommendationService.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmRecommendationService.java
@@ -21,6 +21,7 @@ import org.apache.shardingsphere.mcp.feature.readwritesplitting.tool.model.Readw
 import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowAlgorithmUtils;
 
 import java.util.List;
@@ -47,14 +48,14 @@ public final class ReadwriteSplittingAlgorithmRecommendationService {
             if (actualAlgorithmRows.isEmpty() || WorkflowAlgorithmUtils.containsAlgorithm(actualAlgorithmRows, actualAlgorithmType, "type", "name")) {
                 return List.of(createCandidate(actualAlgorithmType, 100, "User specified load-balance algorithm."));
             }
-            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM,
                     String.format("Load-balance algorithm `%s` is not visible from the current Proxy.", actualAlgorithmType),
                     "Choose an available load-balance algorithm.", false, Map.of()));
             return List.of();
         }
         String recommendedType = resolveRecommendedAlgorithm(actualAlgorithmRows);
         if (recommendedType.isEmpty()) {
-            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM,
                     "No load-balance algorithm is available from the current Proxy.", "Install or expose at least one load-balance algorithm.", false, Map.of()));
             return List.of();
         }
@@ -62,7 +63,7 @@ public final class ReadwriteSplittingAlgorithmRecommendationService {
     }
     
     private AlgorithmCandidate createCandidate(final String algorithmType, final int score, final String reason) {
-        return new AlgorithmCandidate("primary", algorithmType, null, null, null, score, reason, "");
+        return AlgorithmCandidate.builder().algorithmRole("primary").algorithmType(algorithmType).recommendationScore(score).recommendationReason(reason).riskNotes("").build();
     }
     
     private String resolveRecommendedAlgorithm(final List<Map<String, Object>> algorithmRows) {
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleDistSQLPlanningService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleDistSQLPlanningService.java
index 7423bf13200..5c6bc1e6ad8 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleDistSQLPlanningService.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleDistSQLPlanningService.java
@@ -19,6 +19,7 @@ package org.apache.shardingsphere.mcp.feature.readwritesplitting.tool.service;
 
 import org.apache.shardingsphere.mcp.feature.readwritesplitting.tool.model.ReadwriteSplittingRuleWorkflowRequest;
 import org.apache.shardingsphere.mcp.support.workflow.model.RuleArtifact;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
 
 import java.util.Locale;
@@ -36,7 +37,7 @@ public final class ReadwriteSplittingRuleDistSQLPlanningService {
      * @return rule artifact
      */
     public RuleArtifact planCreateRule(final ReadwriteSplittingRuleWorkflowRequest request) {
-        return new RuleArtifact("create", String.format("CREATE READWRITE_SPLITTING RULE %s", createRuleDefinition(request)));
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_CREATE, String.format("CREATE READWRITE_SPLITTING RULE %s", createRuleDefinition(request)));
     }
     
     /**
@@ -46,7 +47,7 @@ public final class ReadwriteSplittingRuleDistSQLPlanningService {
      * @return rule artifact
      */
     public RuleArtifact planAlterRule(final ReadwriteSplittingRuleWorkflowRequest request) {
-        return new RuleArtifact("alter", String.format("ALTER READWRITE_SPLITTING RULE %s", createRuleDefinition(request)));
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_ALTER, String.format("ALTER READWRITE_SPLITTING RULE %s", createRuleDefinition(request)));
     }
     
     /**
@@ -56,7 +57,7 @@ public final class ReadwriteSplittingRuleDistSQLPlanningService {
      * @return rule artifact
      */
     public RuleArtifact planDropRule(final String ruleName) {
-        return new RuleArtifact("drop", String.format("DROP READWRITE_SPLITTING RULE %s", WorkflowSQLUtils.formatGeneratedRuleDistSQLIdentifier(ruleName)));
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, String.format("DROP READWRITE_SPLITTING RULE %s", WorkflowSQLUtils.formatGeneratedRuleDistSQLIdentifier(ruleName)));
     }
     
     private String createRuleDefinition(final ReadwriteSplittingRuleWorkflowRequest request) {
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java
index f88df54e3d4..906a0fdf346 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java
@@ -108,15 +108,18 @@ public final class ReadwriteSplittingRuleWorkflowPlanningService {
     private boolean ensurePlanningContext(final ReadwriteSplittingRuleWorkflowRequest request, final ClarifiedIntent clarifiedIntent, final WorkflowContextSnapshot snapshot) {
         if (request.getDatabase().isEmpty()) {
             clarifiedIntent.getClarificationMessages().add("Please provide logical database first.");
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DATABASE_REQUIRED, "error", "intaking",
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DATABASE_REQUIRED, "error", WorkflowLifecycle.STEP_INTAKING,
                     "Database is required before planning readwrite-splitting rule DistSQL.", "Provide the logical database name.", true, Map.of()));
             snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
             return false;
         }
-        if (!planningSupport.ensureOptionalSupportedIdentifiers("database", List.of(request.getDatabase()), snapshot, "intaking")
-                || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.RULE_FIELD, List.of(request.getRuleName()), snapshot, "intaking")
-                || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.WRITE_STORAGE_UNIT_FIELD, List.of(request.getWriteStorageUnit()), snapshot, "intaking")
-                || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.READ_STORAGE_UNITS_FIELD, request.getReadStorageUnits(), snapshot, "intaking")) {
+        if (!planningSupport.ensureOptionalSupportedIdentifiers("database", List.of(request.getDatabase()), snapshot, WorkflowLifecycle.STEP_INTAKING)
+                || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.RULE_FIELD, List.of(request.getRuleName()),
+                        snapshot, WorkflowLifecycle.STEP_INTAKING)
+                || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.WRITE_STORAGE_UNIT_FIELD, List.of(request.getWriteStorageUnit()),
+                        snapshot, WorkflowLifecycle.STEP_INTAKING)
+                || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.READ_STORAGE_UNITS_FIELD, request.getReadStorageUnits(),
+                        snapshot, WorkflowLifecycle.STEP_INTAKING)) {
             snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
             return false;
         }
@@ -140,7 +143,7 @@ public final class ReadwriteSplittingRuleWorkflowPlanningService {
         }
         if (!missingInputs.isEmpty()) {
             clarifiedIntent.getClarificationMessages().add("Please provide readwrite-splitting rule name, storage units and strategy fields required by DistSQL.");
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_INPUT_REQUIRED, "error", "intaking",
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_INPUT_REQUIRED, "error", WorkflowLifecycle.STEP_INTAKING,
                     "Readwrite-splitting rule DistSQL requires explicit structured inputs.", "Provide the missing fields instead of relying on inferred storage units.", true,
                     Map.of("missing_inputs", missingInputs)));
             snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
@@ -157,18 +160,18 @@ public final class ReadwriteSplittingRuleWorkflowPlanningService {
                                          final WorkflowContextSnapshot snapshot, final MCPFeatureQueryFacade queryFacade) {
         boolean ruleExists = containsRule(rules, queryFacade, request.getDatabase(), request.getRuleName());
         String operationType = clarifiedIntent.getOperationType().toLowerCase(Locale.ENGLISH);
-        if ("create".equals(operationType) && ruleExists) {
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", "discovering",
+        if (WorkflowLifecycle.OPERATION_CREATE.equals(operationType) && ruleExists) {
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", WorkflowLifecycle.STEP_DISCOVERING,
                     String.format("Readwrite-splitting rule `%s` already exists.", request.getRuleName()), "Use alter instead of create.", false, Map.of("rule", request.getRuleName())));
             return false;
         }
-        if ("alter".equals(operationType) && !ruleExists) {
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", "discovering",
+        if (WorkflowLifecycle.OPERATION_ALTER.equals(operationType) && !ruleExists) {
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", WorkflowLifecycle.STEP_DISCOVERING,
                     String.format("Readwrite-splitting rule `%s` does not exist.", request.getRuleName()), "Use create instead of alter.", false, Map.of("rule", request.getRuleName())));
             return false;
         }
         if (WorkflowLifecycle.OPERATION_DROP.equals(operationType) && !ruleExists) {
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DROP_TARGET_RULE_NOT_FOUND, "error", "discovering",
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DROP_TARGET_RULE_NOT_FOUND, "error", WorkflowLifecycle.STEP_DISCOVERING,
                     String.format("Readwrite-splitting rule `%s` does not exist.", request.getRuleName()), "Confirm target rule or skip the drop request.", false,
                     Map.of("rule", request.getRuleName())));
             return false;
@@ -196,7 +199,7 @@ public final class ReadwriteSplittingRuleWorkflowPlanningService {
     private void addRuleArtifact(final WorkflowContextSnapshot snapshot, final ReadwriteSplittingRuleWorkflowRequest request, final String operationType) {
         if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(operationType)) {
             snapshot.getRuleArtifacts().add(distSQLPlanningService.planDropRule(request.getRuleName()));
-        } else if ("alter".equalsIgnoreCase(operationType)) {
+        } else if (WorkflowLifecycle.OPERATION_ALTER.equalsIgnoreCase(operationType)) {
             snapshot.getRuleArtifacts().add(distSQLPlanningService.planAlterRule(request));
         } else {
             snapshot.getRuleArtifacts().add(distSQLPlanningService.planCreateRule(request));
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningService.java
index 3473eb6bcc8..d0b2f2acf49 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningService.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningService.java
@@ -101,14 +101,16 @@ public final class ReadwriteSplittingStatusWorkflowPlanningService {
     private boolean ensurePlanningContext(final ReadwriteSplittingStatusWorkflowRequest request, final ClarifiedIntent clarifiedIntent, final WorkflowContextSnapshot snapshot) {
         if (request.getDatabase().isEmpty()) {
             clarifiedIntent.getClarificationMessages().add("Please provide logical database first.");
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DATABASE_REQUIRED, "error", "intaking",
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DATABASE_REQUIRED, "error", WorkflowLifecycle.STEP_INTAKING,
                     "Database is required before planning readwrite-splitting status DistSQL.", "Provide the logical database name.", true, Map.of()));
             snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
             return false;
         }
-        if (!planningSupport.ensureOptionalSupportedIdentifiers("database", List.of(request.getDatabase()), snapshot, "intaking")
-                || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.RULE_FIELD, List.of(request.getRuleName()), snapshot, "intaking")
-                || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.STORAGE_UNIT_FIELD, List.of(request.getStorageUnit()), snapshot, "intaking")) {
+        if (!planningSupport.ensureOptionalSupportedIdentifiers("database", List.of(request.getDatabase()), snapshot, WorkflowLifecycle.STEP_INTAKING)
+                || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.RULE_FIELD, List.of(request.getRuleName()),
+                        snapshot, WorkflowLifecycle.STEP_INTAKING)
+                || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.STORAGE_UNIT_FIELD, List.of(request.getStorageUnit()),
+                        snapshot, WorkflowLifecycle.STEP_INTAKING)) {
             snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
             return false;
         }
@@ -118,7 +120,7 @@ public final class ReadwriteSplittingStatusWorkflowPlanningService {
         addMissingInput(missingInputs, distSQLPlanningService.resolveStatusOperation(request), ReadwriteSplittingFeatureDefinition.TARGET_STATUS_FIELD);
         if (!missingInputs.isEmpty()) {
             clarifiedIntent.getClarificationMessages().add("Please provide readwrite-splitting rule name, read storage unit and target status.");
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_INPUT_REQUIRED, "error", "intaking",
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_INPUT_REQUIRED, "error", WorkflowLifecycle.STEP_INTAKING,
                     "Readwrite-splitting status DistSQL requires explicit rule, storage unit and status inputs.",
                     "Provide target_status as enable or disable.", true, Map.of("missing_inputs", missingInputs)));
             snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
@@ -138,7 +140,7 @@ public final class ReadwriteSplittingStatusWorkflowPlanningService {
         if (statuses.stream().anyMatch(each -> matchesStatusTarget(request, each, queryFacade))) {
             return true;
         }
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DROP_TARGET_RULE_NOT_FOUND, "error", "discovering",
+        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DROP_TARGET_RULE_NOT_FOUND, "error", WorkflowLifecycle.STEP_DISCOVERING,
                 String.format("Readwrite-splitting status target `%s.%s` does not exist.", request.getRuleName(), request.getStorageUnit()),
                 "Confirm the target rule and read storage unit before planning status changes.", false,
                 Map.of("rule", request.getRuleName(), "storage_unit", request.getStorageUnit())));
diff --git a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingWorkflowValidationServicesTest.java b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingWorkflowValidationServicesTest.java
index ca20c73bbec..2742206bfbb 100644
--- a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingWorkflowValidationServicesTest.java
+++ b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingWorkflowValidationServicesTest.java
@@ -33,7 +33,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.InteractionPlan;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowArtifactBundle.ExecutableWorkflowArtifact;
-import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
+import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowAlgorithmUtils;
 import org.junit.jupiter.api.Test;
 import org.mockito.AdditionalAnswers;
 import org.mockito.MockedConstruction;
@@ -90,7 +90,7 @@ class ReadwriteSplittingWorkflowValidationServicesTest {
         request.setLoadBalancerType("RANDOM");
         try (MockedStatic<TypedSPILoader> mockedStatic = mockStatic(TypedSPILoader.class)) {
             mockedStatic.when(() -> TypedSPILoader.checkService(LoadBalanceAlgorithm.class, "RANDOM",
-                    WorkflowSQLUtils.createProperties(request.getLoadBalancerProperties()))).thenThrow(new IllegalArgumentException("unavailable"));
+                    WorkflowAlgorithmUtils.createProperties(request.getLoadBalancerProperties()))).thenThrow(new IllegalArgumentException("unavailable"));
             List<Map<String, Object>> actual = new ReadwriteSplittingRuleWorkflowValidationService().validate(snapshot, List.of(createRuleDistSQLArtifact()));
             assertThat(actual.size(), is(1));
             assertThat(actual.getFirst().get("code"), is(WorkflowIssueCode.SQL_EXECUTABILITY_FAILED));
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/handler/PlanDefaultShadowAlgorithmToolHandler.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/handler/PlanDefaultShadowAlgorithmToolHandler.java
index c64262c2bc2..62e9d92f1b1 100644
--- a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/handler/PlanDefaultShadowAlgorithmToolHandler.java
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/handler/PlanDefaultShadowAlgorithmToolHandler.java
@@ -33,7 +33,6 @@ import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowRequestBin
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.function.Consumer;
 
 /**
  * Tool handler for default shadow algorithm workflow planning.
@@ -62,29 +61,15 @@ public final class PlanDefaultShadowAlgorithmToolHandler implements MCPToolHandl
     }
     
     private void bindFeatureArguments(final ShadowDefaultAlgorithmWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
-        applyStringArgument(workflowPlanningArguments, ShadowFeatureDefinition.ALGORITHM_TYPE_FIELD, request::setAlgorithmType);
+        workflowPlanningArguments.applyStringArgument(ShadowFeatureDefinition.ALGORITHM_TYPE_FIELD, request::setAlgorithmType);
         request.putAlgorithmProperties(workflowPlanningArguments.getMapArgument(ShadowFeatureDefinition.ALGORITHM_PROPERTIES_FIELD));
     }
     
     private void applyStructuredIntentEvidence(final ShadowDefaultAlgorithmWorkflowRequest request, final Map<String, Object> structuredIntentEvidence) {
-        applyStringField(structuredIntentEvidence, ShadowFeatureDefinition.ALGORITHM_TYPE_FIELD, request::setAlgorithmType);
+        WorkflowRequestBinder.applyStringField(structuredIntentEvidence, ShadowFeatureDefinition.ALGORITHM_TYPE_FIELD, request::setAlgorithmType);
         applyMapField(structuredIntentEvidence, request);
     }
     
-    private void applyStringField(final Map<String, Object> values, final String fieldName, final Consumer<String> consumer) {
-        Object value = values.get(fieldName);
-        if (null != value) {
-            consumer.accept(String.valueOf(value));
-        }
-    }
-    
-    private void applyStringArgument(final WorkflowPlanningArguments workflowPlanningArguments, final String fieldName, final Consumer<String> consumer) {
-        String value = workflowPlanningArguments.getStringArgument(fieldName);
-        if (!value.isEmpty()) {
-            consumer.accept(value);
-        }
-    }
-    
     private void applyMapField(final Map<String, Object> values, final ShadowDefaultAlgorithmWorkflowRequest request) {
         Object value = values.get(ShadowFeatureDefinition.ALGORITHM_PROPERTIES_FIELD);
         if (value instanceof Map) {
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/handler/PlanShadowAlgorithmCleanupToolHandler.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/handler/PlanShadowAlgorithmCleanupToolHandler.java
index 938aaefac8e..d09d59ee866 100644
--- a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/handler/PlanShadowAlgorithmCleanupToolHandler.java
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/handler/PlanShadowAlgorithmCleanupToolHandler.java
@@ -31,7 +31,6 @@ import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowPlanningAr
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowRequestBinder;
 
 import java.util.Map;
-import java.util.function.Consumer;
 
 /**
  * Tool handler for shadow algorithm cleanup workflow planning.
@@ -60,24 +59,11 @@ public final class PlanShadowAlgorithmCleanupToolHandler implements MCPToolHandl
     }
     
     private void bindFeatureArguments(final ShadowAlgorithmCleanupWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
-        applyStringArgument(workflowPlanningArguments, ShadowFeatureDefinition.ALGORITHM_NAME_FIELD, request::setAlgorithmName);
+        workflowPlanningArguments.applyStringArgument(ShadowFeatureDefinition.ALGORITHM_NAME_FIELD, request::setAlgorithmName);
     }
     
     private void applyStructuredIntentEvidence(final ShadowAlgorithmCleanupWorkflowRequest request, final Map<String, Object> structuredIntentEvidence) {
-        applyStringField(structuredIntentEvidence, ShadowFeatureDefinition.ALGORITHM_NAME_FIELD, request::setAlgorithmName);
+        WorkflowRequestBinder.applyStringField(structuredIntentEvidence, ShadowFeatureDefinition.ALGORITHM_NAME_FIELD, request::setAlgorithmName);
     }
     
-    private void applyStringField(final Map<String, Object> values, final String fieldName, final Consumer<String> consumer) {
-        Object value = values.get(fieldName);
-        if (null != value) {
-            consumer.accept(String.valueOf(value));
-        }
-    }
-    
-    private void applyStringArgument(final WorkflowPlanningArguments workflowPlanningArguments, final String fieldName, final Consumer<String> consumer) {
-        String value = workflowPlanningArguments.getStringArgument(fieldName);
-        if (!value.isEmpty()) {
-            consumer.accept(value);
-        }
-    }
 }
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/handler/PlanShadowRuleToolHandler.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/handler/PlanShadowRuleToolHandler.java
index 9c067778725..d324e604877 100644
--- a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/handler/PlanShadowRuleToolHandler.java
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/handler/PlanShadowRuleToolHandler.java
@@ -33,7 +33,6 @@ import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowRequestBin
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.function.Consumer;
 
 /**
  * Tool handler for shadow rule workflow planning.
@@ -62,37 +61,23 @@ public final class PlanShadowRuleToolHandler implements MCPToolHandler<MCPWorkfl
     }
     
     private void bindFeatureArguments(final ShadowRuleWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
-        applyStringArgument(workflowPlanningArguments, ShadowFeatureDefinition.RULE_FIELD, request::setRuleName);
-        applyStringArgument(workflowPlanningArguments, ShadowFeatureDefinition.SOURCE_STORAGE_UNIT_FIELD, request::setSourceStorageUnit);
-        applyStringArgument(workflowPlanningArguments, ShadowFeatureDefinition.SHADOW_STORAGE_UNIT_FIELD, request::setShadowStorageUnit);
-        applyStringArgument(workflowPlanningArguments, ShadowFeatureDefinition.TABLE_FIELD, request::setTableName);
-        applyStringArgument(workflowPlanningArguments, ShadowFeatureDefinition.ALGORITHM_TYPE_FIELD, request::setAlgorithmType);
+        workflowPlanningArguments.applyStringArgument(ShadowFeatureDefinition.RULE_FIELD, request::setRuleName);
+        workflowPlanningArguments.applyStringArgument(ShadowFeatureDefinition.SOURCE_STORAGE_UNIT_FIELD, request::setSourceStorageUnit);
+        workflowPlanningArguments.applyStringArgument(ShadowFeatureDefinition.SHADOW_STORAGE_UNIT_FIELD, request::setShadowStorageUnit);
+        workflowPlanningArguments.applyStringArgument(ShadowFeatureDefinition.TABLE_FIELD, request::setTableName);
+        workflowPlanningArguments.applyStringArgument(ShadowFeatureDefinition.ALGORITHM_TYPE_FIELD, request::setAlgorithmType);
         request.putAlgorithmProperties(workflowPlanningArguments.getMapArgument(ShadowFeatureDefinition.ALGORITHM_PROPERTIES_FIELD));
     }
     
     private void applyStructuredIntentEvidence(final ShadowRuleWorkflowRequest request, final Map<String, Object> structuredIntentEvidence) {
-        applyStringField(structuredIntentEvidence, ShadowFeatureDefinition.RULE_FIELD, request::setRuleName);
-        applyStringField(structuredIntentEvidence, ShadowFeatureDefinition.SOURCE_STORAGE_UNIT_FIELD, request::setSourceStorageUnit);
-        applyStringField(structuredIntentEvidence, ShadowFeatureDefinition.SHADOW_STORAGE_UNIT_FIELD, request::setShadowStorageUnit);
-        applyStringField(structuredIntentEvidence, ShadowFeatureDefinition.TABLE_FIELD, request::setTableName);
-        applyStringField(structuredIntentEvidence, ShadowFeatureDefinition.ALGORITHM_TYPE_FIELD, request::setAlgorithmType);
+        WorkflowRequestBinder.applyStringField(structuredIntentEvidence, ShadowFeatureDefinition.RULE_FIELD, request::setRuleName);
+        WorkflowRequestBinder.applyStringField(structuredIntentEvidence, ShadowFeatureDefinition.SOURCE_STORAGE_UNIT_FIELD, request::setSourceStorageUnit);
+        WorkflowRequestBinder.applyStringField(structuredIntentEvidence, ShadowFeatureDefinition.SHADOW_STORAGE_UNIT_FIELD, request::setShadowStorageUnit);
+        WorkflowRequestBinder.applyStringField(structuredIntentEvidence, ShadowFeatureDefinition.TABLE_FIELD, request::setTableName);
+        WorkflowRequestBinder.applyStringField(structuredIntentEvidence, ShadowFeatureDefinition.ALGORITHM_TYPE_FIELD, request::setAlgorithmType);
         applyMapField(structuredIntentEvidence, request);
     }
     
-    private void applyStringField(final Map<String, Object> values, final String fieldName, final Consumer<String> consumer) {
-        Object value = values.get(fieldName);
-        if (null != value) {
-            consumer.accept(String.valueOf(value));
-        }
-    }
-    
-    private void applyStringArgument(final WorkflowPlanningArguments workflowPlanningArguments, final String fieldName, final Consumer<String> consumer) {
-        String value = workflowPlanningArguments.getStringArgument(fieldName);
-        if (!value.isEmpty()) {
-            consumer.accept(value);
-        }
-    }
-    
     private void applyMapField(final Map<String, Object> values, final ShadowRuleWorkflowRequest request) {
         Object value = values.get(ShadowFeatureDefinition.ALGORITHM_PROPERTIES_FIELD);
         if (value instanceof Map) {
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmRecommendationService.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmRecommendationService.java
index e7f74aa11f4..6544222e651 100644
--- a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmRecommendationService.java
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmRecommendationService.java
@@ -20,6 +20,7 @@ package org.apache.shardingsphere.mcp.feature.shadow.tool.service;
 import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowAlgorithmUtils;
 
@@ -47,14 +48,14 @@ public final class ShadowAlgorithmRecommendationService {
             if (actualAlgorithmRows.isEmpty() || WorkflowAlgorithmUtils.containsAlgorithm(actualAlgorithmRows, actualAlgorithmType, "type", "name")) {
                 return List.of(createCandidate(actualAlgorithmType, 100, "User specified shadow algorithm."));
             }
-            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM,
                     String.format("Shadow algorithm `%s` is not visible from the current Proxy.", actualAlgorithmType),
                     "Choose an available shadow algorithm.", false, Map.of()));
             return List.of();
         }
         String recommendedType = resolveRecommendedAlgorithm(actualAlgorithmRows);
         if (recommendedType.isEmpty()) {
-            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+            issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM,
                     "No shadow algorithm is available from the current Proxy.", "Install or expose at least one shadow algorithm.", false, Map.of()));
             return List.of();
         }
@@ -62,7 +63,7 @@ public final class ShadowAlgorithmRecommendationService {
     }
     
     private AlgorithmCandidate createCandidate(final String algorithmType, final int score, final String reason) {
-        return new AlgorithmCandidate("primary", algorithmType, null, null, null, score, reason, "");
+        return AlgorithmCandidate.builder().algorithmRole("primary").algorithmType(algorithmType).recommendationScore(score).recommendationReason(reason).riskNotes("").build();
     }
     
     private String resolveRecommendedAlgorithm(final List<Map<String, Object>> algorithmRows) {
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowDistSQLPlanningService.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowDistSQLPlanningService.java
index 7172e3b5cc5..25ccc0fb20f 100644
--- a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowDistSQLPlanningService.java
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowDistSQLPlanningService.java
@@ -20,6 +20,7 @@ package org.apache.shardingsphere.mcp.feature.shadow.tool.service;
 import org.apache.shardingsphere.mcp.feature.shadow.tool.model.ShadowDefaultAlgorithmWorkflowRequest;
 import org.apache.shardingsphere.mcp.feature.shadow.tool.model.ShadowRuleWorkflowRequest;
 import org.apache.shardingsphere.mcp.support.workflow.model.RuleArtifact;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
 
 /**
@@ -34,7 +35,7 @@ public final class ShadowDistSQLPlanningService {
      * @return rule artifact
      */
     public RuleArtifact planCreateRule(final ShadowRuleWorkflowRequest request) {
-        return new RuleArtifact("create", String.format("CREATE SHADOW RULE %s(SOURCE=%s, SHADOW=%s, %s(%s))",
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_CREATE, String.format("CREATE SHADOW RULE %s(SOURCE=%s, SHADOW=%s, %s(%s))",
                 WorkflowSQLUtils.formatGeneratedRuleDistSQLIdentifier(request.getRuleName()),
                 WorkflowSQLUtils.formatGeneratedRuleDistSQLIdentifier(request.getSourceStorageUnit()),
                 WorkflowSQLUtils.formatGeneratedRuleDistSQLIdentifier(request.getShadowStorageUnit()),
@@ -49,7 +50,7 @@ public final class ShadowDistSQLPlanningService {
      * @return rule artifact
      */
     public RuleArtifact planAlterRule(final ShadowRuleWorkflowRequest request) {
-        return new RuleArtifact("alter", String.format("ALTER SHADOW RULE %s(SOURCE=%s, SHADOW=%s, %s(%s))",
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_ALTER, String.format("ALTER SHADOW RULE %s(SOURCE=%s, SHADOW=%s, %s(%s))",
                 WorkflowSQLUtils.formatGeneratedRuleDistSQLIdentifier(request.getRuleName()),
                 WorkflowSQLUtils.formatGeneratedRuleDistSQLIdentifier(request.getSourceStorageUnit()),
                 WorkflowSQLUtils.formatGeneratedRuleDistSQLIdentifier(request.getShadowStorageUnit()),
@@ -64,7 +65,7 @@ public final class ShadowDistSQLPlanningService {
      * @return rule artifact
      */
     public RuleArtifact planDropRule(final String ruleName) {
-        return new RuleArtifact("drop", String.format("DROP SHADOW RULE %s", WorkflowSQLUtils.formatGeneratedRuleDistSQLIdentifier(ruleName)));
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, String.format("DROP SHADOW RULE %s", WorkflowSQLUtils.formatGeneratedRuleDistSQLIdentifier(ruleName)));
     }
     
     /**
@@ -74,7 +75,7 @@ public final class ShadowDistSQLPlanningService {
      * @return rule artifact
      */
     public RuleArtifact planCreateDefaultAlgorithm(final ShadowDefaultAlgorithmWorkflowRequest request) {
-        return new RuleArtifact("create", String.format("CREATE DEFAULT SHADOW ALGORITHM %s",
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_CREATE, String.format("CREATE DEFAULT SHADOW ALGORITHM %s",
                 WorkflowSQLUtils.createAlgorithmFragment(request.getAlgorithmType(), request.getAlgorithmProperties())));
     }
     
@@ -85,7 +86,7 @@ public final class ShadowDistSQLPlanningService {
      * @return rule artifact
      */
     public RuleArtifact planAlterDefaultAlgorithm(final ShadowDefaultAlgorithmWorkflowRequest request) {
-        return new RuleArtifact("alter", String.format("ALTER DEFAULT SHADOW ALGORITHM %s",
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_ALTER, String.format("ALTER DEFAULT SHADOW ALGORITHM %s",
                 WorkflowSQLUtils.createAlgorithmFragment(request.getAlgorithmType(), request.getAlgorithmProperties())));
     }
     
@@ -95,7 +96,7 @@ public final class ShadowDistSQLPlanningService {
      * @return rule artifact
      */
     public RuleArtifact planDropDefaultAlgorithm() {
-        return new RuleArtifact("drop", "DROP DEFAULT SHADOW ALGORITHM");
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, "DROP DEFAULT SHADOW ALGORITHM");
     }
     
     /**
@@ -105,6 +106,6 @@ public final class ShadowDistSQLPlanningService {
      * @return rule artifact
      */
     public RuleArtifact planDropAlgorithm(final String algorithmName) {
-        return new RuleArtifact("drop", String.format("DROP SHADOW ALGORITHM %s", WorkflowSQLUtils.formatGeneratedRuleDistSQLIdentifier(algorithmName)));
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, String.format("DROP SHADOW ALGORITHM %s", WorkflowSQLUtils.formatGeneratedRuleDistSQLIdentifier(algorithmName)));
     }
 }
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java
index 7085ed878ae..24cf18c2e70 100644
--- a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java
@@ -98,7 +98,7 @@ public final class ShadowWorkflowPlanningService {
                                             final ShadowRuleWorkflowRequest request) {
         WorkflowContextSnapshot result = workflowSessionContext.getOrCreate(sessionId, request.getPlanId());
         ShadowRuleWorkflowRequest mergedRequest = prepareSnapshot(result, request, ShadowFeatureDefinition.RULE_WORKFLOW_KIND,
-                resolveIntent(request, "create"), "Shadow rule workflow plan.", RULE_INTERACTION_STEPS);
+                resolveIntent(request, WorkflowLifecycle.OPERATION_CREATE), "Shadow rule workflow plan.", RULE_INTERACTION_STEPS);
         planningSupport.applyResolvedIntent(mergedRequest, result.getClarifiedIntent());
         planAlgorithmsIfRequired(queryFacade, mergedRequest, result);
         if (!ensureRulePlanningContext(mergedRequest, result.getClarifiedIntent(), result)) {
@@ -128,7 +128,7 @@ public final class ShadowWorkflowPlanningService {
                                                         final String sessionId, final ShadowDefaultAlgorithmWorkflowRequest request) {
         WorkflowContextSnapshot result = workflowSessionContext.getOrCreate(sessionId, request.getPlanId());
         ShadowDefaultAlgorithmWorkflowRequest mergedRequest = prepareSnapshot(result, request, ShadowFeatureDefinition.DEFAULT_ALGORITHM_WORKFLOW_KIND,
-                resolveIntent(request, "create"), "Default shadow algorithm workflow plan.", DEFAULT_ALGORITHM_INTERACTION_STEPS);
+                resolveIntent(request, WorkflowLifecycle.OPERATION_CREATE), "Default shadow algorithm workflow plan.", DEFAULT_ALGORITHM_INTERACTION_STEPS);
         planningSupport.applyResolvedIntent(mergedRequest, result.getClarifiedIntent());
         planAlgorithmsIfRequired(queryFacade, mergedRequest, result);
         if (!ensureDefaultAlgorithmPlanningContext(mergedRequest, result.getClarifiedIntent(), result)) {
@@ -238,7 +238,7 @@ public final class ShadowWorkflowPlanningService {
     private boolean ensureDatabase(final WorkflowRequest request, final ClarifiedIntent clarifiedIntent, final WorkflowContextSnapshot snapshot) {
         if (request.getDatabase().isEmpty()) {
             clarifiedIntent.getClarificationMessages().add("Please provide logical database first.");
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DATABASE_REQUIRED, "error", "intaking",
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DATABASE_REQUIRED, "error", WorkflowLifecycle.STEP_INTAKING,
                     "Database is required before planning shadow DistSQL.", "Provide the logical database name.", true, Map.of()));
             snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
             return false;
@@ -251,7 +251,7 @@ public final class ShadowWorkflowPlanningService {
     }
     
     private boolean ensureSupportedIdentifiers(final WorkflowContextSnapshot snapshot, final String... identifiers) {
-        if (planningSupport.ensureOptionalSupportedIdentifiers("", List.of(identifiers), snapshot, "intaking")) {
+        if (planningSupport.ensureOptionalSupportedIdentifiers("", List.of(identifiers), snapshot, WorkflowLifecycle.STEP_INTAKING)) {
             return true;
         }
         snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
@@ -285,7 +285,7 @@ public final class ShadowWorkflowPlanningService {
             return true;
         }
         clarifiedIntent.getClarificationMessages().add(message);
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_INPUT_REQUIRED, "error", "intaking", message,
+        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_INPUT_REQUIRED, "error", WorkflowLifecycle.STEP_INTAKING, message,
                 "Provide the missing fields instead of guessing storage units, tables or algorithms.", true, Map.of("missing_inputs", missingInputs)));
         snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
         return false;
@@ -305,14 +305,14 @@ public final class ShadowWorkflowPlanningService {
         boolean configured = algorithms.stream().anyMatch(each -> queryFacade.isSameIdentifier(
                 request.getDatabase(), IdentifierScope.TABLE, request.getAlgorithmName(), WorkflowRuleValueUtils.getRuleValue(each, "shadow_algorithm_name")));
         if (!configured) {
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", "discovering",
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", WorkflowLifecycle.STEP_DISCOVERING,
                     String.format("Shadow algorithm `%s` is not configured.", request.getAlgorithmName()),
                     "Inspect configured shadow algorithms before cleanup.", false, Map.of("algorithm", request.getAlgorithmName())));
             return false;
         }
         if (isReferencedByTableRule(queryFacade, request.getDatabase(), request.getAlgorithmName(), tableRules)
                 || isDefaultAlgorithm(queryFacade, request.getDatabase(), request.getAlgorithmName(), defaultAlgorithm)) {
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", "discovering",
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", WorkflowLifecycle.STEP_DISCOVERING,
                     String.format("Shadow algorithm `%s` is still referenced.", request.getAlgorithmName()),
                     "Remove table-rule or default-algorithm references before cleanup.", false, Map.of("algorithm", request.getAlgorithmName())));
             return false;
@@ -349,7 +349,7 @@ public final class ShadowWorkflowPlanningService {
     private void addRuleArtifact(final WorkflowContextSnapshot snapshot, final ShadowRuleWorkflowRequest request, final String operationType) {
         if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(operationType)) {
             snapshot.getRuleArtifacts().add(distSQLPlanningService.planDropRule(request.getRuleName()));
-        } else if ("alter".equalsIgnoreCase(operationType)) {
+        } else if (WorkflowLifecycle.OPERATION_ALTER.equalsIgnoreCase(operationType)) {
             snapshot.getRuleArtifacts().add(distSQLPlanningService.planAlterRule(request));
         } else {
             snapshot.getRuleArtifacts().add(distSQLPlanningService.planCreateRule(request));
@@ -359,7 +359,7 @@ public final class ShadowWorkflowPlanningService {
     private void addDefaultAlgorithmArtifact(final WorkflowContextSnapshot snapshot, final ShadowDefaultAlgorithmWorkflowRequest request, final String operationType) {
         if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(operationType)) {
             snapshot.getRuleArtifacts().add(distSQLPlanningService.planDropDefaultAlgorithm());
-        } else if ("alter".equalsIgnoreCase(operationType)) {
+        } else if (WorkflowLifecycle.OPERATION_ALTER.equalsIgnoreCase(operationType)) {
             snapshot.getRuleArtifacts().add(distSQLPlanningService.planAlterDefaultAlgorithm(request));
         } else {
             snapshot.getRuleArtifacts().add(distSQLPlanningService.planCreateDefaultAlgorithm(request));
diff --git a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationServiceTest.java b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationServiceTest.java
index 65b8c9afb16..de9ac21e219 100644
--- a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationServiceTest.java
+++ b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationServiceTest.java
@@ -32,7 +32,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowArtifactBundle.ExecutableWorkflowArtifact;
-import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
+import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowAlgorithmUtils;
 import org.apache.shardingsphere.shadow.spi.ShadowAlgorithm;
 import org.junit.jupiter.api.Test;
 import org.mockito.AdditionalAnswers;
@@ -88,7 +88,7 @@ class ShadowWorkflowValidationServiceTest {
         ShadowRuleWorkflowRequest request = (ShadowRuleWorkflowRequest) snapshot.getRequest();
         try (MockedStatic<TypedSPILoader> mockedStatic = mockStatic(TypedSPILoader.class)) {
             mockedStatic.when(() -> TypedSPILoader.checkService(ShadowAlgorithm.class, "VALUE_MATCH",
-                    WorkflowSQLUtils.createProperties(request.getAlgorithmProperties()))).thenThrow(new IllegalArgumentException("unavailable"));
+                    WorkflowAlgorithmUtils.createProperties(request.getAlgorithmProperties()))).thenThrow(new IllegalArgumentException("unavailable"));
             List<Map<String, Object>> actual = new ShadowWorkflowValidationService().validate(snapshot, List.of(createRuleDistSQLArtifact("CREATE SHADOW RULE `shadow_rule`(...)")));
             assertThat(actual.size(), is(1));
             assertThat(actual.getFirst().get("code"), is(WorkflowIssueCode.SQL_EXECUTABILITY_FAILED));
@@ -103,7 +103,7 @@ class ShadowWorkflowValidationServiceTest {
         WorkflowContextSnapshot snapshot = createSnapshot(request, "create");
         try (MockedStatic<TypedSPILoader> mockedStatic = mockStatic(TypedSPILoader.class)) {
             mockedStatic.when(() -> TypedSPILoader.checkService(ShadowAlgorithm.class, "SQL_HINT",
-                    WorkflowSQLUtils.createProperties(request.getAlgorithmProperties()))).thenThrow(new IllegalArgumentException("unavailable"));
+                    WorkflowAlgorithmUtils.createProperties(request.getAlgorithmProperties()))).thenThrow(new IllegalArgumentException("unavailable"));
             List<Map<String, Object>> actual = new ShadowWorkflowValidationService().validate(snapshot,
                     List.of(createRuleDistSQLArtifact("CREATE DEFAULT SHADOW ALGORITHM TYPE(NAME='sql_hint')")));
             assertThat(actual.size(), is(1));
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/ShardingPlanningRequestBinder.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/ShardingPlanningRequestBinder.java
index 68fdf35bdb7..37fa469bcee 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/ShardingPlanningRequestBinder.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/ShardingPlanningRequestBinder.java
@@ -98,122 +98,108 @@ public final class ShardingPlanningRequestBinder {
     }
     
     private void bindTableRuleArguments(final ShardingWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
-        applyStringArgument(workflowPlanningArguments, "rule", request::setRuleName);
-        applyStringArgument(workflowPlanningArguments, "data_nodes", request::setDataNodes);
-        applyStringArgument(workflowPlanningArguments, "storage_units", request::setStorageUnits);
-        applyStringArgument(workflowPlanningArguments, "strategy_type", request::setStrategyType);
-        applyStringArgument(workflowPlanningArguments, "sharding_columns", request::setShardingColumns);
-        applyStringArgument(workflowPlanningArguments, "algorithm_type", request::setAlgorithmType);
-        applyStringArgument(workflowPlanningArguments, "key_generate_column", request::setKeyGenerateColumn);
-        applyStringArgument(workflowPlanningArguments, "key_generator", request::setKeyGeneratorName);
-        applyStringArgument(workflowPlanningArguments, "key_generator_type", request::setKeyGeneratorType);
-        applyStringArgument(workflowPlanningArguments, "allow_hint_disable", request::setAllowHintDisable);
+        workflowPlanningArguments.applyStringArgument("rule", request::setRuleName);
+        workflowPlanningArguments.applyStringArgument("data_nodes", request::setDataNodes);
+        workflowPlanningArguments.applyStringArgument("storage_units", request::setStorageUnits);
+        workflowPlanningArguments.applyStringArgument("strategy_type", request::setStrategyType);
+        workflowPlanningArguments.applyStringArgument("sharding_columns", request::setShardingColumns);
+        workflowPlanningArguments.applyStringArgument("algorithm_type", request::setAlgorithmType);
+        workflowPlanningArguments.applyStringArgument("key_generate_column", request::setKeyGenerateColumn);
+        workflowPlanningArguments.applyStringArgument("key_generator", request::setKeyGeneratorName);
+        workflowPlanningArguments.applyStringArgument("key_generator_type", request::setKeyGeneratorType);
+        workflowPlanningArguments.applyStringArgument("allow_hint_disable", request::setAllowHintDisable);
         request.putAlgorithmProperties(workflowPlanningArguments.getMapArgument("algorithm_properties"));
         request.putKeyGeneratorProperties(workflowPlanningArguments.getMapArgument("key_generator_properties"));
         request.getAuditorNames().addAll(createStringList(workflowPlanningArguments.getStringArgument("auditors")));
     }
     
     private void applyTableRuleEvidence(final ShardingWorkflowRequest request, final Map<String, Object> values) {
-        applyStringField(values, "table", request::setTable);
-        applyStringField(values, "column", request::setColumn);
-        applyStringField(values, "rule", request::setRuleName);
-        applyStringField(values, "data_nodes", request::setDataNodes);
-        applyStringField(values, "storage_units", request::setStorageUnits);
-        applyStringField(values, "strategy_type", request::setStrategyType);
-        applyStringField(values, "sharding_columns", request::setShardingColumns);
-        applyStringField(values, "algorithm_type", request::setAlgorithmType);
-        applyStringField(values, "key_generate_column", request::setKeyGenerateColumn);
-        applyStringField(values, "key_generator", request::setKeyGeneratorName);
-        applyStringField(values, "key_generator_type", request::setKeyGeneratorType);
-        applyStringField(values, "allow_hint_disable", request::setAllowHintDisable);
+        WorkflowRequestBinder.applyStringField(values, "table", request::setTable);
+        WorkflowRequestBinder.applyStringField(values, "column", request::setColumn);
+        WorkflowRequestBinder.applyStringField(values, "rule", request::setRuleName);
+        WorkflowRequestBinder.applyStringField(values, "data_nodes", request::setDataNodes);
+        WorkflowRequestBinder.applyStringField(values, "storage_units", request::setStorageUnits);
+        WorkflowRequestBinder.applyStringField(values, "strategy_type", request::setStrategyType);
+        WorkflowRequestBinder.applyStringField(values, "sharding_columns", request::setShardingColumns);
+        WorkflowRequestBinder.applyStringField(values, "algorithm_type", request::setAlgorithmType);
+        WorkflowRequestBinder.applyStringField(values, "key_generate_column", request::setKeyGenerateColumn);
+        WorkflowRequestBinder.applyStringField(values, "key_generator", request::setKeyGeneratorName);
+        WorkflowRequestBinder.applyStringField(values, "key_generator_type", request::setKeyGeneratorType);
+        WorkflowRequestBinder.applyStringField(values, "allow_hint_disable", request::setAllowHintDisable);
         applyMapField(values, "algorithm_properties", request::putAlgorithmProperties);
         applyMapField(values, "key_generator_properties", request::putKeyGeneratorProperties);
         request.getAuditorNames().addAll(createStringList(values.get("auditors")));
     }
     
     private void bindTableReferenceRuleArguments(final ShardingWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
-        applyStringArgument(workflowPlanningArguments, "rule", request::setRuleName);
+        workflowPlanningArguments.applyStringArgument("rule", request::setRuleName);
         request.getReferenceTables().addAll(createStringList(workflowPlanningArguments.getStringArgument("reference_tables")));
     }
     
     private void applyTableReferenceRuleEvidence(final ShardingWorkflowRequest request, final Map<String, Object> values) {
-        applyStringField(values, "rule", request::setRuleName);
+        WorkflowRequestBinder.applyStringField(values, "rule", request::setRuleName);
         request.getReferenceTables().addAll(createStringList(values.get("reference_tables")));
     }
     
     private void bindDefaultStrategyArguments(final ShardingWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
-        applyStringArgument(workflowPlanningArguments, "default_strategy_type", request::setDefaultStrategyType);
-        applyStringArgument(workflowPlanningArguments, "strategy_type", request::setStrategyType);
-        applyStringArgument(workflowPlanningArguments, "sharding_columns", request::setShardingColumns);
-        applyStringArgument(workflowPlanningArguments, "algorithm_type", request::setAlgorithmType);
+        workflowPlanningArguments.applyStringArgument("default_strategy_type", request::setDefaultStrategyType);
+        workflowPlanningArguments.applyStringArgument("strategy_type", request::setStrategyType);
+        workflowPlanningArguments.applyStringArgument("sharding_columns", request::setShardingColumns);
+        workflowPlanningArguments.applyStringArgument("algorithm_type", request::setAlgorithmType);
         request.putAlgorithmProperties(workflowPlanningArguments.getMapArgument("algorithm_properties"));
     }
     
     private void applyDefaultStrategyEvidence(final ShardingWorkflowRequest request, final Map<String, Object> values) {
-        applyStringField(values, "column", request::setColumn);
-        applyStringField(values, "default_strategy_type", request::setDefaultStrategyType);
-        applyStringField(values, "strategy_type", request::setStrategyType);
-        applyStringField(values, "sharding_columns", request::setShardingColumns);
-        applyStringField(values, "algorithm_type", request::setAlgorithmType);
+        WorkflowRequestBinder.applyStringField(values, "column", request::setColumn);
+        WorkflowRequestBinder.applyStringField(values, "default_strategy_type", request::setDefaultStrategyType);
+        WorkflowRequestBinder.applyStringField(values, "strategy_type", request::setStrategyType);
+        WorkflowRequestBinder.applyStringField(values, "sharding_columns", request::setShardingColumns);
+        WorkflowRequestBinder.applyStringField(values, "algorithm_type", request::setAlgorithmType);
         applyMapField(values, "algorithm_properties", request::putAlgorithmProperties);
     }
     
     private void bindKeyGeneratorArguments(final ShardingWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
-        applyStringArgument(workflowPlanningArguments, "key_generator", request::setKeyGeneratorName);
-        applyStringArgument(workflowPlanningArguments, "key_generator_type", request::setKeyGeneratorType);
+        workflowPlanningArguments.applyStringArgument("key_generator", request::setKeyGeneratorName);
+        workflowPlanningArguments.applyStringArgument("key_generator_type", request::setKeyGeneratorType);
         request.putKeyGeneratorProperties(workflowPlanningArguments.getMapArgument("key_generator_properties"));
         request.putAlgorithmProperties(workflowPlanningArguments.getMapArgument("algorithm_properties"));
     }
     
     private void applyKeyGeneratorEvidence(final ShardingWorkflowRequest request, final Map<String, Object> values) {
-        applyStringField(values, "key_generator", request::setKeyGeneratorName);
-        applyStringField(values, "key_generator_type", request::setKeyGeneratorType);
+        WorkflowRequestBinder.applyStringField(values, "key_generator", request::setKeyGeneratorName);
+        WorkflowRequestBinder.applyStringField(values, "key_generator_type", request::setKeyGeneratorType);
         applyMapField(values, "key_generator_properties", request::putKeyGeneratorProperties);
         applyMapField(values, "algorithm_properties", request::putAlgorithmProperties);
     }
     
     private void bindKeyGenerateStrategyArguments(final ShardingWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
-        applyStringArgument(workflowPlanningArguments, "key_generate_strategy", request::setKeyGenerateStrategyName);
-        applyStringArgument(workflowPlanningArguments, "sequence", request::setSequenceName);
-        applyStringArgument(workflowPlanningArguments, "key_generator", request::setKeyGeneratorName);
-        applyStringArgument(workflowPlanningArguments, "key_generator_type", request::setKeyGeneratorType);
+        workflowPlanningArguments.applyStringArgument("key_generate_strategy", request::setKeyGenerateStrategyName);
+        workflowPlanningArguments.applyStringArgument("sequence", request::setSequenceName);
+        workflowPlanningArguments.applyStringArgument("key_generator", request::setKeyGeneratorName);
+        workflowPlanningArguments.applyStringArgument("key_generator_type", request::setKeyGeneratorType);
         request.putKeyGeneratorProperties(workflowPlanningArguments.getMapArgument("key_generator_properties"));
         request.putAlgorithmProperties(workflowPlanningArguments.getMapArgument("algorithm_properties"));
     }
     
     private void applyKeyGenerateStrategyEvidence(final ShardingWorkflowRequest request, final Map<String, Object> values) {
-        applyStringField(values, "table", request::setTable);
-        applyStringField(values, "column", request::setColumn);
-        applyStringField(values, "key_generate_strategy", request::setKeyGenerateStrategyName);
-        applyStringField(values, "sequence", request::setSequenceName);
-        applyStringField(values, "key_generator", request::setKeyGeneratorName);
-        applyStringField(values, "key_generator_type", request::setKeyGeneratorType);
+        WorkflowRequestBinder.applyStringField(values, "table", request::setTable);
+        WorkflowRequestBinder.applyStringField(values, "column", request::setColumn);
+        WorkflowRequestBinder.applyStringField(values, "key_generate_strategy", request::setKeyGenerateStrategyName);
+        WorkflowRequestBinder.applyStringField(values, "sequence", request::setSequenceName);
+        WorkflowRequestBinder.applyStringField(values, "key_generator", request::setKeyGeneratorName);
+        WorkflowRequestBinder.applyStringField(values, "key_generator_type", request::setKeyGeneratorType);
         applyMapField(values, "key_generator_properties", request::putKeyGeneratorProperties);
         applyMapField(values, "algorithm_properties", request::putAlgorithmProperties);
     }
     
     private void bindRuleComponentCleanupArguments(final ShardingWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
-        applyStringArgument(workflowPlanningArguments, "component_type", request::setComponentType);
-        applyStringArgument(workflowPlanningArguments, "component_name", request::setComponentName);
+        workflowPlanningArguments.applyStringArgument("component_type", request::setComponentType);
+        workflowPlanningArguments.applyStringArgument("component_name", request::setComponentName);
     }
     
     private void applyRuleComponentCleanupEvidence(final ShardingWorkflowRequest request, final Map<String, Object> values) {
-        applyStringField(values, "component_type", request::setComponentType);
-        applyStringField(values, "component_name", request::setComponentName);
-    }
-    
-    private void applyStringArgument(final WorkflowPlanningArguments workflowPlanningArguments, final String fieldName, final Consumer<String> consumer) {
-        String value = workflowPlanningArguments.getStringArgument(fieldName);
-        if (!value.isEmpty()) {
-            consumer.accept(value);
-        }
-    }
-    
-    private void applyStringField(final Map<String, Object> values, final String fieldName, final Consumer<String> consumer) {
-        Object value = values.get(fieldName);
-        if (null != value) {
-            consumer.accept(String.valueOf(value));
-        }
+        WorkflowRequestBinder.applyStringField(values, "component_type", request::setComponentType);
+        WorkflowRequestBinder.applyStringField(values, "component_name", request::setComponentName);
     }
     
     private void applyMapField(final Map<String, Object> values, final String fieldName, final Consumer<Map<String, String>> consumer) {
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmRecommendationService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmRecommendationService.java
index 004db9bc615..6f59f36c6df 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmRecommendationService.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmRecommendationService.java
@@ -21,6 +21,7 @@ import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflo
 import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowAlgorithmUtils;
 
 import java.util.LinkedList;
@@ -97,7 +98,7 @@ public final class ShardingAlgorithmRecommendationService {
     }
     
     private AlgorithmCandidate createCandidate(final String role, final String algorithmType, final int score, final String reason) {
-        return new AlgorithmCandidate(role, algorithmType, null, null, null, score, reason, "");
+        return AlgorithmCandidate.builder().algorithmRole(role).algorithmType(algorithmType).recommendationScore(score).recommendationReason(reason).riskNotes("").build();
     }
     
     private String resolveRecommendedAlgorithm(final List<Map<String, Object>> algorithmRows, final List<String> preferredTypes) {
@@ -110,13 +111,13 @@ public final class ShardingAlgorithmRecommendationService {
     }
     
     private void addAlgorithmNotFoundIssue(final List<WorkflowIssue> issues, final String label, final String algorithmType) {
-        issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+        issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM,
                 String.format("%s `%s` is not visible from the current Proxy.", label, algorithmType),
                 "Choose an available algorithm.", false, Map.of()));
     }
     
     private void addNoAlgorithmIssue(final List<WorkflowIssue> issues, final String label) {
-        issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+        issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM,
                 String.format("No %s is available from the current Proxy.", label), "Install or expose an available algorithm.", false, Map.of()));
     }
 }
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingDistSQLPlanningService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingDistSQLPlanningService.java
index 7c63c041b83..bee27c45c4e 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingDistSQLPlanningService.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingDistSQLPlanningService.java
@@ -42,9 +42,9 @@ public final class ShardingDistSQLPlanningService {
      */
     public RuleArtifact planTableRule(final ShardingWorkflowRequest request, final String operationType) {
         if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(operationType)) {
-            return new RuleArtifact("drop", String.format("DROP SHARDING TABLE RULE %s", format(request.getTable())));
+            return new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, String.format("DROP SHARDING TABLE RULE %s", format(request.getTable())));
         }
-        return new RuleArtifact("create", String.format("CREATE SHARDING TABLE RULE %s(%s)", format(request.getTable()), createTableRuleBody(request)));
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_CREATE, String.format("CREATE SHARDING TABLE RULE %s(%s)", format(request.getTable()), createTableRuleBody(request)));
     }
     
     /**
@@ -56,9 +56,10 @@ public final class ShardingDistSQLPlanningService {
      */
     public RuleArtifact planTableReferenceRule(final ShardingWorkflowRequest request, final String operationType) {
         if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(operationType)) {
-            return new RuleArtifact("drop", String.format("DROP SHARDING TABLE REFERENCE RULE %s", format(request.getRuleName())));
+            return new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, String.format("DROP SHARDING TABLE REFERENCE RULE %s", format(request.getRuleName())));
         }
-        return new RuleArtifact("create", String.format("CREATE SHARDING TABLE REFERENCE RULE %s(%s)", format(request.getRuleName()), joinIdentifiers(request.getReferenceTables())));
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_CREATE,
+                String.format("CREATE SHARDING TABLE REFERENCE RULE %s(%s)", format(request.getRuleName()), joinIdentifiers(request.getReferenceTables())));
     }
     
     /**
@@ -70,9 +71,9 @@ public final class ShardingDistSQLPlanningService {
      */
     public RuleArtifact planDefaultStrategy(final ShardingWorkflowRequest request, final String operationType) {
         if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(operationType)) {
-            return new RuleArtifact("drop", String.format("DROP DEFAULT SHARDING %s STRATEGY", request.getDefaultStrategyType().toUpperCase(Locale.ENGLISH)));
+            return new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, String.format("DROP DEFAULT SHARDING %s STRATEGY", request.getDefaultStrategyType().toUpperCase(Locale.ENGLISH)));
         }
-        return new RuleArtifact("create", String.format("CREATE DEFAULT SHARDING %s STRATEGY (%s)",
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_CREATE, String.format("CREATE DEFAULT SHARDING %s STRATEGY (%s)",
                 request.getDefaultStrategyType().toUpperCase(Locale.ENGLISH), createShardingStrategy(request)));
     }
     
@@ -85,9 +86,10 @@ public final class ShardingDistSQLPlanningService {
      */
     public RuleArtifact planKeyGenerator(final ShardingWorkflowRequest request, final String operationType) {
         if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(operationType)) {
-            return new RuleArtifact("drop", String.format("DROP SHARDING KEY GENERATOR %s", format(request.getKeyGeneratorName())));
+            return new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, String.format("DROP SHARDING KEY GENERATOR %s", format(request.getKeyGeneratorName())));
         }
-        return new RuleArtifact("create", String.format("CREATE SHARDING KEY GENERATOR %s(%s)", format(request.getKeyGeneratorName()), createKeyGeneratorFragment(request)));
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_CREATE,
+                String.format("CREATE SHARDING KEY GENERATOR %s(%s)", format(request.getKeyGeneratorName()), createKeyGeneratorFragment(request)));
     }
     
     /**
@@ -99,9 +101,9 @@ public final class ShardingDistSQLPlanningService {
      */
     public RuleArtifact planKeyGenerateStrategy(final ShardingWorkflowRequest request, final String operationType) {
         if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(operationType)) {
-            return new RuleArtifact("drop", String.format("DROP SHARDING KEY GENERATE STRATEGY %s", format(request.getKeyGenerateStrategyName())));
+            return new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, String.format("DROP SHARDING KEY GENERATE STRATEGY %s", format(request.getKeyGenerateStrategyName())));
         }
-        return new RuleArtifact("create", String.format("CREATE SHARDING KEY GENERATE STRATEGY %s(%s)",
+        return new RuleArtifact(WorkflowLifecycle.OPERATION_CREATE, String.format("CREATE SHARDING KEY GENERATE STRATEGY %s(%s)",
                 format(request.getKeyGenerateStrategyName()), createKeyGenerateStrategyBody(request)));
     }
     
@@ -114,13 +116,13 @@ public final class ShardingDistSQLPlanningService {
     public RuleArtifact planComponentCleanup(final ShardingWorkflowRequest request) {
         switch (normalizeComponentType(request.getComponentType())) {
             case "algorithm":
-                return new RuleArtifact("drop", String.format("DROP SHARDING ALGORITHM %s", format(request.getComponentName())));
+                return new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, String.format("DROP SHARDING ALGORITHM %s", format(request.getComponentName())));
             case "key-generator":
-                return new RuleArtifact("drop", String.format("DROP SHARDING KEY GENERATOR %s", format(request.getComponentName())));
+                return new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, String.format("DROP SHARDING KEY GENERATOR %s", format(request.getComponentName())));
             case "auditor":
-                return new RuleArtifact("drop", String.format("DROP SHARDING AUDITOR %s", format(request.getComponentName())));
+                return new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, String.format("DROP SHARDING AUDITOR %s", format(request.getComponentName())));
             default:
-                return new RuleArtifact("drop", "");
+                return new RuleArtifact(WorkflowLifecycle.OPERATION_DROP, "");
         }
     }
     
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowInputValidator.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowInputValidator.java
index 60523a39dfc..07dbe0c5a87 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowInputValidator.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowInputValidator.java
@@ -47,7 +47,7 @@ final class ShardingWorkflowInputValidator {
                 && areSupportedIdentifiers(splitCsv(request.getShardingColumns()))) {
             return true;
         }
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER, "error", "intaking",
+        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER, "error", WorkflowLifecycle.STEP_INTAKING,
                 "A sharding identifier contains unsupported characters.", "Use reviewable DistSQL identifiers only.", false, Map.of()));
         snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
         return false;
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningService.java
index 1d624064f06..c236a788b41 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningService.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningService.java
@@ -44,7 +44,7 @@ import java.util.Map;
  */
 public final class ShardingWorkflowPlanningService {
     
-    private static final List<String> SUPPORTED_LIFECYCLE_OPERATION_TYPES = List.of("create", WorkflowLifecycle.OPERATION_DROP);
+    private static final List<String> SUPPORTED_LIFECYCLE_OPERATION_TYPES = List.of(WorkflowLifecycle.OPERATION_CREATE, WorkflowLifecycle.OPERATION_DROP);
     
     private static final List<String> INTERACTION_STEPS = List.of(
             "Confirm logical database and sharding intent",
@@ -80,7 +80,7 @@ public final class ShardingWorkflowPlanningService {
     public WorkflowContextSnapshot planTableRule(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
                                                  final String sessionId, final ShardingWorkflowRequest request) {
         return planLifecycleWorkflow(workflowSessionContext, sessionId, request, new ShardingWorkflowLifecycleSpec(
-                ShardingFeatureDefinition.TABLE_RULE_WORKFLOW_KIND, "create", "Sharding table rule workflow plan.",
+                ShardingFeatureDefinition.TABLE_RULE_WORKFLOW_KIND, WorkflowLifecycle.OPERATION_CREATE, "Sharding table rule workflow plan.",
                 actual -> !inspectionService.queryTableRule(queryFacade, actual.getDatabase(), actual.getTable()).isEmpty(),
                 inputValidator::hasRequiredTableRuleInputs, (actual, snapshot) -> planAlgorithms(queryFacade, actual,
                         shouldPlanShardingAlgorithm(actual), shouldPlanTableRuleKeyGenerator(actual), snapshot),
@@ -99,7 +99,7 @@ public final class ShardingWorkflowPlanningService {
     public WorkflowContextSnapshot planTableReferenceRule(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
                                                           final String sessionId, final ShardingWorkflowRequest request) {
         return planLifecycleWorkflow(workflowSessionContext, sessionId, request, new ShardingWorkflowLifecycleSpec(
-                ShardingFeatureDefinition.TABLE_REFERENCE_WORKFLOW_KIND, "create", "Sharding table reference rule workflow plan.",
+                ShardingFeatureDefinition.TABLE_REFERENCE_WORKFLOW_KIND, WorkflowLifecycle.OPERATION_CREATE, "Sharding table reference rule workflow plan.",
                 actual -> !inspectionService.queryTableReferenceRule(queryFacade, actual.getDatabase(), actual.getRuleName()).isEmpty(),
                 inputValidator::hasRequiredReferenceRuleInputs, (actual, snapshot) -> true, actual -> distSQLPlanningService.planTableReferenceRule(actual, actual.getOperationType())));
     }
@@ -116,7 +116,7 @@ public final class ShardingWorkflowPlanningService {
     public WorkflowContextSnapshot planDefaultStrategy(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
                                                        final String sessionId, final ShardingWorkflowRequest request) {
         return planLifecycleWorkflow(workflowSessionContext, sessionId, request, new ShardingWorkflowLifecycleSpec(
-                ShardingFeatureDefinition.DEFAULT_STRATEGY_WORKFLOW_KIND, "create", "Default sharding strategy workflow plan.",
+                ShardingFeatureDefinition.DEFAULT_STRATEGY_WORKFLOW_KIND, WorkflowLifecycle.OPERATION_CREATE, "Default sharding strategy workflow plan.",
                 actual -> containsDefaultStrategy(inspectionService.queryDefaultStrategy(queryFacade, actual.getDatabase()),
                         queryFacade, actual.getDatabase(), actual.getDefaultStrategyType()),
                 inputValidator::hasRequiredDefaultStrategyInputs,
@@ -136,7 +136,7 @@ public final class ShardingWorkflowPlanningService {
     public WorkflowContextSnapshot planKeyGenerator(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
                                                     final String sessionId, final ShardingWorkflowRequest request) {
         return planLifecycleWorkflow(workflowSessionContext, sessionId, request, new ShardingWorkflowLifecycleSpec(
-                ShardingFeatureDefinition.KEY_GENERATOR_WORKFLOW_KIND, "create", "Sharding key generator workflow plan.",
+                ShardingFeatureDefinition.KEY_GENERATOR_WORKFLOW_KIND, WorkflowLifecycle.OPERATION_CREATE, "Sharding key generator workflow plan.",
                 actual -> !inspectionService.queryKeyGenerator(queryFacade, actual.getDatabase(), actual.getKeyGeneratorName()).isEmpty(),
                 inputValidator::hasRequiredKeyGeneratorInputs, (actual, snapshot) -> planAlgorithms(queryFacade, actual, false, true, snapshot),
                 actual -> distSQLPlanningService.planKeyGenerator(actual, actual.getOperationType())));
@@ -154,7 +154,7 @@ public final class ShardingWorkflowPlanningService {
     public WorkflowContextSnapshot planKeyGenerateStrategy(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
                                                            final String sessionId, final ShardingWorkflowRequest request) {
         return planLifecycleWorkflow(workflowSessionContext, sessionId, request, new ShardingWorkflowLifecycleSpec(
-                ShardingFeatureDefinition.KEY_GENERATE_STRATEGY_WORKFLOW_KIND, "create", "Sharding key generate strategy workflow plan.",
+                ShardingFeatureDefinition.KEY_GENERATE_STRATEGY_WORKFLOW_KIND, WorkflowLifecycle.OPERATION_CREATE, "Sharding key generate strategy workflow plan.",
                 actual -> !inspectionService.queryKeyGenerateStrategy(queryFacade, actual.getDatabase(), actual.getKeyGenerateStrategyName()).isEmpty(),
                 inputValidator::hasRequiredKeyGenerateStrategyInputs, (actual, snapshot) -> planAlgorithms(queryFacade, actual, false,
                         shouldPlanKeyGenerateStrategyGenerator(actual), snapshot),
@@ -182,7 +182,7 @@ public final class ShardingWorkflowPlanningService {
             return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_FAILED, WorkflowLifecycle.STATUS_FAILED);
         }
         if (!isUnusedComponent(queryFacade, mergedRequest) || !queryUsedBy(queryFacade, mergedRequest).isEmpty()) {
-            result.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", "discovering",
+            result.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", WorkflowLifecycle.STEP_DISCOVERING,
                     "Sharding component is still used or is not present in unused DistSQL-visible state.",
                     "Inspect unused and used-by resources before cleanup.", false, Map.of()));
             return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_FAILED, WorkflowLifecycle.STATUS_FAILED);
@@ -195,7 +195,7 @@ public final class ShardingWorkflowPlanningService {
         if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(request.getOperationType())) {
             return true;
         }
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.WORKFLOW_STATUS_INVALID, "error", "intaking",
+        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.WORKFLOW_STATUS_INVALID, "error", WorkflowLifecycle.STEP_INTAKING,
                 "Sharding component cleanup only supports dropping unused components.", "Use operation_type=drop or omit operation_type for cleanup.", false,
                 Map.of("operation_type", request.getOperationType())));
         snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationServiceTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationServiceTest.java
index 499dc9444d8..f8ae8abd848 100644
--- a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationServiceTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationServiceTest.java
@@ -33,7 +33,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnaps
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowKind;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowArtifactBundle.ExecutableWorkflowArtifact;
-import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
+import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowAlgorithmUtils;
 import org.apache.shardingsphere.sharding.spi.ShardingAlgorithm;
 import org.apache.shardingsphere.sharding.spi.ShardingAuditAlgorithm;
 import org.junit.jupiter.api.Test;
@@ -93,7 +93,7 @@ class ShardingWorkflowValidationServiceTest {
         WorkflowContextSnapshot snapshot = createSnapshot("plan-1", "session-1", "executed", "create", ShardingFeatureDefinition.TABLE_RULE_WORKFLOW_KIND, request);
         try (MockedStatic<TypedSPILoader> mockedStatic = mockStatic(TypedSPILoader.class)) {
             mockedStatic.when(() -> TypedSPILoader.checkService(ShardingAlgorithm.class, "INLINE",
-                    WorkflowSQLUtils.createProperties(request.getPrimaryAlgorithmProperties()))).thenThrow(new IllegalArgumentException("unavailable"));
+                    WorkflowAlgorithmUtils.createProperties(request.getPrimaryAlgorithmProperties()))).thenThrow(new IllegalArgumentException("unavailable"));
             List<Map<String, Object>> actual = new ShardingWorkflowValidationService().validate(snapshot, List.of(createRuleDistSQLArtifact("CREATE SHARDING TABLE RULE `t_order`(...)")));
             assertThat(actual.size(), is(1));
             assertThat(actual.getFirst().get("message"), is("Generated sharding DistSQL references sharding algorithm `INLINE`, "
@@ -110,7 +110,7 @@ class ShardingWorkflowValidationServiceTest {
         WorkflowContextSnapshot snapshot = createSnapshot("plan-1", "session-1", "executed", "create", ShardingFeatureDefinition.KEY_GENERATOR_WORKFLOW_KIND, request);
         try (MockedStatic<TypedSPILoader> mockedStatic = mockStatic(TypedSPILoader.class)) {
             mockedStatic.when(() -> TypedSPILoader.checkService(KeyGenerateAlgorithm.class, "SNOWFLAKE",
-                    WorkflowSQLUtils.createProperties(request.getKeyGeneratorProperties()))).thenThrow(new IllegalArgumentException("unavailable"));
+                    WorkflowAlgorithmUtils.createProperties(request.getKeyGeneratorProperties()))).thenThrow(new IllegalArgumentException("unavailable"));
             List<Map<String, Object>> actual = new ShardingWorkflowValidationService().validate(snapshot,
                     List.of(createRuleDistSQLArtifact("CREATE SHARDING KEY GENERATOR `snowflake_generator`(TYPE(NAME='snowflake'))")));
             assertThat(actual.size(), is(1));
@@ -127,7 +127,7 @@ class ShardingWorkflowValidationServiceTest {
         WorkflowContextSnapshot snapshot = createSnapshot("plan-1", "session-1", "executed", "create", ShardingFeatureDefinition.TABLE_RULE_WORKFLOW_KIND, request);
         try (MockedStatic<TypedSPILoader> mockedStatic = mockStatic(TypedSPILoader.class)) {
             mockedStatic.when(() -> TypedSPILoader.checkService(ShardingAuditAlgorithm.class, "DML_SHARDING_CONDITIONS",
-                    WorkflowSQLUtils.createProperties(Map.of()))).thenThrow(new IllegalArgumentException("unavailable"));
+                    WorkflowAlgorithmUtils.createProperties(Map.of()))).thenThrow(new IllegalArgumentException("unavailable"));
             List<Map<String, Object>> actual = new ShardingWorkflowValidationService().validate(snapshot, List.of(createRuleDistSQLArtifact("CREATE SHARDING TABLE RULE `t_order`(...)")));
             assertThat(actual.size(), is(1));
             assertThat(actual.getFirst().get("message"), is("Generated sharding DistSQL references auditor algorithm `DML_SHARDING_CONDITIONS`, "
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseCapability.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseCapability.java
index fa22b11ba3a..66deb9e09b7 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseCapability.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseCapability.java
@@ -20,6 +20,7 @@ package org.apache.shardingsphere.mcp.support.database.capability;
 import lombok.Getter;
 import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.schema.DialectSchemaSemantics;
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicySet;
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
 import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseProfile;
 
 import java.util.LinkedHashSet;
@@ -52,7 +53,7 @@ public final class MCPDatabaseCapability {
         databaseType = option.getType();
         MCPDatabaseDialect databaseDialect = MCPDatabaseDialect.of(option.getType());
         supportedMetadataObjectTypes = createSupportedMetadataObjectTypes(databaseDialect);
-        transactionCapability = databaseDialect.getTransactionCapability(databaseProfile.isSupportsTransaction(), databaseProfile.isSupportsSavepoint());
+        transactionCapability = databaseProfile.getTransactionCapability();
         supportedStatementClasses = createSupportedStatementClasses(transactionCapability, option.isExplainSupported());
         defaultSchemaSemantics = databaseDialect.getDefaultSchemaSemantics();
         schemaExecutionSemantics = createSchemaExecutionSemantics(defaultSchemaSemantics);
@@ -99,7 +100,7 @@ public final class MCPDatabaseCapability {
      *
      * @return whether transaction control is supported
      */
-    public boolean isSupportsTransactionControl() {
+    public boolean supportsTransactionControl() {
         return TransactionCapability.NONE != transactionCapability;
     }
     
@@ -108,7 +109,7 @@ public final class MCPDatabaseCapability {
      *
      * @return whether savepoint is supported
      */
-    public boolean isSupportsSavepoint() {
+    public boolean supportsSavepoint() {
         return TransactionCapability.LOCAL_WITH_SAVEPOINT == transactionCapability;
     }
     
@@ -117,7 +118,7 @@ public final class MCPDatabaseCapability {
      *
      * @return whether EXPLAIN query is supported
      */
-    public boolean isSupportsExplain() {
+    public boolean supportsExplain() {
         return supportedStatementClasses.contains(SupportedMCPStatement.EXPLAIN);
     }
     
@@ -126,7 +127,7 @@ public final class MCPDatabaseCapability {
      *
      * @return whether cross-schema SQL is supported
      */
-    public boolean isSupportsCrossSchemaSql() {
+    public boolean supportsCrossSchemaSql() {
         return SchemaExecutionSemantics.BEST_EFFORT == schemaExecutionSemantics;
     }
 }
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialect.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialect.java
index 386b5b09521..0722fd9c679 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialect.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialect.java
@@ -73,20 +73,6 @@ public final class MCPDatabaseDialect {
         return dialectDatabaseMetaData.getSchemaOption().getSchemaSemantics();
     }
     
-    /**
-     * Get transaction capability.
-     *
-     * @param supportsTransaction whether transaction is supported
-     * @param supportsSavepoint whether savepoint is supported
-     * @return transaction capability
-     */
-    public TransactionCapability getTransactionCapability(final boolean supportsTransaction, final boolean supportsSavepoint) {
-        if (!supportsTransaction) {
-            return TransactionCapability.NONE;
-        }
-        return supportsSavepoint ? TransactionCapability.LOCAL_WITH_SAVEPOINT : TransactionCapability.LOCAL;
-    }
-    
     /**
      * Get sequence metadata query.
      *
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/TransactionCapability.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/metadata/TransactionCapability.java
similarity index 86%
rename from mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/TransactionCapability.java
rename to mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/metadata/TransactionCapability.java
index 871dd761ef8..38477d1a8c4 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/TransactionCapability.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/metadata/TransactionCapability.java
@@ -15,10 +15,10 @@
  * limitations under the License.
  */
 
-package org.apache.shardingsphere.mcp.support.database.capability;
+package org.apache.shardingsphere.mcp.support.database.metadata;
 
 /**
- * Transaction capability levels exposed by the capability matrix.
+ * Runtime transaction capability.
  */
 public enum TransactionCapability {
     
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcDatabaseProfileLoader.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcDatabaseProfileLoader.java
index 2d4d73a44ce..58632b6d7e0 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcDatabaseProfileLoader.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcDatabaseProfileLoader.java
@@ -24,6 +24,7 @@ import org.apache.shardingsphere.database.connector.core.type.DatabaseTypeFactor
 import org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
 import org.apache.shardingsphere.infra.exception.external.ShardingSphereExternalException;
 import org.apache.shardingsphere.infra.metadata.identifier.IdentifierCasePolicyResolver;
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
 
 import javax.sql.DataSource;
 import java.io.PrintWriter;
@@ -63,27 +64,31 @@ public final class MCPJdbcDatabaseProfileLoader {
      * @param databaseName database name
      * @param runtimeDatabaseConfig runtime database configuration
      * @return runtime database profile
-     * @throws RuntimeDatabaseConnectionException when runtime database connection or configuration fails
-     * @throws RuntimeDatabaseConnectionException when profile metadata loading fails
+     * @throws RuntimeDatabaseConnectionException when runtime database profile loading fails
      */
     public RuntimeDatabaseProfile load(final String databaseName, final RuntimeDatabaseConfiguration runtimeDatabaseConfig) {
         DatabaseType databaseType;
         String databaseVersion;
-        boolean supportsTransaction;
-        boolean supportsSavepoint;
+        TransactionCapability transactionCapability;
         try (Connection connection = runtimeDatabaseConfig.openConnection(databaseName)) {
             DatabaseMetaData databaseMetaData = connection.getMetaData();
             databaseType = loadDatabaseType(databaseName, databaseMetaData);
             databaseVersion = Objects.toString(databaseMetaData.getDatabaseProductVersion(), "").trim();
-            supportsTransaction = databaseMetaData.supportsTransactions();
-            supportsSavepoint = supportsTransaction && databaseMetaData.supportsSavepoints();
+            transactionCapability = loadTransactionCapability(databaseMetaData);
         } catch (final SQLException ex) {
             throw RuntimeDatabaseConnectionException.connectionFailed(databaseName, ex);
         }
-        return new RuntimeDatabaseProfile(databaseName, databaseType.getType(), databaseVersion, supportsTransaction, supportsSavepoint,
+        return new RuntimeDatabaseProfile(databaseName, databaseType.getType(), databaseVersion, transactionCapability,
                 resolveIdentifierCasePolicySet(databaseName, databaseType, runtimeDatabaseConfig));
     }
     
+    private TransactionCapability loadTransactionCapability(final DatabaseMetaData databaseMetaData) throws SQLException {
+        if (!databaseMetaData.supportsTransactions()) {
+            return TransactionCapability.NONE;
+        }
+        return databaseMetaData.supportsSavepoints() ? TransactionCapability.LOCAL_WITH_SAVEPOINT : TransactionCapability.LOCAL;
+    }
+    
     private IdentifierCasePolicySet resolveIdentifierCasePolicySet(final String databaseName, final DatabaseType databaseType,
                                                                    final RuntimeDatabaseConfiguration runtimeDatabaseConfig) {
         return new IdentifierCasePolicyResolver().resolve(databaseType, new ConfigurationProperties(new Properties()),
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/RuntimeDatabaseProfile.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/RuntimeDatabaseProfile.java
index 7033518c4fc..9d999b762ec 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/RuntimeDatabaseProfile.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/RuntimeDatabaseProfile.java
@@ -20,6 +20,7 @@ package org.apache.shardingsphere.mcp.support.database.metadata.jdbc;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicySet;
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
 
 /**
  * Runtime database profile.
@@ -34,9 +35,7 @@ public final class RuntimeDatabaseProfile {
     
     private final String databaseVersion;
     
-    private final boolean supportsTransaction;
-    
-    private final boolean supportsSavepoint;
+    private final TransactionCapability transactionCapability;
     
     private final IdentifierCasePolicySet identifierCasePolicySet;
 }
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/response/MCPDatabaseCapabilityResponse.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/response/MCPDatabaseCapabilityResponse.java
index 94f63899e00..ee82e35924d 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/response/MCPDatabaseCapabilityResponse.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/response/MCPDatabaseCapabilityResponse.java
@@ -41,12 +41,12 @@ public final class MCPDatabaseCapabilityResponse implements MCPResponse {
         result.put("databaseType", databaseCapability.getDatabaseType());
         result.put("supportedObjectTypes", databaseCapability.getSupportedMetadataObjectTypes());
         result.put("supportedStatementClasses", databaseCapability.getSupportedStatementClasses());
-        result.put("supportsTransactionControl", databaseCapability.isSupportsTransactionControl());
-        result.put("supportsSavepoint", databaseCapability.isSupportsSavepoint());
+        result.put("supportsTransactionControl", databaseCapability.supportsTransactionControl());
+        result.put("supportsSavepoint", databaseCapability.supportsSavepoint());
         result.put("defaultSchemaSemantics", databaseCapability.getDefaultSchemaSemantics());
         result.put("schemaExecutionSemantics", databaseCapability.getSchemaExecutionSemantics());
-        result.put("supportsCrossSchemaSql", databaseCapability.isSupportsCrossSchemaSql());
-        result.put("supportsExplain", databaseCapability.isSupportsExplain());
+        result.put("supportsCrossSchemaSql", databaseCapability.supportsCrossSchemaSql());
+        result.put("supportsExplain", databaseCapability.supportsExplain());
         return result;
     }
 }
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/response/SQLExecutionResponse.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/response/SQLExecutionResponse.java
index 6f7492f0ba4..7fe2e4e9e41 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/response/SQLExecutionResponse.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/response/SQLExecutionResponse.java
@@ -18,6 +18,7 @@
 package org.apache.shardingsphere.mcp.support.database.tool.response;
 
 import lombok.AccessLevel;
+import lombok.Builder;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import org.apache.shardingsphere.mcp.api.protocol.response.MCPResponse;
@@ -40,6 +41,7 @@ import java.util.Set;
  * SQL execution response.
  */
 @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+@Builder(access = AccessLevel.PRIVATE, toBuilder = true)
 @Getter
 public final class SQLExecutionResponse implements MCPResponse {
     
@@ -57,8 +59,6 @@ public final class SQLExecutionResponse implements MCPResponse {
     
     private final String statementType;
     
-    private final String status;
-    
     private final String message;
     
     private final boolean truncated;
@@ -97,8 +97,18 @@ public final class SQLExecutionResponse implements MCPResponse {
      */
     public static SQLExecutionResponse resultSet(final SupportedMCPStatement statementClass, final String statementType,
                                                  final List<ExecuteQueryColumnDefinition> columns, final List<List<Object>> rows, final boolean truncated) {
-        return new SQLExecutionResponse(ExecuteQueryResultKind.RESULT_SET, statementClass, normalizeColumns(columns), normalizeRows(rows), 0, statementType, "OK", "", truncated, 0, 0, "",
-                MCPResponseMode.QUERY, "");
+        return SQLExecutionResponse.builder()
+                .resultKind(ExecuteQueryResultKind.RESULT_SET)
+                .statementClass(statementClass)
+                .columns(normalizeColumns(columns))
+                .rows(normalizeRows(rows))
+                .statementType(statementType)
+                .message("")
+                .truncated(truncated)
+                .normalizedSql("")
+                .responseMode(MCPResponseMode.QUERY)
+                .executionMode("")
+                .build();
     }
     
     /**
@@ -121,8 +131,18 @@ public final class SQLExecutionResponse implements MCPResponse {
      * @return update-count response
      */
     public static SQLExecutionResponse updateCount(final SupportedMCPStatement statementClass, final String statementType, final int affectedRows) {
-        return new SQLExecutionResponse(ExecuteQueryResultKind.UPDATE_COUNT, statementClass, Collections.emptyList(), Collections.emptyList(), affectedRows, statementType, "OK", "",
-                false, 0, 0, "", MCPResponseMode.EXECUTED, "");
+        return SQLExecutionResponse.builder()
+                .resultKind(ExecuteQueryResultKind.UPDATE_COUNT)
+                .statementClass(statementClass)
+                .columns(Collections.emptyList())
+                .rows(Collections.emptyList())
+                .affectedRows(affectedRows)
+                .statementType(statementType)
+                .message("")
+                .normalizedSql("")
+                .responseMode(MCPResponseMode.EXECUTED)
+                .executionMode("")
+                .build();
     }
     
     /**
@@ -134,8 +154,17 @@ public final class SQLExecutionResponse implements MCPResponse {
      * @return acknowledgement response
      */
     public static SQLExecutionResponse statementAck(final SupportedMCPStatement statementClass, final String statementType, final String message) {
-        return new SQLExecutionResponse(ExecuteQueryResultKind.STATEMENT_ACK, statementClass, Collections.emptyList(), Collections.emptyList(), 0, statementType, "OK", message, false,
-                0, 0, "", MCPResponseMode.EXECUTED, "");
+        return SQLExecutionResponse.builder()
+                .resultKind(ExecuteQueryResultKind.STATEMENT_ACK)
+                .statementClass(statementClass)
+                .columns(Collections.emptyList())
+                .rows(Collections.emptyList())
+                .statementType(statementType)
+                .message(message)
+                .normalizedSql("")
+                .responseMode(MCPResponseMode.EXECUTED)
+                .executionMode("")
+                .build();
     }
     
     /**
@@ -146,8 +175,7 @@ public final class SQLExecutionResponse implements MCPResponse {
      * @return response with execution hints
      */
     public SQLExecutionResponse withExecutionHints(final int appliedMaxRows, final int appliedTimeoutMs) {
-        return new SQLExecutionResponse(resultKind, statementClass, columns, rows, affectedRows, statementType, status, message, truncated, appliedMaxRows, appliedTimeoutMs, normalizedSql,
-                responseMode, executionMode);
+        return toBuilder().appliedMaxRows(appliedMaxRows).appliedTimeoutMs(appliedTimeoutMs).build();
     }
     
     /**
@@ -157,8 +185,7 @@ public final class SQLExecutionResponse implements MCPResponse {
      * @return response with normalized SQL
      */
     public SQLExecutionResponse withNormalizedSql(final String normalizedSql) {
-        return new SQLExecutionResponse(resultKind, statementClass, columns, rows, affectedRows, statementType, status, message, truncated, appliedMaxRows, appliedTimeoutMs,
-                null == normalizedSql ? "" : normalizedSql, responseMode, executionMode);
+        return toBuilder().normalizedSql(null == normalizedSql ? "" : normalizedSql).build();
     }
     
     /**
@@ -168,8 +195,7 @@ public final class SQLExecutionResponse implements MCPResponse {
      * @return response with execution mode markers
      */
     public SQLExecutionResponse withExecutionMode(final String executionMode) {
-        return new SQLExecutionResponse(resultKind, statementClass, columns, rows, affectedRows, statementType, status, message, truncated, appliedMaxRows, appliedTimeoutMs, normalizedSql,
-                MCPResponseMode.EXECUTED, null == executionMode ? "" : executionMode);
+        return toBuilder().responseMode(MCPResponseMode.EXECUTED).executionMode(null == executionMode ? "" : executionMode).build();
     }
     
     @Override
@@ -182,7 +208,7 @@ public final class SQLExecutionResponse implements MCPResponse {
         }
         result.put("statement_class", statementClass.name().toLowerCase(Locale.ENGLISH));
         result.put("statement_type", statementType);
-        result.put("status", status);
+        result.put("status", "OK");
         result.put(MCPPayloadFieldNames.SUMMARY, createSummary());
         if (!normalizedSql.isEmpty()) {
             result.put("normalized_sql", normalizedSql);
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogYamlSwapper.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogYamlSwapper.java
index 9a0e867454f..863db69c4d8 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogYamlSwapper.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogYamlSwapper.java
@@ -173,8 +173,13 @@ final class MCPDescriptorCatalogYamlSwapper {
     }
     
     private static MCPToolAnnotations swapToolAnnotations(final YamlMCPToolAnnotations yamlAnnotations) {
-        return new MCPToolAnnotations(yamlAnnotations.getTitle(), Boolean.TRUE.equals(yamlAnnotations.getReadOnlyHint()), Boolean.TRUE.equals(yamlAnnotations.getDestructiveHint()),
-                Boolean.TRUE.equals(yamlAnnotations.getIdempotentHint()), Boolean.TRUE.equals(yamlAnnotations.getOpenWorldHint()));
+        return MCPToolAnnotations.builder()
+                .title(yamlAnnotations.getTitle())
+                .readOnlyHint(Boolean.TRUE.equals(yamlAnnotations.getReadOnlyHint()))
+                .destructiveHint(Boolean.TRUE.equals(yamlAnnotations.getDestructiveHint()))
+                .idempotentHint(Boolean.TRUE.equals(yamlAnnotations.getIdempotentHint()))
+                .openWorldHint(Boolean.TRUE.equals(yamlAnnotations.getOpenWorldHint()))
+                .build();
     }
     
     private static Collection<MCPToolRuntimeDescriptor> swapToolRuntimeDescriptors(final Collection<YamlMCPToolDescriptor> yamlDescriptors) {
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorCatalogValidator.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorCatalogValidator.java
index ba3f6df74b2..ee6ee7d16c7 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorCatalogValidator.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorCatalogValidator.java
@@ -97,7 +97,7 @@ public final class MCPToolDescriptorCatalogValidator {
         Object properties = inputSchema.get("properties");
         ShardingSpherePreconditions.checkState(properties instanceof Map, () -> new IllegalStateException(String.format("Tool `%s` inputSchema must declare properties.", descriptor.getName())));
         validateNestedInputSchemaFields(descriptor, inputSchema, "inputSchema");
-        MCPToolOutputSchemaValidator.validateInputSchemaFields(descriptor);
+        MCPToolDescriptorValidationUtils.validateModelFacingSchemaFields(descriptor, inputSchema);
     }
     
     private static void validateNestedInputSchemaFields(final MCPToolDescriptor descriptor, final Map<?, ?> schema, final String path) {
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 ca24376d7ba..6124adfe791 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
@@ -21,6 +21,7 @@ import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
 import org.apache.shardingsphere.mcp.api.tool.descriptor.MCPToolDescriptor;
+import org.apache.shardingsphere.mcp.support.protocol.MCPModelFacingPayloadContract;
 import org.apache.shardingsphere.mcp.support.protocol.MCPPayloadFieldNames;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowFieldNames;
 
@@ -28,6 +29,7 @@ import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Optional;
 
 /**
@@ -108,6 +110,15 @@ public final class MCPToolDescriptorValidationUtils {
         return findToolSchemaProperty(descriptor.getOutputSchema(), 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();
+    }
+    
     /**
      * Check whether input schema property is required.
      *
@@ -120,13 +131,49 @@ public final class MCPToolDescriptorValidationUtils {
         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();
+    /**
+     * Validate that a model-facing schema does not use removed field aliases.
+     *
+     * @param descriptor tool descriptor
+     * @param schema model-facing schema
+     */
+    static void validateModelFacingSchemaFields(final MCPToolDescriptor descriptor, final Map<String, Object> schema) {
+        validateNoRemovedModelFacingFields(descriptor, schema);
+    }
+    
+    private static void validateNoRemovedModelFacingFields(final MCPToolDescriptor descriptor, final Object value) {
+        if (value instanceof Map) {
+            validateNoRemovedModelFacingFieldMap(descriptor, (Map<?, ?>) value);
+        } else if (value instanceof Collection) {
+            for (Object each : (Collection<?>) value) {
+                validateNoRemovedModelFacingFields(descriptor, each);
+            }
         }
-        Object property = ((Map<?, ?>) properties).get(fieldName);
-        return property instanceof Map ? Optional.of((Map<?, ?>) property) : Optional.empty();
+    }
+    
+    private static void validateNoRemovedModelFacingFieldMap(final MCPToolDescriptor descriptor, final Map<?, ?> value) {
+        for (Entry<?, ?> entry : value.entrySet()) {
+            String key = String.valueOf(entry.getKey());
+            validateNoRemovedModelFacingField(descriptor, key);
+            if ("required".equals(key)) {
+                validateNoRemovedModelFacingRequiredFields(descriptor, entry.getValue());
+            }
+            validateNoRemovedModelFacingFields(descriptor, entry.getValue());
+        }
+    }
+    
+    private static void validateNoRemovedModelFacingRequiredFields(final MCPToolDescriptor descriptor, final Object value) {
+        if (!(value instanceof Collection)) {
+            return;
+        }
+        for (Object each : (Collection<?>) value) {
+            validateNoRemovedModelFacingField(descriptor, String.valueOf(each));
+        }
+    }
+    
+    private static void validateNoRemovedModelFacingField(final MCPToolDescriptor descriptor, final String fieldName) {
+        ShardingSpherePreconditions.checkState(!MCPModelFacingPayloadContract.isRemovedFieldName(fieldName),
+                () -> new IllegalStateException(String.format("Tool `%s` model-facing contract must use canonical fields instead of removed `%s`.", descriptor.getName(), fieldName)));
     }
     
     /**
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolOutputSchemaValidator.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolOutputSchemaValidator.java
index 251cf3ad9a9..04e7e955818 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolOutputSchemaValidator.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolOutputSchemaValidator.java
@@ -53,21 +53,12 @@ public final class MCPToolOutputSchemaValidator {
         Object properties = outputSchema.get("properties");
         ShardingSpherePreconditions.checkState(properties instanceof Map && !((Map<?, ?>) properties).isEmpty(),
                 () -> new IllegalStateException(String.format("Tool `%s` outputSchema must declare properties.", descriptor.getName())));
-        validateNoRemovedModelFacingFields(descriptor, outputSchema);
+        MCPToolDescriptorValidationUtils.validateModelFacingSchemaFields(descriptor, outputSchema);
         validateOutputExamples(descriptor, outputSchema);
         validateOutputExampleContractValues(descriptor, outputSchema);
         validateModelCriticalOutputHints(descriptor, (Map<?, ?>) properties);
     }
     
-    /**
-     * Validate that model-facing input schema fields do not use removed aliases.
-     *
-     * @param descriptor tool descriptor
-     */
-    public static void validateInputSchemaFields(final MCPToolDescriptor descriptor) {
-        validateNoRemovedModelFacingFields(descriptor, descriptor.getInputSchema());
-    }
-    
     private static void validateOutputExamples(final MCPToolDescriptor descriptor, final Map<String, Object> outputSchema) {
         ShardingSpherePreconditions.checkState(isNonEmptyCollection(outputSchema.get("examples")),
                 () -> new IllegalStateException(String.format("Tool `%s` outputSchema must declare examples.", descriptor.getName())));
@@ -118,41 +109,6 @@ public final class MCPToolOutputSchemaValidator {
         }
     }
     
-    private static void validateNoRemovedModelFacingFields(final MCPToolDescriptor descriptor, final Object value) {
-        if (value instanceof Map) {
-            validateNoRemovedModelFacingFieldMap(descriptor, (Map<?, ?>) value);
-        } else if (value instanceof Collection) {
-            for (Object each : (Collection<?>) value) {
-                validateNoRemovedModelFacingFields(descriptor, each);
-            }
-        }
-    }
-    
-    private static void validateNoRemovedModelFacingFieldMap(final MCPToolDescriptor descriptor, final Map<?, ?> value) {
-        for (Entry<?, ?> entry : value.entrySet()) {
-            String key = String.valueOf(entry.getKey());
-            validateNoRemovedModelFacingField(descriptor, key);
-            if ("required".equals(key)) {
-                validateNoRemovedModelFacingRequiredFields(descriptor, entry.getValue());
-            }
-            validateNoRemovedModelFacingFields(descriptor, entry.getValue());
-        }
-    }
-    
-    private static void validateNoRemovedModelFacingRequiredFields(final MCPToolDescriptor descriptor, final Object value) {
-        if (!(value instanceof Collection)) {
-            return;
-        }
-        for (Object each : (Collection<?>) value) {
-            validateNoRemovedModelFacingField(descriptor, String.valueOf(each));
-        }
-    }
-    
-    private static void validateNoRemovedModelFacingField(final MCPToolDescriptor descriptor, final String fieldName) {
-        ShardingSpherePreconditions.checkState(!MCPModelFacingPayloadContract.isRemovedFieldName(fieldName),
-                () -> new IllegalStateException(String.format("Tool `%s` model-facing contract must use canonical fields instead of removed `%s`.", descriptor.getName(), fieldName)));
-    }
-    
     private static void validateConcreteNextActions(final MCPToolDescriptor descriptor, final Object value) {
         ShardingSpherePreconditions.checkState(value instanceof Collection,
                 () -> new IllegalStateException(String.format("Tool `%s` next_actions example must be an array.", descriptor.getName())));
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/protocol/MCPCompletionAction.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/protocol/MCPCompletionAction.java
new file mode 100644
index 00000000000..5394e24baac
--- /dev/null
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/protocol/MCPCompletionAction.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.support.protocol;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * MCP completion action.
+ */
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Builder
+@Getter
+public final class MCPCompletionAction {
+    
+    @Builder.Default
+    private final String referenceType = "";
+    
+    @Builder.Default
+    private final String reference = "";
+    
+    @Builder.Default
+    private final String argumentName = "";
+    
+    @Builder.Default
+    private final String argumentPrefix = "";
+    
+    @Builder.Default
+    private final Map<String, ?> contextArguments = Map.of();
+    
+    @Builder.Default
+    private final Collection<String> missingContextArguments = List.of();
+    
+    @Builder.Default
+    private final String resumeTargetType = "";
+    
+    @Builder.Default
+    private final String resumeTarget = "";
+    
+    @Builder.Default
+    private final Map<String, ?> resumeArguments = Map.of();
+    
+    @Builder.Default
+    private final String reason = "";
+}
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/protocol/MCPNextActionUtils.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/protocol/MCPNextActionUtils.java
index 16122e250a5..a083fe90d01 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/protocol/MCPNextActionUtils.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/protocol/MCPNextActionUtils.java
@@ -82,33 +82,22 @@ public final class MCPNextActionUtils {
     /**
      * Create a completion action.
      *
-     * @param referenceType completion reference type
-     * @param reference completion reference
-     * @param argumentName argument name
-     * @param argumentPrefix argument prefix
-     * @param contextArguments context arguments
-     * @param missingContextArguments missing context arguments
-     * @param resumeTargetType resume target type
-     * @param resumeTarget resume target
-     * @param resumeArguments resume arguments
-     * @param reason action reason
+     * @param action completion action
      * @return action payload
      */
-    public static Map<String, Object> completeArgument(final String referenceType, final String reference, final String argumentName, final String argumentPrefix,
-                                                       final Map<String, ?> contextArguments, final Collection<String> missingContextArguments, final String resumeTargetType,
-                                                       final String resumeTarget, final Map<String, ?> resumeArguments, final String reason) {
-        Map<String, Object> result = createBaseAction("completion", "Complete " + argumentName, reason);
-        result.put("ref", createCompletionRef(referenceType, reference));
-        result.put("argument", Map.of("name", argumentName, "value", argumentPrefix));
-        if (!contextArguments.isEmpty()) {
-            result.put("context", Map.of("arguments", contextArguments));
+    public static Map<String, Object> completeArgument(final MCPCompletionAction action) {
+        Map<String, Object> result = createBaseAction("completion", "Complete " + action.getArgumentName(), action.getReason());
+        result.put("ref", createCompletionRef(action.getReferenceType(), action.getReference()));
+        result.put("argument", Map.of("name", action.getArgumentName(), "value", action.getArgumentPrefix()));
+        if (!action.getContextArguments().isEmpty()) {
+            result.put("context", Map.of("arguments", action.getContextArguments()));
         }
-        result.put("missing_context_arguments", missingContextArguments);
-        if (!resumeTargetType.isEmpty()) {
-            result.put("resume_ref", createCompletionRef(resumeTargetType, resumeTarget));
+        result.put("missing_context_arguments", action.getMissingContextArguments());
+        if (!action.getResumeTargetType().isEmpty()) {
+            result.put("resume_ref", createCompletionRef(action.getResumeTargetType(), action.getResumeTarget()));
         }
-        if (!resumeArguments.isEmpty()) {
-            result.put("resume_arguments", resumeArguments);
+        if (!action.getResumeArguments().isEmpty()) {
+            result.put("resume_arguments", action.getResumeArguments());
         }
         return result;
     }
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/AlgorithmCandidate.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/AlgorithmCandidate.java
index eba776c8ebe..c984be5bb71 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/AlgorithmCandidate.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/AlgorithmCandidate.java
@@ -17,8 +17,10 @@
 
 package org.apache.shardingsphere.mcp.support.workflow.model;
 
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -26,7 +28,8 @@ import java.util.Map;
 /**
  * Algorithm candidate.
  */
-@RequiredArgsConstructor
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Builder
 @Getter
 public final class AlgorithmCandidate {
     
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/InteractionPlan.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/InteractionPlan.java
index efe1860067f..8d6c1e14bea 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/InteractionPlan.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/InteractionPlan.java
@@ -62,8 +62,8 @@ public final class InteractionPlan {
         result.setPlanId(planId);
         result.setSummary(summary);
         result.setCurrentStep(WorkflowLifecycle.STEP_INTAKING);
-        result.setDeliveryMode(resolveMode(request.getDeliveryMode(), "all-at-once"));
-        result.setExecutionMode(resolveMode(request.getExecutionMode(), "review-then-execute"));
+        result.setDeliveryMode(resolveMode(request.getDeliveryMode(), WorkflowLifecycle.DELIVERY_MODE_ALL_AT_ONCE));
+        result.setExecutionMode(resolveMode(request.getExecutionMode(), WorkflowLifecycle.EXECUTION_MODE_REVIEW_THEN_EXECUTE));
         result.getSteps().addAll(steps);
         result.getValidationStrategy().put("layers", validationLayers);
         return result;
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/SecretReferenceValue.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/SecretReferenceValue.java
index 3f62af63e32..a52c3ec45c6 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/SecretReferenceValue.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/SecretReferenceValue.java
@@ -17,6 +17,7 @@
 
 package org.apache.shardingsphere.mcp.support.workflow.model;
 
+import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 
@@ -26,7 +27,7 @@ import java.util.Map;
 /**
  * Secret reference value.
  */
-@RequiredArgsConstructor
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
 @Getter
 public final class SecretReferenceValue {
     
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowContextSnapshot.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowContextSnapshot.java
index 13ce5f2a77e..34cdbb4c7e3 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowContextSnapshot.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowContextSnapshot.java
@@ -96,11 +96,11 @@ public final class WorkflowContextSnapshot {
         result.setFeatureData(null == featureData ? null : featureData.copy());
         result.setInteractionPlan(copyInteractionPlan(interactionPlan));
         issues.forEach(each -> result.getIssues().add(copyWorkflowIssue(each)));
-        algorithmCandidates.forEach(each -> result.getAlgorithmCandidates().add(copyAlgorithmCandidate(each)));
-        propertyRequirements.forEach(each -> result.getPropertyRequirements().add(copyPropertyRequirement(each)));
-        ddlArtifacts.forEach(each -> result.getDdlArtifacts().add(new DDLArtifact(each.getArtifactType(), each.getSql(), each.getExecutionOrder())));
-        ruleArtifacts.forEach(each -> result.getRuleArtifacts().add(new RuleArtifact(each.getOperationType(), each.getSql())));
-        indexPlans.forEach(each -> result.getIndexPlans().add(new IndexPlan(each.getIndexName(), each.getColumnName(), each.getReason(), each.getSql())));
+        result.getAlgorithmCandidates().addAll(algorithmCandidates);
+        result.getPropertyRequirements().addAll(propertyRequirements);
+        result.getDdlArtifacts().addAll(ddlArtifacts);
+        result.getRuleArtifacts().addAll(ruleArtifacts);
+        result.getIndexPlans().addAll(indexPlans);
         result.setValidationReport(copyValidationReport(validationReport));
         return result;
     }
@@ -143,17 +143,6 @@ public final class WorkflowContextSnapshot {
                 original.getUserAction(), original.isRetryable(), copyMap(original.getDetails()));
     }
     
-    private static AlgorithmCandidate copyAlgorithmCandidate(final AlgorithmCandidate original) {
-        return new AlgorithmCandidate(original.getAlgorithmRole(), original.getAlgorithmType(), original.getSupportsDecrypt(),
-                original.getSupportsEquivalentFilter(), original.getSupportsLike(), original.getRecommendationScore(),
-                original.getRecommendationReason(), original.getRiskNotes());
-    }
-    
-    private static AlgorithmPropertyRequirement copyPropertyRequirement(final AlgorithmPropertyRequirement original) {
-        return new AlgorithmPropertyRequirement(original.getAlgorithmRole(), original.getPropertyKey(), original.isRequired(),
-                original.isSecret(), original.getDescription(), original.getDefaultValue());
-    }
-    
     private static ValidationReport copyValidationReport(final ValidationReport original) {
         if (null == original) {
             return null;
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowLifecycle.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowLifecycle.java
index bf106969641..26b89e0ecef 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowLifecycle.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowLifecycle.java
@@ -26,6 +26,14 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public final class WorkflowLifecycle {
     
+    public static final String DELIVERY_MODE_ALL_AT_ONCE = "all-at-once";
+    
+    public static final String EXECUTION_MODE_MANUAL_ONLY = "manual-only";
+    
+    public static final String EXECUTION_MODE_PREVIEW = "preview";
+    
+    public static final String EXECUTION_MODE_REVIEW_THEN_EXECUTE = "review-then-execute";
+    
     public static final String STATUS_AWAITING_MANUAL_EXECUTION = "awaiting-manual-execution";
     
     public static final String STATUS_CLARIFYING = "clarifying";
@@ -48,18 +56,36 @@ public final class WorkflowLifecycle {
     
     public static final String STEP_CLARIFYING = "clarifying";
     
+    public static final String STEP_COLLECTING_PROPERTIES = "collecting-properties";
+    
+    public static final String STEP_COLLECTING_RULE_INPUTS = "collecting-rule-inputs";
+    
+    public static final String STEP_DISCOVERING = "discovering";
+    
     public static final String STEP_EXECUTED = "executed";
     
+    public static final String STEP_EXECUTING = "executing";
+    
     public static final String STEP_FAILED = "failed";
     
     public static final String STEP_INTAKING = "intaking";
     
     public static final String STEP_MANUAL_EXECUTION = "manual-execution";
     
+    public static final String STEP_PLANNING_ARTIFACTS = "planning-artifacts";
+    
     public static final String STEP_REVIEW = "review";
     
+    public static final String STEP_SELECTING_ALGORITHM = "selecting-algorithm";
+    
     public static final String STEP_VALIDATED = "validated";
     
+    public static final String STEP_VALIDATING = "validating";
+    
+    public static final String OPERATION_ALTER = "alter";
+    
+    public static final String OPERATION_CREATE = "create";
+    
     public static final String OPERATION_DROP = "drop";
     
 }
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowRequest.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowRequest.java
index fe2e129f8b6..a325b31e70a 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowRequest.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowRequest.java
@@ -47,9 +47,9 @@ public class WorkflowRequest implements WorkflowPropertySource {
     
     private String fieldSemantics = "";
     
-    private String deliveryMode = "all-at-once";
+    private String deliveryMode = WorkflowLifecycle.DELIVERY_MODE_ALL_AT_ONCE;
     
-    private String executionMode = "review-then-execute";
+    private String executionMode = WorkflowLifecycle.EXECUTION_MODE_REVIEW_THEN_EXECUTE;
     
     private String algorithmType = "";
     
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowAlgorithmRequirementCollector.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowAlgorithmRequirementCollector.java
index 29e49e9c826..5539e809411 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowAlgorithmRequirementCollector.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowAlgorithmRequirementCollector.java
@@ -22,6 +22,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.ClarifiedIntent;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
 
 import java.util.LinkedList;
@@ -42,7 +43,7 @@ public final class WorkflowAlgorithmRequirementCollector {
      * @return whether there is any blocking algorithm issue
      */
     public boolean hasBlockingAlgorithmIssues(final ClarifiedIntent clarifiedIntent, final WorkflowContextSnapshot snapshot, final String fallbackQuestion) {
-        boolean result = snapshot.getIssues().stream().anyMatch(each -> "selecting-algorithm".equals(each.getStage()) && "error".equals(each.getSeverity()));
+        boolean result = snapshot.getIssues().stream().anyMatch(each -> WorkflowLifecycle.STEP_SELECTING_ALGORITHM.equals(each.getStage()) && "error".equals(each.getSeverity()));
         if (result && clarifiedIntent.getClarificationMessages().isEmpty()) {
             clarifiedIntent.getClarificationMessages().add(fallbackQuestion);
         }
@@ -69,7 +70,7 @@ public final class WorkflowAlgorithmRequirementCollector {
         for (String each : missingRequiredProperties) {
             clarifiedIntent.getClarificationMessages().add(String.format("Please provide property `%s`.", each));
         }
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.REQUIRED_PROPERTY_MISSING, "error", "collecting-properties",
+        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.REQUIRED_PROPERTY_MISSING, "error", WorkflowLifecycle.STEP_COLLECTING_PROPERTIES,
                 "Required algorithm properties are still missing.", "Provide all required algorithm properties.", true, Map.of("missing_properties", missingRequiredProperties)));
         return false;
     }
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowAlgorithmUtils.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowAlgorithmUtils.java
index d9bef5a1399..f69c20f76df 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowAlgorithmUtils.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowAlgorithmUtils.java
@@ -25,9 +25,13 @@ import org.apache.shardingsphere.infra.spi.type.typed.TypedSPI;
 import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 
 import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Properties;
 
 /**
  * Workflow algorithm utilities.
@@ -78,6 +82,58 @@ public final class WorkflowAlgorithmUtils {
         return algorithmRows.stream().map(each -> getAlgorithmType(each, typeKeys)).anyMatch(actualAlgorithmType::equals);
     }
     
+    /**
+     * Create properties with trimmed string values.
+     *
+     * @param entries property entries
+     * @return created properties
+     */
+    public static Properties createProperties(final Map<String, String> entries) {
+        Properties result = new Properties();
+        for (Entry<String, String> entry : entries.entrySet()) {
+            result.setProperty(entry.getKey(), trimToEmpty(entry.getValue()));
+        }
+        return result;
+    }
+    
+    /**
+     * Create a property map from supported property carrier types.
+     *
+     * @param value property carrier value
+     * @return normalized property map
+     */
+    public static Map<String, String> createPropertyMap(final Object value) {
+        if (null == value) {
+            return Map.of();
+        }
+        if (value instanceof Properties) {
+            return createPropertyMap((Properties) value);
+        }
+        if (value instanceof Map) {
+            return createPropertyMap((Map<?, ?>) value);
+        }
+        return parsePropertyString(String.valueOf(value));
+    }
+    
+    private static Map<String, String> createPropertyMap(final Properties props) {
+        Map<String, String> result = new LinkedHashMap<>(props.size(), 1F);
+        for (String each : props.stringPropertyNames()) {
+            result.put(each, trimToEmpty(props.getProperty(each)));
+        }
+        return result;
+    }
+    
+    private static Map<String, String> createPropertyMap(final Map<?, ?> props) {
+        Map<String, String> result = new LinkedHashMap<>(props.size(), 1F);
+        for (Entry<?, ?> entry : props.entrySet()) {
+            String key = trimToEmpty(Objects.toString(entry.getKey(), ""));
+            if (!key.isEmpty()) {
+                result.put(key, trimToEmpty(Objects.toString(entry.getValue(), "")));
+            }
+        }
+        return result;
+    }
+    
     /**
      * Check whether an algorithm service can be used by workflow generated artifacts.
      *
@@ -96,7 +152,7 @@ public final class WorkflowAlgorithmUtils {
             return containsServiceType(serviceInterface, actualAlgorithmType);
         }
         try {
-            TypedSPILoader.checkService(serviceInterface, actualAlgorithmType, WorkflowSQLUtils.createProperties(properties));
+            TypedSPILoader.checkService(serviceInterface, actualAlgorithmType, createProperties(properties));
             return true;
         } catch (final ShardingSphereExternalException | IllegalArgumentException ignored) {
             return false;
@@ -107,6 +163,61 @@ public final class WorkflowAlgorithmUtils {
         return properties.values().stream().anyMatch(each -> Objects.toString(each, "").startsWith(SECRET_REFERENCE_PREFIX));
     }
     
+    private static Map<String, String> parsePropertyString(final String value) {
+        String actualValue = trimToEmpty(value);
+        if (actualValue.isEmpty() || "{}".equals(actualValue)) {
+            return Map.of();
+        }
+        String normalizedValue = actualValue;
+        if (normalizedValue.startsWith("{") && normalizedValue.endsWith("}")) {
+            normalizedValue = normalizedValue.substring(1, normalizedValue.length() - 1);
+        }
+        Collection<String> entries = List.of(normalizedValue.split(","));
+        Map<String, String> result = new LinkedHashMap<>(entries.size(), 1F);
+        for (Entry<String, String> entry : parsePropertyEntries(entries).entrySet()) {
+            result.put(stripQuotes(entry.getKey()), stripQuotes(entry.getValue()));
+        }
+        return result;
+    }
+    
+    private static Map<String, String> parsePropertyEntries(final Collection<String> entries) {
+        Map<String, String> result = new LinkedHashMap<>(entries.size(), 1F);
+        for (String each : entries) {
+            int separatorIndex = findPropertySeparatorIndex(each);
+            if (-1 == separatorIndex) {
+                continue;
+            }
+            String key = each.substring(0, separatorIndex).trim();
+            String propertyValue = each.substring(separatorIndex + 1).trim();
+            if (!key.isEmpty()) {
+                result.put(key, propertyValue);
+            }
+        }
+        return result;
+    }
+    
+    private static int findPropertySeparatorIndex(final String propertyEntry) {
+        int equalsIndex = propertyEntry.indexOf('=');
+        return -1 == equalsIndex ? propertyEntry.indexOf(':') : equalsIndex;
+    }
+    
+    private static String stripQuotes(final String value) {
+        String actualValue = trimToEmpty(value);
+        if (2 > actualValue.length()) {
+            return actualValue;
+        }
+        char first = actualValue.charAt(0);
+        char last = actualValue.charAt(actualValue.length() - 1);
+        if ('\'' == first && '\'' == last || '"' == first && '"' == last) {
+            return actualValue.substring(1, actualValue.length() - 1);
+        }
+        return actualValue;
+    }
+    
+    private static String trimToEmpty(final String value) {
+        return null == value ? "" : value.trim();
+    }
+    
     private static <T extends TypedSPI> boolean containsServiceType(final Class<T> serviceInterface, final String algorithmType) {
         for (T each : ShardingSphereServiceLoader.getServiceInstances(serviceInterface)) {
             if (matchesType(algorithmType, each)) {
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidancePayloadBuilder.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidancePayloadBuilder.java
index e326c9b3365..f0a01b4a122 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidancePayloadBuilder.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidancePayloadBuilder.java
@@ -47,10 +47,6 @@ import java.util.Objects;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public final class WorkflowGuidancePayloadBuilder {
     
-    private static final String EXECUTION_MODE_PREVIEW = "preview";
-    
-    private static final String EXECUTION_MODE_MANUAL_ONLY = "manual-only";
-    
     /**
      * Append model-facing next action guidance to a planning response.
      *
@@ -142,7 +138,7 @@ public final class WorkflowGuidancePayloadBuilder {
     }
     
     private static boolean isManualOnlyWorkflow(final WorkflowContextSnapshot snapshot) {
-        return null != snapshot.getRequest() && EXECUTION_MODE_MANUAL_ONLY.equalsIgnoreCase(snapshot.getRequest().getExecutionMode());
+        return null != snapshot.getRequest() && WorkflowLifecycle.EXECUTION_MODE_MANUAL_ONLY.equalsIgnoreCase(snapshot.getRequest().getExecutionMode());
     }
     
     private static List<String> createMissingRequiredInputs(final WorkflowContextSnapshot snapshot) {
@@ -281,7 +277,7 @@ public final class WorkflowGuidancePayloadBuilder {
         }
         if (WorkflowLifecycle.STATUS_PLANNED.equals(snapshot.getStatus())) {
             return List.of(createToolAction(WorkflowToolDescriptors.APPLY_TOOL_NAME, "Preview workflow artifacts before execution.",
-                    Map.of(WorkflowFieldNames.PLAN_ID, snapshot.getPlanId(), WorkflowFieldNames.EXECUTION_MODE, EXECUTION_MODE_PREVIEW)));
+                    Map.of(WorkflowFieldNames.PLAN_ID, snapshot.getPlanId(), WorkflowFieldNames.EXECUTION_MODE, WorkflowLifecycle.EXECUTION_MODE_PREVIEW)));
         }
         if (WorkflowLifecycle.STATUS_FAILED.equals(snapshot.getStatus())) {
             return createRecoveryPlanningActions(snapshot);
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowIntentResolverSupport.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowIntentResolverSupport.java
index 30d84e890d0..fd7727d827d 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowIntentResolverSupport.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowIntentResolverSupport.java
@@ -34,8 +34,6 @@ import java.util.Locale;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public final class WorkflowIntentResolverSupport {
     
-    private static final String EXECUTION_MODE_MANUAL_ONLY = "manual-only";
-    
     private static final List<FieldSemanticRule> FIELD_SEMANTIC_RULES = List.of(
             new FieldSemanticRule("phone", List.of("phone number", "phone", "mobile", "tel", "手机号", "手机", "电话号码", "电话"), List.of("phone", "mobile", "tel")),
             new FieldSemanticRule("id_card", List.of("identity card", "id card", "身份证", "证件"), List.of("id_card")),
@@ -64,9 +62,9 @@ public final class WorkflowIntentResolverSupport {
             return recordInferredValue(clarifiedIntent, WorkflowFieldNames.OPERATION_TYPE, WorkflowLifecycle.OPERATION_DROP);
         }
         if (containsAny(naturalLanguageIntent, "alter", "modify", "update", "修改", "更新", "调整", "变更")) {
-            return recordInferredValue(clarifiedIntent, WorkflowFieldNames.OPERATION_TYPE, "alter");
+            return recordInferredValue(clarifiedIntent, WorkflowFieldNames.OPERATION_TYPE, WorkflowLifecycle.OPERATION_ALTER);
         }
-        return recordInferredValue(clarifiedIntent, WorkflowFieldNames.OPERATION_TYPE, "create");
+        return recordInferredValue(clarifiedIntent, WorkflowFieldNames.OPERATION_TYPE, WorkflowLifecycle.OPERATION_CREATE);
     }
     
     /**
@@ -96,7 +94,7 @@ public final class WorkflowIntentResolverSupport {
         if (containsAny(naturalLanguageIntent, "manual", "manual-only", "manual only", "manual execution", "manual artifacts",
                 "export", "outside mcp", "without applying", "do not apply", "no runtime side effects", "side effects out of mcp",
                 "手动", "手工", "人工", "导出", "不要执行", "不执行", "不要应用", "不应用", "不产生副作用")) {
-            return recordInferredValue(clarifiedIntent, WorkflowFieldNames.EXECUTION_MODE, EXECUTION_MODE_MANUAL_ONLY);
+            return recordInferredValue(clarifiedIntent, WorkflowFieldNames.EXECUTION_MODE, WorkflowLifecycle.EXECUTION_MODE_MANUAL_ONLY);
         }
         String actualExecutionMode = request.getExecutionMode().toLowerCase(Locale.ENGLISH);
         return actualExecutionMode.isEmpty() ? "" : actualExecutionMode;
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanPayloadBuilder.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanPayloadBuilder.java
index 3cdf25d406e..938019277b7 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanPayloadBuilder.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanPayloadBuilder.java
@@ -42,12 +42,6 @@ import java.util.Map;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public final class WorkflowPlanPayloadBuilder {
     
-    private static final String DELIVERY_MODE_ALL_AT_ONCE = "all-at-once";
-    
-    private static final String EXECUTION_MODE_REVIEW_THEN_EXECUTE = "review-then-execute";
-    
-    private static final String EXECUTION_MODE_MANUAL_ONLY = "manual-only";
-    
     /**
      * Build one workflow-plan payload map.
      *
@@ -131,7 +125,7 @@ public final class WorkflowPlanPayloadBuilder {
     
     private static Map<String, Object> createReviewFocus(final WorkflowContextSnapshot snapshot) {
         Map<String, Object> result = new LinkedHashMap<>(5, 1F);
-        boolean manualOnly = EXECUTION_MODE_MANUAL_ONLY.equals(snapshot.getInteractionPlan().getExecutionMode());
+        boolean manualOnly = WorkflowLifecycle.EXECUTION_MODE_MANUAL_ONLY.equals(snapshot.getInteractionPlan().getExecutionMode());
         result.put("artifact_categories", createReviewArtifactCategories(snapshot));
         result.put("side_effect_scope", createReviewSideEffectScope(snapshot));
         result.put("manual_only", manualOnly);
@@ -173,7 +167,9 @@ public final class WorkflowPlanPayloadBuilder {
     }
     
     private static boolean isDefaultMode(final String fieldName, final String value) {
-        return WorkflowFieldNames.DELIVERY_MODE.equals(fieldName) ? DELIVERY_MODE_ALL_AT_ONCE.equals(value) : EXECUTION_MODE_REVIEW_THEN_EXECUTE.equals(value);
+        return WorkflowFieldNames.DELIVERY_MODE.equals(fieldName)
+                ? WorkflowLifecycle.DELIVERY_MODE_ALL_AT_ONCE.equals(value)
+                : WorkflowLifecycle.EXECUTION_MODE_REVIEW_THEN_EXECUTE.equals(value);
     }
     
     private static Map<String, Object> getInferredValues(final WorkflowContextSnapshot snapshot) {
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningArguments.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningArguments.java
index 9e56dbe5d7a..932b4c55892 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningArguments.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningArguments.java
@@ -26,6 +26,7 @@ import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.function.Consumer;
 
 /**
  * Workflow-scoped planning arguments.
@@ -45,6 +46,19 @@ public final class WorkflowPlanningArguments {
         return Objects.toString(arguments.get(name), "").trim();
     }
     
+    /**
+     * Apply a non-empty string argument.
+     *
+     * @param name argument name
+     * @param consumer argument consumer
+     */
+    public void applyStringArgument(final String name, final Consumer<String> consumer) {
+        String value = getStringArgument(name);
+        if (!value.isEmpty()) {
+            consumer.accept(value);
+        }
+    }
+    
     /**
      * Get string map argument.
      *
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidator.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidator.java
index 79b3a8c081f..1b776cc66c2 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidator.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidator.java
@@ -85,14 +85,14 @@ public final class WorkflowPlanningContextValidator {
                                          final ClarifiedIntent clarifiedIntent, final WorkflowContextSnapshot snapshot) {
         if (isEmptyIdentifier(request.getDatabase())) {
             clarifiedIntent.getClarificationMessages().add("Please provide logical database first.");
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DATABASE_REQUIRED, "error", "intaking",
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DATABASE_REQUIRED, "error", WorkflowLifecycle.STEP_INTAKING,
                     "Database is required before planning.", "Provide the logical database name.", true, Map.of()));
             snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
             return false;
         }
-        if (!ensureSupportedIdentifiers(WorkflowFieldNames.DATABASE, List.of(request.getDatabase()), snapshot, "discovering")
-                || !ensureSupportedIdentifiers(WorkflowFieldNames.TABLE, List.of(request.getTable()), snapshot, "discovering")
-                || !ensureSupportedIdentifiers(WorkflowFieldNames.COLUMN, List.of(request.getColumn()), snapshot, "discovering")) {
+        if (!ensureSupportedIdentifiers(WorkflowFieldNames.DATABASE, List.of(request.getDatabase()), snapshot, WorkflowLifecycle.STEP_DISCOVERING)
+                || !ensureSupportedIdentifiers(WorkflowFieldNames.TABLE, List.of(request.getTable()), snapshot, WorkflowLifecycle.STEP_DISCOVERING)
+                || !ensureSupportedIdentifiers(WorkflowFieldNames.COLUMN, List.of(request.getColumn()), snapshot, WorkflowLifecycle.STEP_DISCOVERING)) {
             snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
             return false;
         }
@@ -101,7 +101,7 @@ public final class WorkflowPlanningContextValidator {
         queryFacade.checkDatabaseCapability(databaseName);
         List<ShardingSphereSchema> schemas = isEmptyIdentifier(request.getSchema()) ? metadataQueryFacade.querySchemas(databaseName) : List.of();
         request.setSchema(resolveSchema(schemas, request, clarifiedIntent, queryFacade, databaseName));
-        if (!ensureSupportedIdentifiers(WorkflowFieldNames.SCHEMA, List.of(request.getSchema()), snapshot, "discovering")) {
+        if (!ensureSupportedIdentifiers(WorkflowFieldNames.SCHEMA, List.of(request.getSchema()), snapshot, WorkflowLifecycle.STEP_DISCOVERING)) {
             snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
             return false;
         }
@@ -149,7 +149,7 @@ public final class WorkflowPlanningContextValidator {
     private void addMissingTableQuestion(final WorkflowRequest request, final ClarifiedIntent clarifiedIntent, final WorkflowContextSnapshot snapshot, final String message) {
         if (isEmptyIdentifier(request.getTable())) {
             clarifiedIntent.getClarificationMessages().add("Please specify target table.");
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.TABLE_REQUIRED, "error", "intaking",
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.TABLE_REQUIRED, "error", WorkflowLifecycle.STEP_INTAKING,
                     message, "Provide the logical table name.", true, Map.of()));
         }
     }
@@ -157,7 +157,7 @@ public final class WorkflowPlanningContextValidator {
     private void addMissingColumnQuestion(final WorkflowRequest request, final ClarifiedIntent clarifiedIntent, final WorkflowContextSnapshot snapshot, final String message) {
         if (isEmptyIdentifier(request.getColumn())) {
             clarifiedIntent.getClarificationMessages().add("Please specify target column.");
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.COLUMN_REQUIRED, "error", "intaking",
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.COLUMN_REQUIRED, "error", WorkflowLifecycle.STEP_INTAKING,
                     message, "Provide the logical column name.", true, Map.of()));
         }
     }
@@ -246,7 +246,7 @@ public final class WorkflowPlanningContextValidator {
         if (table.isPresent()) {
             return true;
         }
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.TABLE_NOT_FOUND, "error", "discovering",
+        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.TABLE_NOT_FOUND, "error", WorkflowLifecycle.STEP_DISCOVERING,
                 String.format("Table `%s` does not exist in Proxy logical metadata.", request.getTable()), "Check database, schema and table name.", false, Map.of()));
         return false;
     }
@@ -258,7 +258,7 @@ public final class WorkflowPlanningContextValidator {
                 return true;
             }
         }
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.COLUMN_NOT_FOUND, "error", "discovering",
+        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.COLUMN_NOT_FOUND, "error", WorkflowLifecycle.STEP_DISCOVERING,
                 String.format("Column `%s` does not exist in Proxy logical metadata.", request.getColumn()), "Check column name.", false, Map.of()));
         return false;
     }
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupport.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupport.java
index 7f4c8168861..6f1451803e7 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupport.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupport.java
@@ -96,19 +96,19 @@ public final class WorkflowPlanningSupport {
     public boolean ensureLifecycleState(final String ruleLabel, final ClarifiedIntent clarifiedIntent,
                                         final boolean ruleExists, final WorkflowContextSnapshot snapshot) {
         String actualOperationType = clarifiedIntent.getOperationType().toLowerCase(Locale.ENGLISH);
-        if ("create".equals(actualOperationType) && ruleExists) {
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", "discovering",
+        if (WorkflowLifecycle.OPERATION_CREATE.equals(actualOperationType) && ruleExists) {
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", WorkflowLifecycle.STEP_DISCOVERING,
                     String.format("%s already exists for the target column.", ruleLabel), "Use a supported change path for the existing rule, or drop it before creating a replacement.", false,
                     Map.of()));
             return false;
         }
-        if ("alter".equals(actualOperationType) && !ruleExists) {
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", "discovering",
+        if (WorkflowLifecycle.OPERATION_ALTER.equals(actualOperationType) && !ruleExists) {
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", WorkflowLifecycle.STEP_DISCOVERING,
                     String.format("%s does not exist for the target column.", ruleLabel), "Use create or confirm the target column.", false, Map.of()));
             return false;
         }
-        if ("drop".equals(actualOperationType) && !ruleExists) {
-            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DROP_TARGET_RULE_NOT_FOUND, "error", "discovering",
+        if (WorkflowLifecycle.OPERATION_DROP.equals(actualOperationType) && !ruleExists) {
+            snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.DROP_TARGET_RULE_NOT_FOUND, "error", WorkflowLifecycle.STEP_DISCOVERING,
                     String.format("%s does not exist for the target column.", ruleLabel), "Confirm target table and column or skip the drop request.", false, Map.of()));
             return false;
         }
@@ -127,7 +127,7 @@ public final class WorkflowPlanningSupport {
         if (containsOperationType(supportedOperationTypes, clarifiedIntent.getOperationType())) {
             return true;
         }
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.WORKFLOW_STATUS_INVALID, "error", "intaking",
+        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.WORKFLOW_STATUS_INVALID, "error", WorkflowLifecycle.STEP_INTAKING,
                 "Unsupported workflow operation type.", String.format("Use one of: %s.", String.join(", ", supportedOperationTypes)), false,
                 Map.of("supported_operation_types", supportedOperationTypes)));
         clarifiedIntent.getInferredValues().remove(WorkflowFieldNames.OPERATION_TYPE);
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowRequestBinder.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowRequestBinder.java
index dda64211eb0..3e550aee9c1 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowRequestBinder.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowRequestBinder.java
@@ -69,6 +69,20 @@ public final class WorkflowRequestBinder {
         return bindPlanningRequest(WorkflowRequest::new, arguments, featureArgumentBinder, structuredIntentBinder);
     }
     
+    /**
+     * Apply a present structured intent string field without changing its representation.
+     *
+     * @param values structured intent values
+     * @param fieldName field name
+     * @param consumer field consumer
+     */
+    public static void applyStringField(final Map<String, Object> values, final String fieldName, final Consumer<String> consumer) {
+        Object value = values.get(fieldName);
+        if (null != value) {
+            consumer.accept(String.valueOf(value));
+        }
+    }
+    
     private static void bindCommonPlanningFields(final WorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
         request.setPlanId(workflowPlanningArguments.getStringArgument(WorkflowFieldNames.PLAN_ID));
         request.setDatabase(workflowPlanningArguments.getStringArgument(WorkflowFieldNames.DATABASE));
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowSQLUtils.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowSQLUtils.java
index c55dd64a0bc..29fff36a12e 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowSQLUtils.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowSQLUtils.java
@@ -25,12 +25,8 @@ import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
 import org.apache.shardingsphere.mcp.api.protocol.exception.MCPInvalidRequestException;
 import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseDialect;
 
-import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
 import java.util.Properties;
 import java.util.Set;
 import java.util.TreeMap;
@@ -170,20 +166,6 @@ public final class WorkflowSQLUtils {
         return trimToEmpty(value).replace("'", "''");
     }
     
-    /**
-     * Create properties with trimmed string values.
-     *
-     * @param entries property entries
-     * @return created properties
-     */
-    public static Properties createProperties(final Map<String, String> entries) {
-        Properties result = new Properties();
-        for (Entry<String, String> entry : entries.entrySet()) {
-            result.setProperty(entry.getKey(), trimToEmpty(entry.getValue()));
-        }
-        return result;
-    }
-    
     /**
      * Create an algorithm fragment for DistSQL.
      *
@@ -196,7 +178,7 @@ public final class WorkflowSQLUtils {
         if (actualType.isEmpty()) {
             return "";
         }
-        Properties actualProperties = createProperties(properties);
+        Properties actualProperties = WorkflowAlgorithmUtils.createProperties(properties);
         return actualProperties.isEmpty()
                 ? String.format("TYPE(NAME='%s')", escapeLiteral(actualType))
                 : String.format("TYPE(NAME='%s', PROPERTIES(%s))", escapeLiteral(actualType), createPropertiesFragment(actualProperties));
@@ -208,60 +190,6 @@ public final class WorkflowSQLUtils {
                 .collect(Collectors.joining(", "));
     }
     
-    private static Map<String, String> parsePropertyEntries(final List<String> entries) {
-        Map<String, String> result = new LinkedHashMap<>(entries.size(), 1F);
-        for (String each : entries) {
-            int separatorIndex = findPropertySeparatorIndex(each);
-            if (-1 == separatorIndex) {
-                continue;
-            }
-            String key = each.substring(0, separatorIndex).trim();
-            String value = each.substring(separatorIndex + 1).trim();
-            if (!key.isEmpty()) {
-                result.put(key, value);
-            }
-        }
-        return result;
-    }
-    
-    /**
-     * Create a property map from supported property carrier types.
-     *
-     * @param value property carrier value
-     * @return normalized property map
-     */
-    public static Map<String, String> createPropertyMap(final Object value) {
-        if (null == value) {
-            return Map.of();
-        }
-        if (value instanceof Properties) {
-            return createPropertyMap((Properties) value);
-        }
-        if (value instanceof Map) {
-            return createPropertyMap((Map<?, ?>) value);
-        }
-        return parsePropertyString(String.valueOf(value));
-    }
-    
-    private static Map<String, String> createPropertyMap(final Properties props) {
-        Map<String, String> result = new LinkedHashMap<>(props.size(), 1F);
-        for (String each : props.stringPropertyNames()) {
-            result.put(each, trimToEmpty(props.getProperty(each)));
-        }
-        return result;
-    }
-    
-    private static Map<String, String> createPropertyMap(final Map<?, ?> props) {
-        Map<String, String> result = new LinkedHashMap<>(props.size(), 1F);
-        for (Entry<?, ?> entry : props.entrySet()) {
-            String key = trimToEmpty(Objects.toString(entry.getKey(), ""));
-            if (!key.isEmpty()) {
-                result.put(key, trimToEmpty(Objects.toString(entry.getValue(), "")));
-            }
-        }
-        return result;
-    }
-    
     private static boolean isDelimitedIdentifier(final String identifier) {
         return isDelimitedIdentifier(identifier, BACK_QUOTE, BACK_QUOTE) || isDelimitedIdentifier(identifier, DOUBLE_QUOTE, DOUBLE_QUOTE) || isDelimitedIdentifier(identifier, '[', ']');
     }
@@ -294,41 +222,6 @@ public final class WorkflowSQLUtils {
                         + quoteCharacter.getEndDelimiter();
     }
     
-    private static Map<String, String> parsePropertyString(final String value) {
-        String actualValue = trimToEmpty(value);
-        if (actualValue.isEmpty() || "{}".equals(actualValue)) {
-            return Map.of();
-        }
-        String normalizedValue = actualValue;
-        if (normalizedValue.startsWith("{") && normalizedValue.endsWith("}")) {
-            normalizedValue = normalizedValue.substring(1, normalizedValue.length() - 1);
-        }
-        List<String> entries = List.of(normalizedValue.split(","));
-        Map<String, String> result = new LinkedHashMap<>(entries.size(), 1F);
-        for (Entry<String, String> entry : parsePropertyEntries(entries).entrySet()) {
-            result.put(stripQuotes(entry.getKey()), stripQuotes(entry.getValue()));
-        }
-        return result;
-    }
-    
-    private static int findPropertySeparatorIndex(final String propertyEntry) {
-        int equalsIndex = propertyEntry.indexOf('=');
-        return -1 == equalsIndex ? propertyEntry.indexOf(':') : equalsIndex;
-    }
-    
-    private static String stripQuotes(final String value) {
-        String actualValue = trimToEmpty(value);
-        if (2 > actualValue.length()) {
-            return actualValue;
-        }
-        char first = actualValue.charAt(0);
-        char last = actualValue.charAt(actualValue.length() - 1);
-        if ('\'' == first && '\'' == last || '"' == first && '"' == last) {
-            return actualValue.substring(1, actualValue.length() - 1);
-        }
-        return actualValue;
-    }
-    
     private static String trimToEmpty(final String value) {
         return null == value ? "" : value.trim();
     }
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupport.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupport.java
index b412bdd8782..ee17a771ca4 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupport.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupport.java
@@ -159,7 +159,7 @@ public final class WorkflowValidationSupport {
         if (!WorkflowLifecycle.STATUS_FAILED.equals(validationReport.getOverallStatus())) {
             return List.of();
         }
-        return List.of(new WorkflowIssue(resolveValidationIssueCode(validationReport), "error", "validating",
+        return List.of(new WorkflowIssue(resolveValidationIssueCode(validationReport), "error", WorkflowLifecycle.STEP_VALIDATING,
                 "Validation detected mismatches between the plan and the current state.", "Inspect mismatches and re-run the workflow after fixes.", true, Map.of()).toMap());
     }
     
@@ -215,7 +215,7 @@ public final class WorkflowValidationSupport {
         result.put(MCPPayloadFieldNames.SUMMARY, String.format("Workflow validation cannot run for plan `%s`.", snapshot.getPlanId()));
         result.put(WorkflowFieldNames.PLAN_ID, snapshot.getPlanId());
         result.put("status", WorkflowLifecycle.STATUS_FAILED);
-        result.put("issues", List.of(new WorkflowIssue(issueCode, "error", "validating", message, userAction, false, Map.of()).toMap()));
+        result.put("issues", List.of(new WorkflowIssue(issueCode, "error", WorkflowLifecycle.STEP_VALIDATING, message, userAction, false, Map.of()).toMap()));
         result.put("overall_status", WorkflowLifecycle.STATUS_FAILED);
         result.put("mismatches", List.of());
         result.put("recovery_guidance", userAction);
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseCapabilityProviderTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseCapabilityProviderTest.java
index 54aaa387715..3974af4294d 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseCapabilityProviderTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseCapabilityProviderTest.java
@@ -17,6 +17,8 @@
 
 package org.apache.shardingsphere.mcp.support.database.capability;
 
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
+
 import org.apache.shardingsphere.database.connector.core.metadata.database.enums.QuoteCharacter;
 import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
 import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.schema.DefaultSchemaOption;
@@ -68,12 +70,12 @@ class MCPDatabaseCapabilityProviderTest {
                 is(EnumSet.of(SupportedMCPMetadataObjectType.SCHEMA, SupportedMCPMetadataObjectType.TABLE, SupportedMCPMetadataObjectType.VIEW, SupportedMCPMetadataObjectType.COLUMN,
                         SupportedMCPMetadataObjectType.INDEX)));
         assertThat(actual.get().getTransactionCapability(), is(TransactionCapability.LOCAL_WITH_SAVEPOINT));
-        assertTrue(actual.get().isSupportsTransactionControl());
-        assertTrue(actual.get().isSupportsSavepoint());
+        assertTrue(actual.get().supportsTransactionControl());
+        assertTrue(actual.get().supportsSavepoint());
         assertThat(actual.get().getDefaultSchemaSemantics(), is(DialectSchemaSemantics.DATABASE_AS_SCHEMA));
         assertThat(actual.get().getSchemaExecutionSemantics(), is(SchemaExecutionSemantics.FIXED_TO_DATABASE));
-        assertFalse(actual.get().isSupportsCrossSchemaSql());
-        assertTrue(actual.get().isSupportsExplain());
+        assertFalse(actual.get().supportsCrossSchemaSql());
+        assertTrue(actual.get().supportsExplain());
         assertFalse(actual.get().getIdentifierCasePolicySet().getPolicy(IdentifierScope.TABLE).matches("phone", "Phone", QuoteCharacter.NONE));
     }
     
@@ -84,8 +86,8 @@ class MCPDatabaseCapabilityProviderTest {
         assertTrue(actual.get().getSupportedMetadataObjectTypes().contains(SupportedMCPMetadataObjectType.INDEX));
         assertFalse(actual.get().getSupportedMetadataObjectTypes().contains(SupportedMCPMetadataObjectType.SEQUENCE));
         assertThat(actual.get().getTransactionCapability(), is(TransactionCapability.NONE));
-        assertFalse(actual.get().isSupportsTransactionControl());
-        assertFalse(actual.get().isSupportsSavepoint());
+        assertFalse(actual.get().supportsTransactionControl());
+        assertFalse(actual.get().supportsSavepoint());
         assertThat(actual.get().getDefaultSchemaSemantics(), is(DialectSchemaSemantics.DATABASE_AS_SCHEMA));
         assertThat(actual.get().getSchemaExecutionSemantics(), is(SchemaExecutionSemantics.FIXED_TO_DATABASE));
     }
@@ -121,13 +123,13 @@ class MCPDatabaseCapabilityProviderTest {
                 SchemaExecutionSemantics.FIXED_TO_DATABASE == expectedSchemaExecutionSemantics ? DialectSchemaSemantics.DATABASE_AS_SCHEMA : DialectSchemaSemantics.NATIVE_SCHEMA);
         Optional<MCPDatabaseCapability> actual = createCapabilityProvider(databaseType, capabilityFixture).provide("logic_db");
         assertTrue(actual.isPresent());
-        assertThat(actual.get().isSupportsTransactionControl(), is(expectedTransactionControl));
-        assertThat(actual.get().isSupportsSavepoint(), is(expectedSavepoint));
+        assertThat(actual.get().supportsTransactionControl(), is(expectedTransactionControl));
+        assertThat(actual.get().supportsSavepoint(), is(expectedSavepoint));
         assertTrue(actual.get().getSupportedMetadataObjectTypes().contains(SupportedMCPMetadataObjectType.INDEX));
         assertThat(actual.get().getSupportedMetadataObjectTypes().contains(SupportedMCPMetadataObjectType.SEQUENCE), is(expectedSequenceSupport));
         assertThat(actual.get().getSchemaExecutionSemantics(), is(expectedSchemaExecutionSemantics));
-        assertThat(actual.get().isSupportsCrossSchemaSql(), is(SchemaExecutionSemantics.BEST_EFFORT == expectedSchemaExecutionSemantics));
-        assertThat(actual.get().isSupportsExplain(), is(expectedExplainSupport));
+        assertThat(actual.get().supportsCrossSchemaSql(), is(SchemaExecutionSemantics.BEST_EFFORT == expectedSchemaExecutionSemantics));
+        assertThat(actual.get().supportsExplain(), is(expectedExplainSupport));
     }
     
     private MCPDatabaseCapabilityProvider createCapabilityProvider() {
@@ -180,7 +182,10 @@ class MCPDatabaseCapabilityProviderTest {
     
     private RuntimeDatabaseProfile createDatabaseProfile(final String databaseName, final String databaseType, final CapabilityFixture capabilityFixture,
                                                          final IdentifierCasePolicySet identifierCasePolicySet) {
-        return new RuntimeDatabaseProfile(databaseName, databaseType, "", capabilityFixture.transactionSupported, capabilityFixture.savepointSupported, identifierCasePolicySet);
+        TransactionCapability transactionCapability = capabilityFixture.transactionSupported
+                ? capabilityFixture.savepointSupported ? TransactionCapability.LOCAL_WITH_SAVEPOINT : TransactionCapability.LOCAL
+                : TransactionCapability.NONE;
+        return new RuntimeDatabaseProfile(databaseName, databaseType, "", transactionCapability, identifierCasePolicySet);
     }
     
     private static Stream<Arguments> provideCapabilityMatrixArguments() {
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialectTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialectTest.java
index 5202873ae5f..98c2b368710 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialectTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialectTest.java
@@ -95,18 +95,6 @@ class MCPDatabaseDialectTest {
         }
     }
     
-    @ParameterizedTest(name = "{0}")
-    @MethodSource("getTransactionCapabilityArguments")
-    void assertGetTransactionCapability(final String name, final boolean transactionSupported, final boolean savepointSupported, final TransactionCapability expected) {
-        try (
-                MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class);
-                MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
-            DatabaseType databaseType = mockDatabaseType("Fixture", typedSPILoader);
-            mockDialectDatabaseMetaData(databaseType, databaseTypedSPILoader);
-            assertThat(MCPDatabaseDialect.of("Fixture").getTransactionCapability(transactionSupported, savepointSupported), is(expected));
-        }
-    }
-    
     @Test
     void assertIsSequenceSupported() {
         try (
@@ -209,11 +197,4 @@ class MCPDatabaseDialectTest {
         return result;
     }
     
-    private static Stream<Arguments> getTransactionCapabilityArguments() {
-        return Stream.of(
-                Arguments.of("not supported", false, false, TransactionCapability.NONE),
-                Arguments.of("local only", true, false, TransactionCapability.LOCAL),
-                Arguments.of("local with savepoint", true, true, TransactionCapability.LOCAL_WITH_SAVEPOINT));
-    }
-    
 }
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcDatabaseProfileLoaderTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcDatabaseProfileLoaderTest.java
index 2d6b539f87c..112c38c34bc 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcDatabaseProfileLoaderTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcDatabaseProfileLoaderTest.java
@@ -24,6 +24,7 @@ import org.apache.shardingsphere.database.connector.core.metadata.identifier.Ide
 import org.apache.shardingsphere.database.connector.core.type.DatabaseTypeFactory;
 import org.apache.shardingsphere.infra.exception.external.ShardingSphereExternalException;
 import org.apache.shardingsphere.infra.metadata.identifier.IdentifierCasePolicyResolver;
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
 import org.apache.shardingsphere.mcp.support.fixture.SupportDatabaseTypeFactoryMocker;
 import org.junit.jupiter.api.Test;
 import org.mockito.MockedConstruction;
@@ -65,19 +66,30 @@ class MCPJdbcDatabaseProfileLoaderTest {
             assertThat(actual.getDatabase(), is("logic_db"));
             assertThat(actual.getDatabaseType(), is("FixtureDB"));
             assertThat(actual.getDatabaseVersion(), is("1.0"));
-            assertTrue(actual.isSupportsTransaction());
-            assertTrue(actual.isSupportsSavepoint());
+            assertThat(actual.getTransactionCapability(), is(TransactionCapability.LOCAL_WITH_SAVEPOINT));
             assertThat(actual.getIdentifierCasePolicySet(), is(expectedIdentifierCasePolicySet));
         }
     }
     
     @Test
     void assertLoadWithoutTransaction() throws SQLException {
+        Connection connection = mock(Connection.class);
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = createRuntimeDatabaseConfiguration(
+                SupportDatabaseTypeFactoryMocker.createJdbcUrl("FixtureDB"), "1.0", false, true, connection);
+        DatabaseMetaData databaseMetaData = connection.getMetaData();
+        try (MockedStatic<DatabaseTypeFactory> ignored = SupportDatabaseTypeFactoryMocker.mockByConnectionMetadata()) {
+            RuntimeDatabaseProfile actual = new MCPJdbcDatabaseProfileLoader().load("logic_db", runtimeDatabaseConfig);
+            assertThat(actual.getTransactionCapability(), is(TransactionCapability.NONE));
+            verify(databaseMetaData, never()).supportsSavepoints();
+        }
+    }
+    
+    @Test
+    void assertLoadWithoutSavepoint() throws SQLException {
         try (MockedStatic<DatabaseTypeFactory> ignored = SupportDatabaseTypeFactoryMocker.mockByConnectionMetadata()) {
             RuntimeDatabaseProfile actual =
-                    new MCPJdbcDatabaseProfileLoader().load("logic_db", createRuntimeDatabaseConfiguration(SupportDatabaseTypeFactoryMocker.createJdbcUrl("FixtureDB"), "1.0", false, true));
-            assertFalse(actual.isSupportsTransaction());
-            assertFalse(actual.isSupportsSavepoint());
+                    new MCPJdbcDatabaseProfileLoader().load("logic_db", createRuntimeDatabaseConfiguration(SupportDatabaseTypeFactoryMocker.createJdbcUrl("FixtureDB"), "1.0", true, false));
+            assertThat(actual.getTransactionCapability(), is(TransactionCapability.LOCAL));
         }
     }
     
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcMetadataLoaderFailureTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcMetadataLoaderFailureTest.java
index d4e4334ea55..5f2fd535f31 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcMetadataLoaderFailureTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcMetadataLoaderFailureTest.java
@@ -17,6 +17,8 @@
 
 package org.apache.shardingsphere.mcp.support.database.metadata.jdbc;
 
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
+
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicyFactory;
 import org.junit.jupiter.api.Test;
 
@@ -38,7 +40,7 @@ class MCPJdbcMetadataLoaderFailureTest {
         MCPJdbcMetadataLoader metadataLoader = new MCPJdbcMetadataLoader();
         RuntimeDatabaseConnectionException actual = assertThrows(RuntimeDatabaseConnectionException.class,
                 () -> metadataLoader.load("logic_db", runtimeDatabaseConfiguration,
-                        new RuntimeDatabaseProfile("logic_db", "FixtureDB", "", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet())));
+                        new RuntimeDatabaseProfile("logic_db", "FixtureDB", "", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet())));
         assertThat(actual.getMessage(), is("Runtime database `logic_db` connection failed: connection_failed."));
         assertThat(actual.getCause(), is(expected));
     }
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/response/MCPDatabaseCapabilityResponseTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/response/MCPDatabaseCapabilityResponseTest.java
index b6244402367..95e6ef23908 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/response/MCPDatabaseCapabilityResponseTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/response/MCPDatabaseCapabilityResponseTest.java
@@ -52,11 +52,11 @@ class MCPDatabaseCapabilityResponseTest {
             supportedStatementClasses.add(SupportedMCPStatement.EXPLAIN);
         }
         when(actualCapability.getSupportedStatementClasses()).thenReturn(supportedStatementClasses);
-        when(actualCapability.isSupportsTransactionControl()).thenReturn(true);
-        when(actualCapability.isSupportsSavepoint()).thenReturn(true);
+        when(actualCapability.supportsTransactionControl()).thenReturn(true);
+        when(actualCapability.supportsSavepoint()).thenReturn(true);
         when(actualCapability.getDefaultSchemaSemantics()).thenReturn(DialectSchemaSemantics.DATABASE_AS_SCHEMA);
         when(actualCapability.getSchemaExecutionSemantics()).thenReturn(SchemaExecutionSemantics.FIXED_TO_DATABASE);
-        when(actualCapability.isSupportsExplain()).thenReturn(supportsExplain);
+        when(actualCapability.supportsExplain()).thenReturn(supportsExplain);
         Map<String, Object> actual = new MCPDatabaseCapabilityResponse(actualCapability).toPayload();
         assertThat(actual, is(Map.ofEntries(
                 Map.entry("response_mode", "detail"),
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/service/RuntimeDatabaseValidationServiceTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/service/RuntimeDatabaseValidationServiceTest.java
index 2a45094769e..0831adf4080 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/service/RuntimeDatabaseValidationServiceTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/service/RuntimeDatabaseValidationServiceTest.java
@@ -17,6 +17,8 @@
 
 package org.apache.shardingsphere.mcp.support.database.tool.service;
 
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
+
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicyFactory;
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicySet;
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierScope;
@@ -134,7 +136,7 @@ class RuntimeDatabaseValidationServiceTest {
                 IdentifierCasePolicyFactory.newSensitivePolicySet().getPolicy(IdentifierScope.TABLE),
                 Map.of(IdentifierScope.SCHEMA, IdentifierCasePolicyFactory.newInsensitivePolicySet().getPolicy(IdentifierScope.SCHEMA)));
         when(profileLoader.load(any(), any(RuntimeDatabaseConfiguration.class)))
-                .thenReturn(new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", true, true, identifierCasePolicySet));
+                .thenReturn(new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, identifierCasePolicySet));
         when(metadataLoader.load(any(), any(RuntimeDatabaseConfiguration.class), any(RuntimeDatabaseProfile.class))).thenReturn(createMetadata("Logic_DB"));
         RuntimeDatabaseValidationResult actual = service.validate(new RuntimeDatabaseValidationRequest("logic_db"),
                 ignored -> Optional.of(runtimeDatabaseConfig), RuntimeDatabaseValidationServiceTest::createRecoveryPayload);
@@ -150,7 +152,7 @@ class RuntimeDatabaseValidationServiceTest {
         MCPJdbcMetadataLoader metadataLoader = getMetadataLoader();
         RuntimeDatabaseConfiguration runtimeDatabaseConfig = new RuntimeDatabaseConfiguration(InvisibleDatabaseDriver.JDBC_URL, "demo", "", InvisibleDatabaseDriver.class.getName());
         when(profileLoader.load(any(), any(RuntimeDatabaseConfiguration.class))).thenReturn(
-                new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", true, true, IdentifierCasePolicyFactory.newSensitivePolicySet()));
+                new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newSensitivePolicySet()));
         when(metadataLoader.load(any(), any(RuntimeDatabaseConfiguration.class), any(RuntimeDatabaseProfile.class))).thenReturn(createMetadata("Logic_DB"));
         RuntimeDatabaseValidationResult actual = service.validate(new RuntimeDatabaseValidationRequest("logic_db"),
                 ignored -> Optional.of(runtimeDatabaseConfig), RuntimeDatabaseValidationServiceTest::createRecoveryPayload);
@@ -172,7 +174,7 @@ class RuntimeDatabaseValidationServiceTest {
                 IdentifierCasePolicyFactory.newInsensitivePolicySet().getPolicy(IdentifierScope.TABLE),
                 Map.of(IdentifierScope.SCHEMA, IdentifierCasePolicyFactory.newSensitivePolicySet().getPolicy(IdentifierScope.SCHEMA)));
         when(getProfileLoader().load(any(), any(RuntimeDatabaseConfiguration.class)))
-                .thenReturn(new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", true, true, identifierCasePolicySet));
+                .thenReturn(new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, identifierCasePolicySet));
         when(getMetadataLoader().load(any(), any(RuntimeDatabaseConfiguration.class), any(RuntimeDatabaseProfile.class))).thenReturn(createMetadata("public"));
         RuntimeDatabaseValidationResult actual = service.validate(new RuntimeDatabaseValidationRequest("logic_db"),
                 ignored -> Optional.of(runtimeDatabaseConfig), RuntimeDatabaseValidationServiceTest::createRecoveryPayload);
@@ -241,7 +243,7 @@ class RuntimeDatabaseValidationServiceTest {
     }
     
     private static RuntimeDatabaseProfile createProfile() {
-        return new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet());
+        return new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet());
     }
     
     private static RuntimeDatabaseConfiguration createRuntimeDatabaseConfiguration() {
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogPayloadBuilderTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogPayloadBuilderTest.java
index b42511d5379..73b4060b939 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogPayloadBuilderTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogPayloadBuilderTest.java
@@ -40,7 +40,8 @@ class MCPDescriptorCatalogPayloadBuilderTest {
                 createResourceDescriptor("shardingsphere://capabilities", MCPResourceAnnotations.EMPTY),
                 createResourceDescriptor("shardingsphere://databases", MCPResourceAnnotations.EMPTY));
         Map<String, Object> payload = MCPDescriptorCatalogPayloadBuilder.build(
-                createCatalog(resourceDescriptors, List.of(createToolDescriptor(new MCPToolAnnotations(null, false, true, false, true)))),
+                createCatalog(resourceDescriptors, List.of(createToolDescriptor(MCPToolAnnotations.builder()
+                        .title(null).readOnlyHint(false).destructiveHint(true).idempotentHint(false).openWorldHint(true).build()))),
                 List.of("shardingsphere://capabilities"), List.of("database_gateway_test_tool"), List.of("SelectStatement"));
         assertThat(payload.get("response_mode"), is("catalog"));
         assertThat(payload.get("guidanceResource"), is("shardingsphere://guidance"));
@@ -81,7 +82,8 @@ class MCPDescriptorCatalogPayloadBuilderTest {
     @Test
     @SuppressWarnings("unchecked")
     void assertBuildToolAnnotationsPayload() {
-        MCPToolDescriptor toolDescriptor = createToolDescriptor(new MCPToolAnnotations(null, false, true, false, true));
+        MCPToolDescriptor toolDescriptor = createToolDescriptor(MCPToolAnnotations.builder()
+                .title(null).readOnlyHint(false).destructiveHint(true).idempotentHint(false).openWorldHint(true).build());
         Map<String, Object> payload = MCPDescriptorCatalogPayloadBuilder.build(createCatalog(List.of(), List.of(toolDescriptor)), List.of(), List.of(), List.of());
         List<Map<String, Object>> actualTools = (List<Map<String, Object>>) payload.get("tools");
         assertThat((Map<String, Object>) actualTools.get(0).get("annotations"), is(Map.of(
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidatorTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidatorTest.java
index 9db5e5f9a74..973be440cc7 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidatorTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidatorTest.java
@@ -49,13 +49,13 @@ class MCPDescriptorCatalogValidatorTest {
     void assertValidateScopedProtocolNonGoalFieldsAreNotRequired() {
         assertDoesNotThrow(() -> MCPDescriptorCatalogValidator.validate(createCatalog(
                 List.of(createResourceDescriptor(MCPResourceAnnotations.EMPTY)),
-                List.of(createToolDescriptor(new MCPToolAnnotations("Test Tool", true, false, true, true))))));
+                List.of(createToolDescriptor(createReadOnlyToolAnnotations())))));
     }
     
     @Test
     void assertValidateRejectsRemovedModelFacingOutputField() {
         assertValidationError(createCatalog(List.of(), List.of(createToolDescriptor(
-                "database_gateway_test_tool", new MCPToolAnnotations("Test Tool", true, false, true, true),
+                "database_gateway_test_tool", createReadOnlyToolAnnotations(),
                 createOutputSchema(Map.of("recommended_next_tool", Map.of("type", "string", "description", "Removed alias.")))))),
                 "Tool `database_gateway_test_tool` model-facing contract must use canonical fields instead of removed `recommended_next_tool`.");
     }
@@ -66,7 +66,7 @@ class MCPDescriptorCatalogValidatorTest {
                 "database_gateway_test_tool", "Test Tool", "Run a test tool.",
                 createInputSchema(Map.of("query", Map.of("type", "string", "description", "Query."),
                         "user_overrides", Map.of("type", "object", "description", "Removed duplicate input."))),
-                createOutputSchema(), new MCPToolAnnotations("Test Tool", true, false, true, true), Map.of()))),
+                createOutputSchema(), createReadOnlyToolAnnotations(), Map.of()))),
                 "Tool `database_gateway_test_tool` model-facing contract must use canonical fields instead of removed `user_overrides`.");
     }
     
@@ -75,7 +75,7 @@ class MCPDescriptorCatalogValidatorTest {
         assertValidationError(createCatalog(List.of(), List.of(new MCPToolDescriptor(
                 "database_gateway_test_tool", "Test Tool", "Run a test tool.",
                 createInputSchema(Map.of("query", Map.of("type", "string", "description", "Query.")), List.of("query", "required_arguments")),
-                createOutputSchema(), new MCPToolAnnotations("Test Tool", true, false, true, true), Map.of()))),
+                createOutputSchema(), createReadOnlyToolAnnotations(), Map.of()))),
                 "Tool `database_gateway_test_tool` model-facing contract must use canonical fields instead of removed `required_arguments`.");
     }
     
@@ -85,7 +85,7 @@ class MCPDescriptorCatalogValidatorTest {
         inputSchema.put("oneOf", List.of(Map.of("required", List.of("query"))));
         assertValidationError(createCatalog(List.of(), List.of(new MCPToolDescriptor(
                 "database_gateway_test_tool", "Test Tool", "Run a test tool.", inputSchema,
-                createOutputSchema(), new MCPToolAnnotations("Test Tool", true, false, true, true), Map.of()))),
+                createOutputSchema(), createReadOnlyToolAnnotations(), Map.of()))),
                 "Tool `database_gateway_test_tool` inputSchema contains unsupported top-level field `oneOf`.");
     }
     
@@ -94,7 +94,7 @@ class MCPDescriptorCatalogValidatorTest {
         assertValidationError(createCatalog(List.of(), List.of(new MCPToolDescriptor(
                 "database_gateway_test_tool", "Test Tool", "Run a test tool.",
                 createInputSchema(Map.of("query", Map.of("type", "string", "description", "Query.", "pattern", ".+"))),
-                createOutputSchema(), new MCPToolAnnotations("Test Tool", true, false, true, true), Map.of()))),
+                createOutputSchema(), createReadOnlyToolAnnotations(), Map.of()))),
                 "Tool `database_gateway_test_tool` inputSchema at `inputSchema.properties.query` contains unsupported field `pattern`.");
     }
     
@@ -102,7 +102,7 @@ class MCPDescriptorCatalogValidatorTest {
     void assertValidateRejectsUnknownRelatedResourceUri() {
         assertValidationError(createCatalog(List.of(), List.of(new MCPToolDescriptor(
                 "database_gateway_test_tool", "Test Tool", "Run a test tool.", createInputSchema(),
-                createOutputSchema(), new MCPToolAnnotations("Test Tool", true, false, true, true),
+                createOutputSchema(), createReadOnlyToolAnnotations(),
                 Map.of(MCPShardingSphereMetadataKeys.RELATED_RESOURCE_URIS, List.of("shardingsphere://unknown"))))),
                 "Tool `database_gateway_test_tool` metadata `org.apache.shardingsphere/related-resource-uris` references unknown resource `shardingsphere://unknown`.");
     }
@@ -131,7 +131,8 @@ class MCPDescriptorCatalogValidatorTest {
     @Test
     void assertValidateRejectsPlanningToolReadOnlyHint() {
         String toolName = "database_gateway_test_plan_rule";
-        assertValidationError(createToolRuntimeCatalog(List.of(), List.of(createToolDescriptor(toolName, new MCPToolAnnotations("Test Tool", true, false, false, true),
+        assertValidationError(createToolRuntimeCatalog(List.of(), List.of(createToolDescriptor(toolName, MCPToolAnnotations.builder()
+                .title("Test Tool").readOnlyHint(true).destructiveHint(false).idempotentHint(false).openWorldHint(true).build(),
                 createWorkflowPlanOutputSchema(), createPlanningToolMeta("test.rule"))), List.of(new MCPToolRuntimeDescriptor(toolName, "plan", List.of()))),
                 "Planning tool `database_gateway_test_plan_rule` annotations.readOnlyHint must be false.");
     }
@@ -139,7 +140,8 @@ class MCPDescriptorCatalogValidatorTest {
     @Test
     void assertValidateRejectsPlanningToolDestructiveHint() {
         String toolName = "database_gateway_test_plan_rule";
-        assertValidationError(createToolRuntimeCatalog(List.of(), List.of(createToolDescriptor(toolName, new MCPToolAnnotations("Test Tool", false, true, false, true),
+        assertValidationError(createToolRuntimeCatalog(List.of(), List.of(createToolDescriptor(toolName, MCPToolAnnotations.builder()
+                .title("Test Tool").readOnlyHint(false).destructiveHint(true).idempotentHint(false).openWorldHint(true).build(),
                 createWorkflowPlanOutputSchema(), createPlanningToolMeta("test.rule"))), List.of(new MCPToolRuntimeDescriptor(toolName, "plan", List.of()))),
                 "Planning tool `database_gateway_test_plan_rule` annotations.destructiveHint must be false.");
     }
@@ -147,7 +149,8 @@ class MCPDescriptorCatalogValidatorTest {
     @Test
     void assertValidateRejectsPlanningToolIdempotentHint() {
         String toolName = "database_gateway_test_plan_rule";
-        assertValidationError(createToolRuntimeCatalog(List.of(), List.of(createToolDescriptor(toolName, new MCPToolAnnotations("Test Tool", false, false, true, true),
+        assertValidationError(createToolRuntimeCatalog(List.of(), List.of(createToolDescriptor(toolName, MCPToolAnnotations.builder()
+                .title("Test Tool").readOnlyHint(false).destructiveHint(false).idempotentHint(true).openWorldHint(true).build(),
                 createWorkflowPlanOutputSchema(), createPlanningToolMeta("test.rule"))), List.of(new MCPToolRuntimeDescriptor(toolName, "plan", List.of()))),
                 "Planning tool `database_gateway_test_plan_rule` annotations.idempotentHint must be false.");
     }
@@ -155,7 +158,7 @@ class MCPDescriptorCatalogValidatorTest {
     @Test
     void assertValidateRejectsUnsupportedNextActionSchemaField() {
         assertValidationError(createCatalog(List.of(), List.of(createToolDescriptor(
-                "database_gateway_test_tool", new MCPToolAnnotations("Test Tool", true, false, true, true),
+                "database_gateway_test_tool", createReadOnlyToolAnnotations(),
                 createOutputSchema(Map.of("next_actions", createNextActionsSchema("extra_context")))))),
                 "Tool `database_gateway_test_tool` next_actions item contains unsupported field `extra_context`.");
     }
@@ -163,7 +166,7 @@ class MCPDescriptorCatalogValidatorTest {
     @Test
     void assertValidateRejectsUnsupportedNextActionExampleField() {
         assertValidationError(createCatalog(List.of(), List.of(createToolDescriptor(
-                "database_gateway_test_tool", new MCPToolAnnotations("Test Tool", true, false, true, true),
+                "database_gateway_test_tool", createReadOnlyToolAnnotations(),
                 createOutputSchema(Map.of("next_actions", createNextActionsSchema()), List.of(Map.of("next_actions", List.of(
                         Map.of("order", 1, "type", "tool_call", "title", "Retry", "tool_name", "database_gateway_test_tool", "arguments", Map.of(), "extra_context", "bad")))))))),
                 "Tool `database_gateway_test_tool` next_actions example `tool_call` contains unsupported field `extra_context`.");
@@ -172,7 +175,7 @@ class MCPDescriptorCatalogValidatorTest {
     @Test
     void assertValidateRejectsMissingNextActionExampleField() {
         assertValidationError(createCatalog(List.of(), List.of(createToolDescriptor(
-                "database_gateway_test_tool", new MCPToolAnnotations("Test Tool", true, false, true, true),
+                "database_gateway_test_tool", createReadOnlyToolAnnotations(),
                 createOutputSchema(Map.of("next_actions", createNextActionsSchema()), List.of(Map.of("next_actions", List.of(
                         Map.of("order", 1, "type", "tool_call", "title", "Retry", "tool_name", "database_gateway_test_tool")))))))),
                 "Tool `database_gateway_test_tool` next_actions example `tool_call` must contain `arguments`.");
@@ -181,13 +184,13 @@ class MCPDescriptorCatalogValidatorTest {
     @Test
     void assertValidateAcceptsFeatureOwnedToolDescriptorWithoutExtensionMarker() {
         assertDoesNotThrow(() -> MCPDescriptorCatalogValidator.validate(createCatalog(List.of(), List.of(createToolDescriptor(
-                "database_gateway_extension_test_tool", new MCPToolAnnotations("Extension Tool", true, false, true, true), createOutputSchema())))));
+                "database_gateway_extension_test_tool", createReadOnlyToolAnnotations("Extension Tool"), createOutputSchema())))));
     }
     
     @Test
     void assertValidateRejectsIncompleteCoreToolDescriptor() {
         assertValidationError(createCatalog(List.of(), List.of(createToolDescriptor(
-                "database_gateway_search_metadata", new MCPToolAnnotations("Search Metadata", true, false, true, true), createOutputSchema()))),
+                "database_gateway_search_metadata", createReadOnlyToolAnnotations("Search Metadata"), createOutputSchema()))),
                 "Tool `database_gateway_search_metadata` outputSchema must declare `response_mode`.");
     }
     
@@ -308,7 +311,15 @@ class MCPDescriptorCatalogValidatorTest {
     }
     
     private MCPToolAnnotations createPlanningToolAnnotations() {
-        return new MCPToolAnnotations("Test Tool", false, false, false, true);
+        return MCPToolAnnotations.builder().title("Test Tool").readOnlyHint(false).destructiveHint(false).idempotentHint(false).openWorldHint(true).build();
+    }
+    
+    private MCPToolAnnotations createReadOnlyToolAnnotations() {
+        return createReadOnlyToolAnnotations("Test Tool");
+    }
+    
+    private MCPToolAnnotations createReadOnlyToolAnnotations(final String title) {
+        return MCPToolAnnotations.builder().title(title).readOnlyHint(true).destructiveHint(false).idempotentHint(true).openWorldHint(true).build();
     }
     
     private Map<String, Object> createInputSchema() {
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorComponentValidatorsTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorComponentValidatorsTest.java
index 5185f3adfca..6c6c506112e 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorComponentValidatorsTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorComponentValidatorsTest.java
@@ -102,7 +102,7 @@ class MCPDescriptorComponentValidatorsTest {
     }
     
     private MCPToolAnnotations createAnnotations() {
-        return new MCPToolAnnotations("Test Tool", true, false, true, true);
+        return MCPToolAnnotations.builder().title("Test Tool").readOnlyHint(true).destructiveHint(false).idempotentHint(true).openWorldHint(true).build();
     }
     
     private Map<String, Object> createInputSchema() {
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPProtocolDescriptorCatalogTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPProtocolDescriptorCatalogTest.java
index 8753562081f..16d57f114d8 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPProtocolDescriptorCatalogTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPProtocolDescriptorCatalogTest.java
@@ -75,7 +75,7 @@ class MCPProtocolDescriptorCatalogTest {
     
     private MCPToolDescriptor createToolDescriptor() {
         return new MCPToolDescriptor("database_gateway_test_tool", "Test Tool", "Run a test tool.", Map.of(), Map.of(),
-                new MCPToolAnnotations("Test Tool", true, false, true, true), Map.of());
+                MCPToolAnnotations.builder().title("Test Tool").readOnlyHint(true).destructiveHint(false).idempotentHint(true).openWorldHint(true).build(), Map.of());
     }
     
     private MCPPromptDescriptor createPromptDescriptor() {
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 fd87ac5e663..18d576b7b31 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
@@ -126,7 +126,7 @@ class MCPToolDescriptorValidationUtilsTest {
     }
     
     private MCPToolAnnotations createAnnotations() {
-        return new MCPToolAnnotations("Fixture Tool", true, false, true, false);
+        return MCPToolAnnotations.builder().title("Fixture Tool").readOnlyHint(true).destructiveHint(false).idempotentHint(true).openWorldHint(false).build();
     }
     
     private Map<String, Object> createOutputProperties(final Collection<String> outputFields) {
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/protocol/MCPNextActionUtilsTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/protocol/MCPNextActionUtilsTest.java
index 65c450fcf0d..65508f6f992 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/protocol/MCPNextActionUtilsTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/protocol/MCPNextActionUtilsTest.java
@@ -65,8 +65,11 @@ class MCPNextActionUtilsTest {
     
     @Test
     void assertCompleteArgument() {
-        Map<String, Object> actual = MCPNextActionUtils.completeArgument("ref/prompt", "inspect_metadata", "schema", "pub", Map.of("database", "logic_db"),
-                List.of("table"), "tool", "database_gateway_search_metadata", Map.of("query", "orders"), "Complete schema.");
+        Map<String, Object> actual = MCPNextActionUtils.completeArgument(MCPCompletionAction.builder()
+                .referenceType("ref/prompt").reference("inspect_metadata").argumentName("schema").argumentPrefix("pub")
+                .contextArguments(Map.of("database", "logic_db")).missingContextArguments(List.of("table"))
+                .resumeTargetType("tool").resumeTarget("database_gateway_search_metadata").resumeArguments(Map.of("query", "orders"))
+                .reason("Complete schema.").build());
         assertThat(actual.get("type"), is("completion"));
         assertThat(actual.get("title"), is("Complete schema"));
         assertThat(actual.get("ref"), is(Map.of("type", "ref/prompt", "name", "inspect_metadata")));
@@ -79,17 +82,21 @@ class MCPNextActionUtilsTest {
     
     @Test
     void assertCompleteArgumentNormalizesReferenceTypes() {
-        Map<String, Object> actual = MCPNextActionUtils.completeArgument("prompt", "inspect_metadata", "schema", "pub", Map.of("database", "logic_db"),
-                List.of("table"), "resource", "shardingsphere://databases/{database}/schemas/{schema}", Map.of("database", "logic_db"), "Complete schema.");
+        Map<String, Object> actual = MCPNextActionUtils.completeArgument(MCPCompletionAction.builder()
+                .referenceType("prompt").reference("inspect_metadata").argumentName("schema").argumentPrefix("pub")
+                .contextArguments(Map.of("database", "logic_db")).missingContextArguments(List.of("table"))
+                .resumeTargetType("resource").resumeTarget("shardingsphere://databases/{database}/schemas/{schema}").resumeArguments(Map.of("database", "logic_db"))
+                .reason("Complete schema.").build());
         assertThat(actual.get("ref"), is(Map.of("type", "ref/prompt", "name", "inspect_metadata")));
         assertThat(actual.get("resume_ref"), is(Map.of("type", "ref/resource", "uri", "shardingsphere://databases/{database}/schemas/{schema}")));
     }
     
     @Test
     void assertCompleteArgumentWithoutResumeTarget() {
-        Map<String, Object> actual = MCPNextActionUtils.completeArgument("ref/resource", "shardingsphere://databases/{database}", "database", "", Map.of(), List.of(), "", "", Map.of(),
-                "Complete database.");
+        Map<String, Object> actual = MCPNextActionUtils.completeArgument(MCPCompletionAction.builder()
+                .referenceType("ref/resource").reference("shardingsphere://databases/{database}").argumentName("database").reason("Complete database.").build());
         assertThat(actual.get("type"), is("completion"));
+        assertThat(actual.get("missing_context_arguments"), is(List.of()));
         assertFalse(actual.containsKey("context"));
         assertFalse(actual.containsKey("resume_ref"));
         assertFalse(actual.containsKey("resume_arguments"));
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowContextSnapshotTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowContextSnapshotTest.java
index cf83f38fb18..3aab53c7c95 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowContextSnapshotTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowContextSnapshotTest.java
@@ -46,6 +46,11 @@ class WorkflowContextSnapshotTest {
         originalSnapshot.getInteractionPlan().getValidationStrategy().put("layers", List.of("rule"));
         getStringList(originalSnapshot.getIssues().get(0).getDetails(), "missing_fields").add("schema");
         getStringList(originalSnapshot.getValidationReport().getMismatches().get(0), "layers").add("rule");
+        originalSnapshot.getAlgorithmCandidates().clear();
+        originalSnapshot.getPropertyRequirements().clear();
+        originalSnapshot.getDdlArtifacts().clear();
+        originalSnapshot.getRuleArtifacts().clear();
+        originalSnapshot.getIndexPlans().clear();
         assertThat(actualSnapshot.getRequest().getTable(), is("orders"));
         assertThat(actualSnapshot.getClarifiedIntent().getClarificationMessages(), is(List.of("provide schema")));
         assertTrue(actualSnapshot.getClarifiedIntent().getUnresolvedFields().isEmpty());
@@ -54,6 +59,11 @@ class WorkflowContextSnapshotTest {
         assertThat(actualSnapshot.getInteractionPlan().getValidationStrategy().get("layers"), is(List.of("ddl")));
         assertThat(actualSnapshot.getIssues().get(0).getDetails().get("missing_fields"), is(List.of("column")));
         assertThat(actualSnapshot.getValidationReport().getMismatches().get(0).get("layers"), is(List.of("ddl")));
+        assertThat(actualSnapshot.getAlgorithmCandidates().size(), is(1));
+        assertThat(actualSnapshot.getPropertyRequirements().size(), is(1));
+        assertThat(actualSnapshot.getDdlArtifacts().size(), is(1));
+        assertThat(actualSnapshot.getRuleArtifacts().size(), is(1));
+        assertThat(actualSnapshot.getIndexPlans().size(), is(1));
     }
     
     @Test
@@ -96,7 +106,8 @@ class WorkflowContextSnapshotTest {
         Map<String, Object> issueDetails = new LinkedHashMap<>(1, 1F);
         issueDetails.put("missing_fields", new LinkedList<>(List.of("column")));
         result.getIssues().add(new WorkflowIssue("code", "error", "stage", "message", "action", true, issueDetails));
-        result.getAlgorithmCandidates().add(new AlgorithmCandidate("primary", "AES", true, true, false, 90, "reason", ""));
+        result.getAlgorithmCandidates().add(AlgorithmCandidate.builder().algorithmRole("primary").algorithmType("AES")
+                .supportsDecrypt(true).supportsEquivalentFilter(true).supportsLike(false).recommendationScore(90).recommendationReason("reason").riskNotes("").build());
         result.getPropertyRequirements().add(new AlgorithmPropertyRequirement("primary", "key", true, true, "desc", ""));
         result.getDdlArtifacts().add(new DDLArtifact("ddl", "ALTER TABLE t ADD c VARCHAR(32)", 1));
         result.getRuleArtifacts().add(new RuleArtifact("create", "CREATE ENCRYPT RULE t"));
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowAlgorithmUtilsTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowAlgorithmUtilsTest.java
index 22f8df49410..94ab6e2fba9 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowAlgorithmUtilsTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowAlgorithmUtilsTest.java
@@ -62,11 +62,39 @@ class WorkflowAlgorithmUtilsTest {
         assertTrue(WorkflowAlgorithmUtils.containsAlgorithm(List.of(Map.of("name", " fixture ")), "fixture", "type", "name"));
     }
     
+    @Test
+    void assertCreatePropertiesTrimsValues() {
+        Properties actualProperties = WorkflowAlgorithmUtils.createProperties(Map.of("aes-key-value", " 123456 "));
+        assertThat(actualProperties.getProperty("aes-key-value"), is("123456"));
+    }
+    
+    @Test
+    void assertCreatePropertyMapReturnsEmptyForNull() {
+        assertThat(WorkflowAlgorithmUtils.createPropertyMap(null), is(Map.of()));
+    }
+    
+    @Test
+    void assertCreatePropertyMapHandlesProperties() {
+        Properties props = new Properties();
+        props.setProperty("aes-key-value", " 123456 ");
+        assertThat(WorkflowAlgorithmUtils.createPropertyMap(props), is(Map.of("aes-key-value", "123456")));
+    }
+    
+    @Test
+    void assertCreatePropertyMapHandlesMap() {
+        assertThat(WorkflowAlgorithmUtils.createPropertyMap(Map.of("aes-key-value", " 123456 ")), is(Map.of("aes-key-value", "123456")));
+    }
+    
+    @Test
+    void assertCreatePropertyMapHandlesString() {
+        assertThat(WorkflowAlgorithmUtils.createPropertyMap("{'aes-key-value':'123456','iv':'abc'}"), is(Map.of("aes-key-value", "123456", "iv", "abc")));
+    }
+    
     @Test
     void assertAlgorithmServiceAvailable() {
         try (MockedStatic<TypedSPILoader> ignored = mockStatic(TypedSPILoader.class)) {
             assertTrue(WorkflowAlgorithmUtils.isAlgorithmServiceAvailable(TypedSPIFixture.class, "FIXTURE", Map.of("foo", "bar")));
-            ignored.verify(() -> TypedSPILoader.checkService(TypedSPIFixture.class, "FIXTURE", WorkflowSQLUtils.createProperties(Map.of("foo", "bar"))));
+            ignored.verify(() -> TypedSPILoader.checkService(TypedSPIFixture.class, "FIXTURE", WorkflowAlgorithmUtils.createProperties(Map.of("foo", "bar"))));
         }
     }
     
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanPayloadBuilderTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanPayloadBuilderTest.java
index 685ed4392d8..6703f23c834 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanPayloadBuilderTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanPayloadBuilderTest.java
@@ -62,7 +62,8 @@ class WorkflowPlanPayloadBuilderTest {
         clarifiedIntent.getClarificationMessages().add("Do you need LIKE query?");
         snapshot.setClarifiedIntent(clarifiedIntent);
         snapshot.getIssues().add(new WorkflowIssue("code", "warning", "clarifying", "message", "action", true, Map.of("missing_properties", List.of("aes-key-value"))));
-        snapshot.getAlgorithmCandidates().add(new AlgorithmCandidate("primary", "AES", true, true, false, 95, "reason", ""));
+        snapshot.getAlgorithmCandidates().add(AlgorithmCandidate.builder().algorithmRole("primary").algorithmType("AES")
+                .supportsDecrypt(true).supportsEquivalentFilter(true).supportsLike(false).recommendationScore(95).recommendationReason("reason").riskNotes("").build());
         snapshot.getPropertyRequirements().add(new AlgorithmPropertyRequirement("primary", "aes-key-value", true, true, "desc", ""));
         InteractionPlan interactionPlan = new InteractionPlan();
         interactionPlan.setCurrentStep("review");
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningArgumentsTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningArgumentsTest.java
index 06df869c60e..2d9ae0a3b91 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningArgumentsTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningArgumentsTest.java
@@ -17,11 +17,13 @@
 
 package org.apache.shardingsphere.mcp.support.workflow.service;
 
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -35,6 +37,20 @@ class WorkflowPlanningArgumentsTest {
         assertThat(new WorkflowPlanningArguments(rawArguments).getStringArgument("name"), is(expectedValue));
     }
     
+    @Test
+    void assertApplyStringArgument() {
+        AtomicReference<String> actual = new AtomicReference<>("unchanged");
+        new WorkflowPlanningArguments(Map.of("name", " foo_name ")).applyStringArgument("name", actual::set);
+        assertThat(actual.get(), is("foo_name"));
+    }
+    
+    @Test
+    void assertApplyStringArgumentWithMissingArgument() {
+        AtomicReference<String> actual = new AtomicReference<>("unchanged");
+        new WorkflowPlanningArguments(Map.of()).applyStringArgument("name", actual::set);
+        assertThat(actual.get(), is("unchanged"));
+    }
+    
     @ParameterizedTest(name = "{0}")
     @MethodSource("getMapArgumentCases")
     void assertGetMapArgument(final String name, final Map<String, Object> rawArguments, final Map<String, String> expectedValue) {
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidatorTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidatorTest.java
index c8388c9de89..df4bae6ca9a 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidatorTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidatorTest.java
@@ -17,6 +17,8 @@
 
 package org.apache.shardingsphere.mcp.support.workflow.service;
 
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
+
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicyFactory;
 import org.apache.shardingsphere.database.connector.core.metadata.database.enums.TableType;
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierScope;
@@ -187,7 +189,7 @@ class WorkflowPlanningContextValidatorTest {
     }
     
     private RuntimeDatabaseProfile createDatabaseMetadata() {
-        return new RuntimeDatabaseProfile("logic_db", "Fixture", "1.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet());
+        return new RuntimeDatabaseProfile("logic_db", "Fixture", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet());
     }
     
     private ShardingSphereSchema createSchemaMetadata() {
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupportTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupportTest.java
index 2cd816f21a4..d83d3b915bc 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupportTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupportTest.java
@@ -17,6 +17,8 @@
 
 package org.apache.shardingsphere.mcp.support.workflow.service;
 
+import org.apache.shardingsphere.mcp.support.database.metadata.TransactionCapability;
+
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicyFactory;
 import org.apache.shardingsphere.database.connector.core.metadata.database.enums.TableType;
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierScope;
@@ -355,7 +357,7 @@ class WorkflowPlanningSupportTest {
     }
     
     private RuntimeDatabaseProfile createDatabaseMetadata() {
-        return new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", true, true, IdentifierCasePolicyFactory.newInsensitivePolicySet());
+        return new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL_WITH_SAVEPOINT, IdentifierCasePolicyFactory.newInsensitivePolicySet());
     }
     
     private ShardingSphereSchema createSchemaMetadata(final String schemaName, final String tableName, final String columnName) {
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowRequestBinderTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowRequestBinderTest.java
index 45e4d90401d..af3b3e5bfb2 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowRequestBinderTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowRequestBinderTest.java
@@ -88,4 +88,18 @@ class WorkflowRequestBinderTest {
                 }));
         assertThat(actual.getMessage(), is("structured_intent_evidence must be an object."));
     }
+    
+    @Test
+    void assertApplyStringField() {
+        AtomicReference<String> actual = new AtomicReference<>();
+        WorkflowRequestBinder.applyStringField(Map.of("field", 42), "field", actual::set);
+        assertThat(actual.get(), is("42"));
+    }
+    
+    @Test
+    void assertApplyStringFieldIgnoresMissingField() {
+        AtomicReference<String> actual = new AtomicReference<>("unchanged");
+        WorkflowRequestBinder.applyStringField(Map.of(), "field", actual::set);
+        assertThat(actual.get(), is("unchanged"));
+    }
 }
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowSQLUtilsTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowSQLUtilsTest.java
index ca51f92fad4..7fb796f51f5 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowSQLUtilsTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowSQLUtilsTest.java
@@ -33,7 +33,6 @@ import org.mockito.MockedStatic;
 
 import java.util.Map;
 import java.util.Optional;
-import java.util.Properties;
 import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -244,12 +243,6 @@ class WorkflowSQLUtilsTest {
         assertThat(actualValue, is("O''Brien"));
     }
     
-    @Test
-    void assertCreatePropertiesTrimsValues() {
-        Properties actualProperties = WorkflowSQLUtils.createProperties(Map.of("aes-key-value", " 123456 "));
-        assertThat(actualProperties.getProperty("aes-key-value"), is("123456"));
-    }
-    
     @Test
     void assertCreateAlgorithmFragmentFormatsProperties() {
         String actualFragment = WorkflowSQLUtils.createAlgorithmFragment(" AES ", Map.of("aes-key-value", " 123456 "));
@@ -268,32 +261,6 @@ class WorkflowSQLUtilsTest {
         assertThat(actualFragment, is(""));
     }
     
-    @Test
-    void assertCreatePropertyMapReturnsEmptyForNull() {
-        Map<String, String> actualEntries = WorkflowSQLUtils.createPropertyMap(null);
-        assertThat(actualEntries, is(Map.of()));
-    }
-    
-    @Test
-    void assertCreatePropertyMapHandlesProperties() {
-        Properties props = new Properties();
-        props.setProperty("aes-key-value", " 123456 ");
-        Map<String, String> actualEntries = WorkflowSQLUtils.createPropertyMap(props);
-        assertThat(actualEntries, is(Map.of("aes-key-value", "123456")));
-    }
-    
-    @Test
-    void assertCreatePropertyMapHandlesMap() {
-        Map<String, String> actualEntries = WorkflowSQLUtils.createPropertyMap(Map.of("aes-key-value", " 123456 "));
-        assertThat(actualEntries, is(Map.of("aes-key-value", "123456")));
-    }
-    
-    @Test
-    void assertCreatePropertyMapHandlesString() {
-        Map<String, String> actualEntries = WorkflowSQLUtils.createPropertyMap("{'aes-key-value':'123456','iv':'abc'}");
-        assertThat(actualEntries, is(Map.of("aes-key-value", "123456", "iv", "abc")));
-    }
-    
     private static Stream<Arguments> getDistSQLKeywordCases() {
         return Stream.of(
                 Arguments.of("quote if keyword", "if", "`if`"),
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/config/LLME2EConfiguration.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/config/LLME2EConfiguration.java
index 074eb36216d..b717ea1b707 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/config/LLME2EConfiguration.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/config/LLME2EConfiguration.java
@@ -19,6 +19,9 @@ package org.apache.shardingsphere.test.e2e.mcp.llm.config;
 
 import org.apache.shardingsphere.test.e2e.env.runtime.EnvironmentPropertiesLoader;
 
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
@@ -36,7 +39,8 @@ import java.util.UUID;
 /**
  * LLM E2E configuration.
  */
-@RequiredArgsConstructor
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Builder(toBuilder = true)
 @Getter
 public final class LLME2EConfiguration {
     
@@ -87,21 +91,22 @@ public final class LLME2EConfiguration {
         Properties props = EnvironmentPropertiesLoader.loadProperties();
         RuntimeMode runtimeMode = RuntimeMode.from(readString(props, "mcp.llm.runtime-mode", RuntimeMode.DOCKER.getValue()));
         ModelMetadata modelMetadata = readModelMetadata(props);
-        return new LLME2EConfiguration(
-                normalizeBaseUrl(readString(props, "mcp.llm.base-url", DEFAULT_BASE_URL)),
-                readString(props, "mcp.llm.provider", "openai-compatible"),
-                readString(props, "mcp.llm.model", DEFAULT_MODEL_NAME),
-                readString(props, "mcp.llm.api-key", DEFAULT_API_KEY),
-                readInteger(props, "mcp.llm.ready-timeout-seconds", 600),
-                readInteger(props, "mcp.llm.request-timeout-seconds", 240),
-                readInteger(props, "mcp.llm.max-turns", 10),
-                Paths.get(readString(props, "mcp.llm.artifact-root", "target/llm-e2e")),
-                readString(props, "mcp.llm.run-id", createDefaultRunId()),
-                runtimeMode,
-                readString(props, "mcp.llm.server-image", DEFAULT_SERVER_IMAGE),
-                readString(props, "mcp.llm.base-server-image", ""),
-                readString(props, "mcp.llm.base-server-image-digest", ""),
-                modelMetadata);
+        return LLME2EConfiguration.builder()
+                .baseUrl(normalizeBaseUrl(readString(props, "mcp.llm.base-url", DEFAULT_BASE_URL)))
+                .modelProvider(readString(props, "mcp.llm.provider", "openai-compatible"))
+                .modelName(readString(props, "mcp.llm.model", DEFAULT_MODEL_NAME))
+                .apiKey(readString(props, "mcp.llm.api-key", DEFAULT_API_KEY))
+                .readyTimeoutSeconds(readInteger(props, "mcp.llm.ready-timeout-seconds", 600))
+                .requestTimeoutSeconds(readInteger(props, "mcp.llm.request-timeout-seconds", 240))
+                .maxTurns(readInteger(props, "mcp.llm.max-turns", 10))
+                .artifactRoot(Paths.get(readString(props, "mcp.llm.artifact-root", "target/llm-e2e")))
+                .runId(readString(props, "mcp.llm.run-id", createDefaultRunId()))
+                .runtimeMode(runtimeMode)
+                .serverImage(readString(props, "mcp.llm.server-image", DEFAULT_SERVER_IMAGE))
+                .baseServerImage(readString(props, "mcp.llm.base-server-image", ""))
+                .baseServerImageDigest(readString(props, "mcp.llm.base-server-image-digest", ""))
+                .modelMetadata(modelMetadata)
+                .build();
     }
     
     /**
@@ -125,8 +130,7 @@ public final class LLME2EConfiguration {
      * @return copied configuration
      */
     public LLME2EConfiguration withModelEndpoint(final String baseUrl, final String apiKey) {
-        return new LLME2EConfiguration(normalizeBaseUrl(baseUrl), modelProvider, modelName, apiKey, readyTimeoutSeconds, requestTimeoutSeconds, maxTurns, artifactRoot, runId,
-                runtimeMode, serverImage, baseServerImage, baseServerImageDigest, modelMetadata);
+        return toBuilder().baseUrl(normalizeBaseUrl(baseUrl)).apiKey(apiKey).build();
     }
     
     /**
@@ -137,8 +141,7 @@ public final class LLME2EConfiguration {
      * @return copied configuration
      */
     public LLME2EConfiguration withReadinessTimeouts(final int readyTimeoutSeconds, final int requestTimeoutSeconds) {
-        return new LLME2EConfiguration(baseUrl, modelProvider, modelName, apiKey, readyTimeoutSeconds, requestTimeoutSeconds, maxTurns, artifactRoot, runId, runtimeMode,
-                serverImage, baseServerImage, baseServerImageDigest, modelMetadata);
+        return toBuilder().readyTimeoutSeconds(readyTimeoutSeconds).requestTimeoutSeconds(requestTimeoutSeconds).build();
     }
     
     /**
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/config/LLME2EConfigurationTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/config/LLME2EConfigurationTest.java
index eb7eb42f453..5fe3ed756a5 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/config/LLME2EConfigurationTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/config/LLME2EConfigurationTest.java
@@ -224,10 +224,23 @@ class LLME2EConfigurationTest {
     }
     
     private LLME2EConfiguration createConfiguration(final RuntimeMode runtimeMode, final Path artifactRoot) {
-        return new LLME2EConfiguration("http://127.0.0.1:8080/v1", "openai-compatible", "ggml-org/Qwen3-1.7B-GGUF:Q4_K_M", "mcp-llm-score", 600, 240, 10,
-                artifactRoot, "run-id", runtimeMode, "apache/shardingsphere-mcp-llm-runtime:local", "ghcr.io/ggml-org/llama.cpp:server-b9191", "",
-                new LLME2EConfiguration.ModelMetadata("ggml-org/Qwen3-1.7B-GGUF", "Qwen3-1.7B-Q4_K_M.gguf", "Q4_K_M", "daeb8e2d528a760970442092f6bf1e55c3b659eb",
-                        "configured-model-sha256"));
+        return LLME2EConfiguration.builder()
+                .baseUrl("http://127.0.0.1:8080/v1")
+                .modelProvider("openai-compatible")
+                .modelName("ggml-org/Qwen3-1.7B-GGUF:Q4_K_M")
+                .apiKey("mcp-llm-score")
+                .readyTimeoutSeconds(600)
+                .requestTimeoutSeconds(240)
+                .maxTurns(10)
+                .artifactRoot(artifactRoot)
+                .runId("run-id")
+                .runtimeMode(runtimeMode)
+                .serverImage("apache/shardingsphere-mcp-llm-runtime:local")
+                .baseServerImage("ghcr.io/ggml-org/llama.cpp:server-b9191")
+                .baseServerImageDigest("")
+                .modelMetadata(new LLME2EConfiguration.ModelMetadata("ggml-org/Qwen3-1.7B-GGUF", "Qwen3-1.7B-Q4_K_M.gguf", "Q4_K_M",
+                        "daeb8e2d528a760970442092f6bf1e55c3b659eb", "configured-model-sha256"))
+                .build();
     }
     
     private void restoreProperty(final String name, final String value) {
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationArtifacts.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationArtifacts.java
index 1f05abfddc2..f5938ab5981 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationArtifacts.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationArtifacts.java
@@ -64,7 +64,17 @@ final class LLMMCPConversationArtifacts {
     }
     
     LLME2EArtifactBundle createArtifactBundle(final LLME2EScenario scenario, final LLME2EAssertionReport assertionReport) {
-        return new LLME2EArtifactBundle(scenario.getScenarioId(), scenario.getSystemPrompt(), scenario.getUserPrompt(), modelProvider, modelName,
-                finalAnswerJson, rawModelOutputs, interactionTrace, mcpRuntimeLogLines, assertionReport);
+        return LLME2EArtifactBundle.builder()
+                .scenarioId(scenario.getScenarioId())
+                .systemPrompt(scenario.getSystemPrompt())
+                .userPrompt(scenario.getUserPrompt())
+                .modelProvider(modelProvider)
+                .modelName(modelName)
+                .finalAnswerJson(finalAnswerJson)
+                .rawModelOutputs(rawModelOutputs)
+                .interactionTrace(interactionTrace)
+                .mcpRuntimeLogLines(mcpRuntimeLogLines)
+                .assertionReport(assertionReport)
+                .build();
     }
 }
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationArtifactsTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationArtifactsTest.java
index bd89b769cdd..8be23ce4d9e 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationArtifactsTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationArtifactsTest.java
@@ -36,7 +36,7 @@ class LLMMCPConversationArtifactsTest {
     void assertAddRawModelOutput() {
         LLMMCPConversationArtifacts artifacts = new LLMMCPConversationArtifacts("provider", "model");
         artifacts.addRawModelOutput("raw-output");
-        assertThat(artifacts.createArtifactBundle(createScenario(), LLME2EAssertionReport.isSuccess("ok")).getRawModelOutputs(), is(List.of("raw-output")));
+        assertThat(artifacts.createArtifactBundle(createScenario(), LLME2EAssertionReport.success("ok")).getRawModelOutputs(), is(List.of("raw-output")));
     }
     
     @Test
@@ -51,7 +51,7 @@ class LLMMCPConversationArtifactsTest {
     void assertAddRuntimeLogLine() {
         LLMMCPConversationArtifacts artifacts = new LLMMCPConversationArtifacts("provider", "model");
         artifacts.addRuntimeLogLine("runtime-log");
-        assertThat(artifacts.createArtifactBundle(createScenario(), LLME2EAssertionReport.isSuccess("ok")).getMcpRuntimeLogLines(), is(List.of("runtime-log")));
+        assertThat(artifacts.createArtifactBundle(createScenario(), LLME2EAssertionReport.success("ok")).getMcpRuntimeLogLines(), is(List.of("runtime-log")));
     }
     
     @Test
@@ -81,7 +81,7 @@ class LLMMCPConversationArtifactsTest {
         MCPInteractionTraceRecord traceRecord = createTrace();
         artifacts.addInteractionTrace(traceRecord);
         artifacts.addRuntimeLogLine("runtime-log");
-        LLME2EAssertionReport assertionReport = LLME2EAssertionReport.isSuccess("ok");
+        LLME2EAssertionReport assertionReport = LLME2EAssertionReport.success("ok");
         LLME2EArtifactBundle actual = artifacts.createArtifactBundle(createScenario(), assertionReport);
         assertThat(actual.getScenarioId(), is("scenario"));
         assertThat(actual.getSystemPrompt(), is("system"));
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationInstructionFactory.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationInstructionFactory.java
index 96015410280..e7f2b36ccf4 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationInstructionFactory.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationInstructionFactory.java
@@ -52,25 +52,7 @@ final class LLMMCPConversationInstructionFactory {
         if (!immediateNextActionInstruction.isEmpty()) {
             return immediateNextActionInstruction;
         }
-        return hasSideEffectExecutionNextAction(interactionTrace) ? createSideEffectExecutionNextActionInstruction(scenario.getExpectedAnswer()) : "";
-    }
-    
-    boolean hasPendingImmediateNextAction(final List<MCPInteractionTraceRecord> interactionTrace) {
-        return !interactionTrace.isEmpty() && !findImmediateNextActionName(interactionTrace.getLast()).isEmpty();
-    }
-    
-    String findImmediateNextActionName(final MCPInteractionTraceRecord traceRecord) {
-        for (Map<?, ?> each : LLMMCPNextActions.getNextActions(traceRecord.getStructuredContent())) {
-            String result = findMachineNextActionName(each);
-            if (!result.isEmpty()) {
-                return result;
-            }
-        }
-        return "";
-    }
-    
-    boolean hasSideEffectExecutionNextAction(final List<MCPInteractionTraceRecord> interactionTrace) {
-        return !interactionTrace.isEmpty() && LLMMCPNextActions.getNextActions(interactionTrace.getLast().getStructuredContent()).stream().anyMatch(LLMMCPSideEffectNextAction::isExecutionAction);
+        return LLMMCPNextActions.hasSideEffectExecutionNextAction(interactionTrace) ? createSideEffectExecutionNextActionInstruction(scenario.getExpectedAnswer()) : "";
     }
     
     List<LLMChatMessage> createFinalAnswerMessages(final LLME2EScenario scenario, final List<MCPInteractionTraceRecord> interactionTrace) {
@@ -127,20 +109,6 @@ final class LLMMCPConversationInstructionFactory {
         return "";
     }
     
-    private String findMachineNextActionName(final Map<?, ?> action) {
-        if (LLMMCPSideEffectNextAction.isExecutionAction(action)) {
-            return "";
-        }
-        String actionType = Objects.toString(action.get("type"), "").trim();
-        if ("resource_read".equals(actionType) && !Objects.toString(action.get("resource_uri"), "").trim().isEmpty()) {
-            return MCPInteractionActionNames.READ_RESOURCE;
-        }
-        if ("tool_call".equals(actionType)) {
-            return Objects.toString(action.get("tool_name"), "").trim();
-        }
-        return "completion".equals(actionType) ? MCPInteractionActionNames.COMPLETE : "";
-    }
-    
     private String createMachineNextActionInstruction(final Map<?, ?> action) {
         if (LLMMCPSideEffectNextAction.isExecutionAction(action)) {
             return "";
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationRunner.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationRunner.java
index 89ba943fce1..f3ce17eafcd 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationRunner.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationRunner.java
@@ -89,7 +89,7 @@ public final class LLMMCPConversationRunner {
         try {
             openInteractionClient();
             LLMMCPConversationInstructionFactory instructionFactory = new LLMMCPConversationInstructionFactory();
-            LLMMCPConversationTurnPlanner turnPlanner = new LLMMCPConversationTurnPlanner(instructionFactory);
+            LLMMCPConversationTurnPlanner turnPlanner = new LLMMCPConversationTurnPlanner();
             boolean finalAnswerRequested = false;
             for (int turnIndex = 0; turnIndex < maxTurns; turnIndex++) {
                 finalAnswerRequested = requestFinalAnswerIfReady(scenario, messages, artifacts, finalAnswerRequested, instructionFactory);
@@ -139,7 +139,7 @@ public final class LLMMCPConversationRunner {
         if (finalAnswerRequested || !LLMMCPInteractionCoverage.hasRequiredInteractionCoverage(scenario.getRequiredToolNames(), artifacts.getInteractionTrace())) {
             return finalAnswerRequested;
         }
-        if (instructionFactory.hasPendingImmediateNextAction(artifacts.getInteractionTrace())) {
+        if (LLMMCPNextActions.hasPendingImmediateNextAction(artifacts.getInteractionTrace())) {
             return false;
         }
         if (scenario.getRequiredToolNames().contains("database_gateway_execute_query") && !hasExpectedExecuteQuery(scenario.getExpectedAnswer(), artifacts.getInteractionTrace())) {
@@ -190,7 +190,7 @@ public final class LLMMCPConversationRunner {
         }
         messages.add(LLMChatMessage.assistant(completion.getContent(), completion.getToolCalls()));
         for (LLMToolCall each : completion.getToolCalls()) {
-            List<String> availableToolNames = resolveAvailableToolNames(scenario, artifacts, instructionFactory, turnPlanner);
+            List<String> availableToolNames = resolveAvailableToolNames(scenario, artifacts, turnPlanner);
             if (!availableToolNames.isEmpty() && !availableToolNames.contains(each.getName())) {
                 addUnavailableToolCorrection(scenario, each, messages, availableToolNames);
                 return Optional.empty();
@@ -208,9 +208,9 @@ public final class LLMMCPConversationRunner {
     }
     
     private List<String> resolveAvailableToolNames(final LLME2EScenario scenario, final LLMMCPConversationArtifacts artifacts,
-                                                   final LLMMCPConversationInstructionFactory instructionFactory, final LLMMCPConversationTurnPlanner turnPlanner) {
+                                                   final LLMMCPConversationTurnPlanner turnPlanner) {
         List<String> result = turnPlanner.createImmediateNextActionToolNames(artifacts.getInteractionTrace());
-        return result.isEmpty() && instructionFactory.hasSideEffectExecutionNextAction(artifacts.getInteractionTrace())
+        return result.isEmpty() && LLMMCPNextActions.hasSideEffectExecutionNextAction(artifacts.getInteractionTrace())
                 ? turnPlanner.createTurnToolNames(scenario, artifacts.getInteractionTrace())
                 : result;
     }
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationRunnerFailureTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationRunnerFailureTest.java
index c153b19423f..89d13fab5af 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationRunnerFailureTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationRunnerFailureTest.java
@@ -98,6 +98,7 @@ class LLMMCPConversationRunnerFailureTest extends AbstractLLMMCPConversationRunn
         LLME2EArtifactBundle actual = actualRunner.run(actualScenario);
         
         assertThat(actual.getAssertionReport().getFailureType(), is("mcp_runtime_unavailable"));
+        assertThat(actual.getAssertionReport().getMessage(), is("MCP action `database_gateway_execute_query` failed: boom"));
     }
     
     @Test
@@ -109,10 +110,40 @@ class LLMMCPConversationRunnerFailureTest extends AbstractLLMMCPConversationRunn
         LLME2EArtifactBundle actual = actualRunner.run(actualScenario);
         
         assertThat(actual.getAssertionReport().getFailureType(), is("mcp_runtime_unavailable"));
+        assertThat(actual.getAssertionReport().getMessage(), is("MCP runtime failed to initialize."));
         verify(getMCPInteractionClient()).open();
         verify(getMCPInteractionClient()).close();
     }
     
+    @Test
+    void assertRunWithInterruptedMcpOpen() throws IOException, InterruptedException {
+        doThrow(new InterruptedException("boom")).when(getMCPInteractionClient()).open();
+        try {
+            LLME2EArtifactBundle actual = createRunner(1).run(createScenario(List.of("database_gateway_execute_query")));
+            
+            assertThat(actual.getAssertionReport().getFailureType(), is("model_service_unavailable"));
+            assertTrue(Thread.currentThread().isInterrupted());
+        } finally {
+            Thread.interrupted();
+        }
+    }
+    
+    @Test
+    void assertRunWithInterruptedMcpAction() throws IOException, InterruptedException {
+        Map<String, Object> executeQueryArguments = createExecuteQueryArguments(QUERY);
+        when(getLLMChatClient().complete(anyList(), anyList(), eq("required"), eq(false))).thenReturn(
+                createToolCallCompletion("tool-1", "database_gateway_execute_query", executeQueryArguments, "tool-call-response"));
+        when(getMCPInteractionClient().call("database_gateway_execute_query", executeQueryArguments)).thenThrow(new InterruptedException("boom"));
+        try {
+            LLME2EArtifactBundle actual = createRunner(1).run(createScenario(List.of("database_gateway_execute_query")));
+            
+            assertThat(actual.getAssertionReport().getFailureType(), is("model_service_unavailable"));
+            assertTrue(Thread.currentThread().isInterrupted());
+        } finally {
+            Thread.interrupted();
+        }
+    }
+    
     @Test
     void assertRunWithModelRequestIOException() throws IOException, InterruptedException {
         LLME2EScenario actualScenario = createScenario(List.of("database_gateway_execute_query"));
@@ -122,6 +153,7 @@ class LLMMCPConversationRunnerFailureTest extends AbstractLLMMCPConversationRunn
         LLME2EArtifactBundle actual = actualRunner.run(actualScenario);
         
         assertThat(actual.getAssertionReport().getFailureType(), is("model_service_unavailable"));
+        assertThat(actual.getAssertionReport().getMessage(), is("Model service request failed: http 500"));
         verify(getMCPInteractionClient()).open();
         verify(getMCPInteractionClient()).close();
     }
@@ -155,6 +187,13 @@ class LLMMCPConversationRunnerFailureTest extends AbstractLLMMCPConversationRunn
         verify(getMCPInteractionClient()).close();
     }
     
+    @Test
+    void assertRunClassifiesModelFailureByMessage() throws IOException, InterruptedException {
+        when(getLLMChatClient().complete(anyList(), anyList(), eq("required"), eq(false))).thenThrow(new IllegalStateException("MCP-shaped model failure."));
+        LLME2EArtifactBundle actual = createRunner(1).run(createScenario(List.of("database_gateway_execute_query")));
+        assertThat(actual.getAssertionReport().getFailureType(), is("mcp_runtime_unavailable"));
+    }
+    
     @Test
     void assertRunIgnoresCloseIOException() throws IOException, InterruptedException {
         LLME2EScenario actualScenario = createScenario(List.of("database_gateway_execute_query"));
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationTurnPlanner.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationTurnPlanner.java
index 8bc9a077981..75a471643ef 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationTurnPlanner.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationTurnPlanner.java
@@ -34,12 +34,10 @@ import java.util.stream.Collectors;
 @AllArgsConstructor(access = AccessLevel.PACKAGE)
 final class LLMMCPConversationTurnPlanner {
     
-    private final LLMMCPConversationInstructionFactory instructionFactory;
-    
     private final Set<String> readOnlyToolNames;
     
-    LLMMCPConversationTurnPlanner(final LLMMCPConversationInstructionFactory instructionFactory) {
-        this(instructionFactory, createReadOnlyToolNames());
+    LLMMCPConversationTurnPlanner() {
+        this(createReadOnlyToolNames());
     }
     
     private static Set<String> createReadOnlyToolNames() {
@@ -64,7 +62,7 @@ final class LLMMCPConversationTurnPlanner {
         if (!immediateActionToolNames.isEmpty()) {
             return immediateActionToolNames;
         }
-        if (instructionFactory.hasSideEffectExecutionNextAction(interactionTrace)) {
+        if (LLMMCPNextActions.hasSideEffectExecutionNextAction(interactionTrace)) {
             List<String> missingReadOnlyToolNames = findMissingReadOnlyToolNames(scenario, interactionTrace);
             if (!missingReadOnlyToolNames.isEmpty()) {
                 return List.of(missingReadOnlyToolNames.getFirst());
@@ -78,7 +76,7 @@ final class LLMMCPConversationTurnPlanner {
         if (interactionTrace.isEmpty()) {
             return List.of();
         }
-        String immediateActionName = instructionFactory.findImmediateNextActionName(interactionTrace.getLast());
+        String immediateActionName = LLMMCPNextActions.findImmediateNextActionName(interactionTrace.getLast());
         return immediateActionName.isEmpty() ? List.of() : List.of(immediateActionName);
     }
     
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationTurnPlannerTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationTurnPlannerTest.java
index ab62d92a90c..d450104b6b2 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationTurnPlannerTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationTurnPlannerTest.java
@@ -34,8 +34,7 @@ class LLMMCPConversationTurnPlannerTest {
     
     @Test
     void assertCreateTurnToolNamesWithImmediateResourceAction() {
-        LLMMCPConversationInstructionFactory instructionFactory = new LLMMCPConversationInstructionFactory();
-        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner(instructionFactory, Set.of());
+        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner(Set.of());
         List<String> actual = planner.createTurnToolNames(createScenario(List.of(MCPInteractionActionNames.READ_RESOURCE, "database_gateway_execute_query"),
                 List.of(MCPInteractionActionNames.READ_RESOURCE, "database_gateway_execute_query")),
                 List.of(createTraceRecord("database_gateway_plan_mask_rule",
@@ -45,8 +44,7 @@ class LLMMCPConversationTurnPlannerTest {
     
     @Test
     void assertCreateTurnToolNamesWithImmediateCompletionAction() {
-        LLMMCPConversationInstructionFactory instructionFactory = new LLMMCPConversationInstructionFactory();
-        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner(instructionFactory, Set.of());
+        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner(Set.of());
         List<String> actual = planner.createTurnToolNames(createScenario(List.of("database_gateway_plan_mask_rule", "database_gateway_execute_query"),
                 List.of("database_gateway_plan_mask_rule", "database_gateway_execute_query")),
                 List.of(createTraceRecord("database_gateway_plan_mask_rule",
@@ -56,8 +54,7 @@ class LLMMCPConversationTurnPlannerTest {
     
     @Test
     void assertCreateTurnToolNamesPrefersReadOnlyAfterSideEffectNextAction() {
-        LLMMCPConversationInstructionFactory instructionFactory = new LLMMCPConversationInstructionFactory();
-        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner(instructionFactory, Set.of("database_gateway_execute_query"));
+        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner(Set.of("database_gateway_execute_query"));
         Map<String, Object> nextAction = Map.of("type", "tool_call", "tool_name", "database_gateway_execute_update", "arguments", Map.of("execution_mode", "execute"));
         List<String> actual = planner.createTurnToolNames(createScenario(List.of("database_gateway_execute_update", "database_gateway_execute_query"),
                 List.of("database_gateway_execute_update", "database_gateway_execute_query")),
@@ -67,8 +64,7 @@ class LLMMCPConversationTurnPlannerTest {
     
     @Test
     void assertCreateTurnToolNamesPrefersDescriptorReadOnlyToolAfterSideEffectNextAction() {
-        LLMMCPConversationInstructionFactory instructionFactory = new LLMMCPConversationInstructionFactory();
-        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner(instructionFactory);
+        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner();
         Map<String, Object> nextAction = Map.of("type", "tool_call", "tool_name", "database_gateway_execute_update", "arguments", Map.of("execution_mode", "execute"));
         List<String> actual = planner.createTurnToolNames(createScenario(List.of("database_gateway_execute_update", "database_gateway_validate_runtime_database"),
                 List.of("database_gateway_execute_update", "database_gateway_validate_runtime_database")),
@@ -78,8 +74,7 @@ class LLMMCPConversationTurnPlannerTest {
     
     @Test
     void assertCreateTurnToolNamesPrefersDiscoveryBridgeAfterSideEffectNextAction() {
-        LLMMCPConversationInstructionFactory instructionFactory = new LLMMCPConversationInstructionFactory();
-        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner(instructionFactory);
+        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner();
         Map<String, Object> nextAction = Map.of("type", "tool_call", "tool_name", "database_gateway_execute_update", "arguments", Map.of("execution_mode", "execute"));
         List<String> actual = planner.createTurnToolNames(createScenario(List.of("database_gateway_execute_update", MCPInteractionActionNames.LIST_TOOLS),
                 List.of("database_gateway_execute_update", MCPInteractionActionNames.LIST_TOOLS)),
@@ -89,23 +84,20 @@ class LLMMCPConversationTurnPlannerTest {
     
     @Test
     void assertCreateToolChoiceWithMissingCoverage() {
-        LLMMCPConversationInstructionFactory instructionFactory = new LLMMCPConversationInstructionFactory();
-        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner(instructionFactory, Set.of());
+        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner(Set.of());
         assertThat(planner.createToolChoice(createScenario(List.of("database_gateway_execute_query"), List.of("database_gateway_execute_query")), List.of(), false), is("required"));
     }
     
     @Test
     void assertCreateToolChoiceWithCoveredRequiredTools() {
-        LLMMCPConversationInstructionFactory instructionFactory = new LLMMCPConversationInstructionFactory();
-        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner(instructionFactory, Set.of());
+        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner(Set.of());
         assertThat(planner.createToolChoice(createScenario(List.of("database_gateway_execute_query"), List.of("database_gateway_execute_query")),
                 List.of(createTraceRecord("database_gateway_execute_query", Map.of("row_objects", List.of()))), false), is("auto"));
     }
     
     @Test
     void assertCreateToolChoiceWithFinalAnswer() {
-        LLMMCPConversationInstructionFactory instructionFactory = new LLMMCPConversationInstructionFactory();
-        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner(instructionFactory, Set.of());
+        LLMMCPConversationTurnPlanner planner = new LLMMCPConversationTurnPlanner(Set.of());
         assertThat(planner.createToolChoice(createScenario(List.of("database_gateway_execute_query"), List.of("database_gateway_execute_query")), List.of(), true), is("none"));
     }
     
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPFinalAnswerValidator.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPFinalAnswerValidator.java
index 885db0f2086..27e2980e0c5 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPFinalAnswerValidator.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPFinalAnswerValidator.java
@@ -61,7 +61,7 @@ final class LLMMCPFinalAnswerValidator {
         if (!createComparableInteractionSequence(interactionTrace).equals(collapseConsecutiveActionNames(actualAnswer.getInteractionSequence()))) {
             return LLME2EAssertionReport.failure("unexpected_query_result", "Final answer interactionSequence does not match the observed interaction trace.");
         }
-        return LLME2EAssertionReport.isSuccess("LLM MCP interaction passed.");
+        return LLME2EAssertionReport.success("LLM MCP interaction passed.");
     }
     
     private List<String> createComparableInteractionSequence(final List<MCPInteractionTraceRecord> interactionTrace) {
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPNextActions.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPNextActions.java
index 22016d78c4f..e38635d9bb4 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPNextActions.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPNextActions.java
@@ -19,10 +19,13 @@ package org.apache.shardingsphere.test.e2e.mcp.llm.conversation;
 
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
+import org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPInteractionActionNames;
+import org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPInteractionTraceRecord;
 
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Model-facing MCP next action extractor.
@@ -46,6 +49,42 @@ public final class LLMMCPNextActions {
         return result;
     }
     
+    /**
+     * Judge whether the latest trace record has an immediate machine action.
+     *
+     * @param interactionTrace interaction trace
+     * @return whether an immediate machine action is pending
+     */
+    static boolean hasPendingImmediateNextAction(final List<MCPInteractionTraceRecord> interactionTrace) {
+        return !interactionTrace.isEmpty() && !findImmediateNextActionName(interactionTrace.getLast()).isEmpty();
+    }
+    
+    /**
+     * Find the immediate machine action name.
+     *
+     * @param traceRecord interaction trace record
+     * @return immediate machine action name
+     */
+    static String findImmediateNextActionName(final MCPInteractionTraceRecord traceRecord) {
+        for (Map<?, ?> each : getNextActions(traceRecord.getStructuredContent())) {
+            String result = findMachineNextActionName(each);
+            if (!result.isEmpty()) {
+                return result;
+            }
+        }
+        return "";
+    }
+    
+    /**
+     * Judge whether the latest trace record recommends side-effect execution.
+     *
+     * @param interactionTrace interaction trace
+     * @return whether side-effect execution is recommended
+     */
+    static boolean hasSideEffectExecutionNextAction(final List<MCPInteractionTraceRecord> interactionTrace) {
+        return !interactionTrace.isEmpty() && getNextActions(interactionTrace.getLast().getStructuredContent()).stream().anyMatch(LLMMCPSideEffectNextAction::isExecutionAction);
+    }
+    
     private static void appendActions(final List<Map<?, ?>> actions, final Object value) {
         if (!(value instanceof List)) {
             return;
@@ -56,4 +95,18 @@ public final class LLMMCPNextActions {
             }
         }
     }
+    
+    private static String findMachineNextActionName(final Map<?, ?> action) {
+        if (LLMMCPSideEffectNextAction.isExecutionAction(action)) {
+            return "";
+        }
+        String actionType = Objects.toString(action.get("type"), "").trim();
+        if ("resource_read".equals(actionType) && !Objects.toString(action.get("resource_uri"), "").trim().isEmpty()) {
+            return MCPInteractionActionNames.READ_RESOURCE;
+        }
+        if ("tool_call".equals(actionType)) {
+            return Objects.toString(action.get("tool_name"), "").trim();
+        }
+        return "completion".equals(actionType) ? MCPInteractionActionNames.COMPLETE : "";
+    }
 }
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPNextActionsTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPNextActionsTest.java
index 98080c412e8..9a830594515 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPNextActionsTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPNextActionsTest.java
@@ -40,4 +40,5 @@ class LLMMCPNextActionsTest {
     void assertGetNextActionsWithNoActions() {
         assertThat(LLMMCPNextActions.getNextActions(Map.of("next_actions", "ignored", "recovery", Map.of("next_actions", "ignored"))), is(List.of()));
     }
+    
 }
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EArtifactBundle.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EArtifactBundle.java
index 8e9e53ba309..ba996af0c7b 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EArtifactBundle.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EArtifactBundle.java
@@ -17,13 +17,16 @@
 
 package org.apache.shardingsphere.test.e2e.mcp.llm.conversation.artifact;
 
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
 import org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPInteractionTraceRecord;
 
 import java.util.List;
 
-@RequiredArgsConstructor
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Builder
 @Getter
 public final class LLME2EArtifactBundle {
     
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EArtifactWriterTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EArtifactWriterTest.java
index 77ecd1b2b08..feb4eeb5bba 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EArtifactWriterTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EArtifactWriterTest.java
@@ -43,9 +43,18 @@ class LLME2EArtifactWriterTest {
     
     @Test
     void assertWriteRedactsSecretsAndRecordsRunContext() throws IOException {
-        LLME2EArtifactBundle artifactBundle = new LLME2EArtifactBundle("scenario-id", "system", "user",
-                "openai-compatible", MODEL_NAME, "{\"api_key\":\"secret-value\"}", List.of("{\"token\":\"raw-secret\"}"), List.of(),
-                List.of("Authorization: Bearer runtime-secret", "MCP_LLM_API_KEY=runtime-secret"), LLME2EAssertionReport.failure("boom", "failed"));
+        LLME2EArtifactBundle artifactBundle = LLME2EArtifactBundle.builder()
+                .scenarioId("scenario-id")
+                .systemPrompt("system")
+                .userPrompt("user")
+                .modelProvider("openai-compatible")
+                .modelName(MODEL_NAME)
+                .finalAnswerJson("{\"api_key\":\"secret-value\"}")
+                .rawModelOutputs(List.of("{\"token\":\"raw-secret\"}"))
+                .interactionTrace(List.of())
+                .mcpRuntimeLogLines(List.of("Authorization: Bearer runtime-secret", "MCP_LLM_API_KEY=runtime-secret"))
+                .assertionReport(LLME2EAssertionReport.failure("boom", "failed"))
+                .build();
         new LLME2EArtifactWriter().write(tempDir, artifactBundle, createScoreClosingEvidence());
         Map<String, Object> runContext = JsonUtils.fromJsonString(Files.readString(tempDir.resolve("run-context.json")), new TypeReference<>() {
         });
@@ -64,8 +73,18 @@ class LLME2EArtifactWriterTest {
     
     @Test
     void assertWriteWithMissingScoreClosingEvidence() {
-        LLME2EArtifactBundle artifactBundle = new LLME2EArtifactBundle("scenario-id", "system", "user",
-                "openai-compatible", MODEL_NAME, "{}", List.of(), List.of(), List.of(), LLME2EAssertionReport.failure("boom", "failed"));
+        LLME2EArtifactBundle artifactBundle = LLME2EArtifactBundle.builder()
+                .scenarioId("scenario-id")
+                .systemPrompt("system")
+                .userPrompt("user")
+                .modelProvider("openai-compatible")
+                .modelName(MODEL_NAME)
+                .finalAnswerJson("{}")
+                .rawModelOutputs(List.of())
+                .interactionTrace(List.of())
+                .mcpRuntimeLogLines(List.of())
+                .assertionReport(LLME2EAssertionReport.failure("boom", "failed"))
+                .build();
         IllegalStateException actualException = assertThrows(IllegalStateException.class,
                 () -> new LLME2EArtifactWriter().write(tempDir, artifactBundle, Map.of("scoreClosing", true)));
         assertThat(actualException.getMessage(), is("Missing score-closing LLM runtime evidence field `runtimeMode`."));
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EAssertionReport.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EAssertionReport.java
index 2cbc9bb2b8b..5d33ad43649 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EAssertionReport.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EAssertionReport.java
@@ -17,13 +17,14 @@
 
 package org.apache.shardingsphere.test.e2e.mcp.llm.conversation.artifact;
 
+import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 
 /**
  * LLM E2E assertion report.
  */
-@RequiredArgsConstructor
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
 @Getter
 public final class LLME2EAssertionReport {
     
@@ -34,21 +35,21 @@ public final class LLME2EAssertionReport {
     private final String message;
     
     /**
-     * Whether success.
+     * Create a successful assertion report.
      *
      * @param message message
-     * @return success or not
+     * @return successful assertion report
      */
-    public static LLME2EAssertionReport isSuccess(final String message) {
+    public static LLME2EAssertionReport success(final String message) {
         return new LLME2EAssertionReport(true, "", message);
     }
     
     /**
-     * Whether failure.
+     * Create a failed assertion report.
      *
      * @param failureType failure type
      * @param message message
-     * @return failure or not
+     * @return failed assertion report
      */
     public static LLME2EAssertionReport failure(final String failureType, final String message) {
         return new LLME2EAssertionReport(false, failureType, message);
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/client/LLMChatMessage.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/client/LLMChatMessage.java
index 186e7ebdc5c..a0bf4e104b7 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/client/LLMChatMessage.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/client/LLMChatMessage.java
@@ -17,6 +17,7 @@
 
 package org.apache.shardingsphere.test.e2e.mcp.llm.conversation.client;
 
+import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 
@@ -25,7 +26,7 @@ import java.util.List;
 /**
  * LLM chat message.
  */
-@RequiredArgsConstructor
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
 @Getter
 public final class LLMChatMessage {
     
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/client/LLMChatModelClientTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/client/LLMChatModelClientTest.java
index 36b844e27be..f6b3b9abeed 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/client/LLMChatModelClientTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/client/LLMChatModelClientTest.java
@@ -161,9 +161,22 @@ class LLMChatModelClientTest {
     }
     
     private LLME2EConfiguration createConfiguration(final String baseUrl, final int readyTimeoutSeconds) {
-        return new LLME2EConfiguration(baseUrl, "openai-compatible", REQUIRED_MODEL, "mcp-llm-score", readyTimeoutSeconds, 30, 10,
-                Path.of("target/llm-e2e"), "run-id", RuntimeMode.DOCKER, "apache/shardingsphere-mcp-llm-runtime:local", "ghcr.io/ggml-org/llama.cpp:server-b9191",
-                "test-base-server-image-digest", MODEL_METADATA);
+        return LLME2EConfiguration.builder()
+                .baseUrl(baseUrl)
+                .modelProvider("openai-compatible")
+                .modelName(REQUIRED_MODEL)
+                .apiKey("mcp-llm-score")
+                .readyTimeoutSeconds(readyTimeoutSeconds)
+                .requestTimeoutSeconds(30)
+                .maxTurns(10)
+                .artifactRoot(Path.of("target/llm-e2e"))
+                .runId("run-id")
+                .runtimeMode(RuntimeMode.DOCKER)
+                .serverImage("apache/shardingsphere-mcp-llm-runtime:local")
+                .baseServerImage("ghcr.io/ggml-org/llama.cpp:server-b9191")
+                .baseServerImageDigest("test-base-server-image-digest")
+                .modelMetadata(MODEL_METADATA)
+                .build();
     }
     
     private HttpServer startModelServer(final String modelName, final List<String> requestBodies) throws IOException {
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeSupportTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeSupportTest.java
index c6219da67dd..a510c99c286 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeSupportTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeSupportTest.java
@@ -84,16 +84,32 @@ class LLMRuntimeSupportTest {
     
     @Test
     void assertPrepareWithUnsupportedProvider() {
-        LLME2EConfiguration config = new LLME2EConfiguration("http://127.0.0.1:8080/v1", "openai", REQUIRED_MODEL, "mcp-llm-score", 600, 240, 10,
-                Path.of("target/llm-e2e"), "run-id", RuntimeMode.DOCKER, "apache/shardingsphere-mcp-llm-runtime:local", "ghcr.io/ggml-org/llama.cpp:server-b9191",
-                "test-base-server-image-digest", MODEL_METADATA);
+        LLME2EConfiguration config = createConfiguration(RuntimeMode.DOCKER, REQUIRED_MODEL, "http://127.0.0.1:8080/v1").toBuilder()
+                .modelProvider("openai")
+                .readyTimeoutSeconds(600)
+                .requestTimeoutSeconds(240)
+                .build();
         IllegalStateException actualException = assertThrows(IllegalStateException.class, () -> LLMRuntimeSupport.prepare(config));
         assertThat(actualException.getMessage(), is("MCP LLM E2E requires provider openai-compatible."));
     }
     
     private LLME2EConfiguration createConfiguration(final RuntimeMode runtimeMode, final String modelName, final String baseUrl) {
-        return new LLME2EConfiguration(baseUrl, "openai-compatible", modelName, "mcp-llm-score", 2, 2, 10, Path.of("target/llm-e2e"), "run-id",
-                runtimeMode, "apache/shardingsphere-mcp-llm-runtime:local", "ghcr.io/ggml-org/llama.cpp:server-b9191", "test-base-server-image-digest", MODEL_METADATA);
+        return LLME2EConfiguration.builder()
+                .baseUrl(baseUrl)
+                .modelProvider("openai-compatible")
+                .modelName(modelName)
+                .apiKey("mcp-llm-score")
+                .readyTimeoutSeconds(2)
+                .requestTimeoutSeconds(2)
+                .maxTurns(10)
+                .artifactRoot(Path.of("target/llm-e2e"))
+                .runId("run-id")
+                .runtimeMode(runtimeMode)
+                .serverImage("apache/shardingsphere-mcp-llm-runtime:local")
+                .baseServerImage("ghcr.io/ggml-org/llama.cpp:server-b9191")
+                .baseServerImageDigest("test-base-server-image-digest")
+                .modelMetadata(MODEL_METADATA)
+                .build();
     }
     
     private HttpServer startModelServer(final String modelName) throws IOException {
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteE2ETest.java
index 6639df93a99..00fa412b9dc 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteE2ETest.java
@@ -119,11 +119,11 @@ class LLMUsabilitySuiteE2ETest extends AbstractConfigBackedRuntimeE2ETest {
         LLMConversationExecutor conversationExecutor = new LLMConversationExecutor(getRequiredLLMConfiguration(), getRequiredLLMRuntimeEvidence());
         conversationExecutor.assertModelReady();
         prepareRuntimeFixture();
-        suiteRunner.assertCoreSuite(suiteId + "/core",
+        suiteRunner.assertSuite(suiteId + "/core",
                 this::createCoreScenarios,
                 each -> conversationExecutor.runConversation(suiteId + "/core/" + each.getScenarioId(), each, createInteractionClient()),
                 conversationExecutor.getConfiguration());
-        suiteRunner.assertExtendedSuite(suiteId + "/extended",
+        suiteRunner.assertSuite(suiteId + "/extended",
                 this::createExtendedScenarios,
                 each -> conversationExecutor.runConversation(suiteId + "/extended/" + each.getScenarioId(), each, createInteractionClient()),
                 conversationExecutor.getConfiguration());
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteRunner.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteRunner.java
index 8860f88e89e..5177fb1cf51 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteRunner.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteRunner.java
@@ -64,18 +64,8 @@ final class LLMUsabilitySuiteRunner {
     
     private final LLMUsabilityReportWriter reportWriter = new LLMUsabilityReportWriter();
     
-    void assertCoreSuite(final String suiteId, final Supplier<List<LLMUsabilityScenario>> scenarioSupplier,
-                         final ConversationRunner conversationRunner, final LLME2EConfiguration configuration) throws IOException {
-        assertSuite(suiteId, scenarioSupplier, conversationRunner, configuration);
-    }
-    
-    void assertExtendedSuite(final String suiteId, final Supplier<List<LLMUsabilityScenario>> scenarioSupplier,
-                             final ConversationRunner conversationRunner, final LLME2EConfiguration configuration) throws IOException {
-        assertSuite(suiteId, scenarioSupplier, conversationRunner, configuration);
-    }
-    
-    private void assertSuite(final String suiteId, final Supplier<List<LLMUsabilityScenario>> scenarioSupplier,
-                             final ConversationRunner conversationRunner, final LLME2EConfiguration configuration) throws IOException {
+    void assertSuite(final String suiteId, final Supplier<List<LLMUsabilityScenario>> scenarioSupplier,
+                     final ConversationRunner conversationRunner, final LLME2EConfiguration configuration) throws IOException {
         EvaluatedSuite evaluatedSuite = evaluateSuite(suiteId, scenarioSupplier, conversationRunner, configuration);
         assumeModelServiceAvailable(evaluatedSuite.scorecard());
         assertFullScore(evaluatedSuite);
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteRunnerTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteRunnerTest.java
index 58309165b03..3006f027554 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteRunnerTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteRunnerTest.java
@@ -48,7 +48,7 @@ class LLMUsabilitySuiteRunnerTest {
     @Test
     void assertAssertCoreSuite() throws IOException {
         LLME2EConfiguration configuration = createConfiguration();
-        new LLMUsabilitySuiteRunner().assertCoreSuite("core-suite", () -> List.of(createScenario(LLMUsabilityScenario.NATURAL_TASK_TAG)),
+        new LLMUsabilitySuiteRunner().assertSuite("core-suite", () -> List.of(createScenario(LLMUsabilityScenario.NATURAL_TASK_TAG)),
                 scenario -> createConversationResult(configuration, scenario), configuration);
         assertTrue(Files.isRegularFile(tempDir.resolve("run-id").resolve("core-suite").resolve("scorecard.json")));
     }
@@ -56,7 +56,7 @@ class LLMUsabilitySuiteRunnerTest {
     @Test
     void assertAssertExtendedSuite() throws IOException {
         LLME2EConfiguration configuration = createConfiguration();
-        new LLMUsabilitySuiteRunner().assertExtendedSuite("extended-suite", () -> List.of(createScenario(LLMUsabilityScenario.PROTOCOL_CONTRACT_TAG)),
+        new LLMUsabilitySuiteRunner().assertSuite("extended-suite", () -> List.of(createScenario(LLMUsabilityScenario.PROTOCOL_CONTRACT_TAG)),
                 scenario -> createConversationResult(configuration, scenario), configuration);
         assertTrue(Files.isRegularFile(tempDir.resolve("run-id").resolve("extended-suite").resolve("scorecard.json")));
     }
@@ -65,7 +65,7 @@ class LLMUsabilitySuiteRunnerTest {
     void assertAssertCoreSuiteWithModelServiceUnavailable() {
         LLME2EConfiguration configuration = createConfiguration();
         TestAbortedException actual = assertThrows(TestAbortedException.class,
-                () -> new LLMUsabilitySuiteRunner().assertCoreSuite("core-suite", () -> List.of(createScenario(LLMUsabilityScenario.NATURAL_TASK_TAG)),
+                () -> new LLMUsabilitySuiteRunner().assertSuite("core-suite", () -> List.of(createScenario(LLMUsabilityScenario.NATURAL_TASK_TAG)),
                         scenario -> createConversationResult(configuration, scenario, LLME2EAssertionReport.failure("model_service_unavailable",
                                 "Model completion request failed with status 400.")),
                         configuration));
@@ -77,7 +77,7 @@ class LLMUsabilitySuiteRunnerTest {
     void assertFailureSummaryIncludesArtifactDirectory() {
         LLME2EConfiguration configuration = createConfiguration();
         AssertionError actual = assertThrows(AssertionError.class,
-                () -> new LLMUsabilitySuiteRunner().assertCoreSuite("core-suite", () -> List.of(createScenario(LLMUsabilityScenario.NATURAL_TASK_TAG)),
+                () -> new LLMUsabilitySuiteRunner().assertSuite("core-suite", () -> List.of(createScenario(LLMUsabilityScenario.NATURAL_TASK_TAG)),
                         scenario -> createConversationResult(configuration, scenario, LLME2EAssertionReport.failure("answer_mismatch", "Wrong answer.")),
                         configuration));
         Path artifactDirectory = tempDir.resolve("run-id").resolve("scenario-" + LLMUsabilityScenario.NATURAL_TASK_TAG);
@@ -86,7 +86,7 @@ class LLMUsabilitySuiteRunnerTest {
     }
     
     private LLMConversationExecutor.ConversationResult createConversationResult(final LLME2EConfiguration configuration, final LLME2EScenario scenario) throws IOException {
-        return createConversationResult(configuration, scenario, LLME2EAssertionReport.isSuccess("ok"));
+        return createConversationResult(configuration, scenario, LLME2EAssertionReport.success("ok"));
     }
     
     private LLMConversationExecutor.ConversationResult createConversationResult(final LLME2EConfiguration configuration, final LLME2EScenario scenario,
@@ -95,8 +95,18 @@ class LLMUsabilitySuiteRunnerTest {
         createArtifactFiles(artifactDirectory);
         List<MCPInteractionTraceRecord> trace = List.of(new MCPInteractionTraceRecord(1, "tool_call", MCPInteractionTraceRecord.MODEL_TOOL_CALL_ORIGIN,
                 "database_gateway_execute_query", Map.of(), Map.of("result_kind", "result_set"), true, 0L));
-        LLME2EArtifactBundle artifactBundle = new LLME2EArtifactBundle(scenario.getScenarioId(), scenario.getSystemPrompt(), scenario.getUserPrompt(), "provider", "model",
-                "{}", List.of("raw-output"), trace, List.of("runtime-log"), assertionReport);
+        LLME2EArtifactBundle artifactBundle = LLME2EArtifactBundle.builder()
+                .scenarioId(scenario.getScenarioId())
+                .systemPrompt(scenario.getSystemPrompt())
+                .userPrompt(scenario.getUserPrompt())
+                .modelProvider("provider")
+                .modelName("model")
+                .finalAnswerJson("{}")
+                .rawModelOutputs(List.of("raw-output"))
+                .interactionTrace(trace)
+                .mcpRuntimeLogLines(List.of("runtime-log"))
+                .assertionReport(assertionReport)
+                .build();
         return new LLMConversationExecutor.ConversationResult(artifactBundle, artifactDirectory);
     }
     
@@ -111,12 +121,36 @@ class LLMUsabilitySuiteRunnerTest {
         LLME2EScenario llmScenario = new LLME2EScenario("scenario-" + tag, "system", "Count orders.",
                 new LLMStructuredAnswer("logic_db", "public", "orders", "SELECT COUNT(*) FROM orders", 2, List.of()),
                 List.of("database_gateway_execute_query"), List.of("database_gateway_execute_query"));
-        return new LLMUsabilityScenario("scenario-" + tag, LLMUsabilityDimension.TOOL, "runtime", List.of(tag), llmScenario,
-                List.of("database_gateway_execute_query"), List.of(), false, false, "");
+        return LLMUsabilityScenario.builder()
+                .scenarioId("scenario-" + tag)
+                .dimension(LLMUsabilityDimension.TOOL)
+                .runtimeKind("runtime")
+                .tags(List.of(tag))
+                .llmScenario(llmScenario)
+                .expectedFirstActionNames(List.of("database_gateway_execute_query"))
+                .expectedResourceUris(List.of())
+                .resourceHitRequired(false)
+                .recoveryExpected(false)
+                .expectedRecoveryCategory("")
+                .build();
     }
     
     private LLME2EConfiguration createConfiguration() {
-        return new LLME2EConfiguration("http://127.0.0.1:8080/v1", "provider", "model", "api-key", 1, 1, 1, tempDir, "run-id", RuntimeMode.EXTERNAL_DEBUG,
-                "server-image", "base-image", "", new LLME2EConfiguration.ModelMetadata("repository", "model.gguf", "Q4", "revision", "sha256"));
+        return LLME2EConfiguration.builder()
+                .baseUrl("http://127.0.0.1:8080/v1")
+                .modelProvider("provider")
+                .modelName("model")
+                .apiKey("api-key")
+                .readyTimeoutSeconds(1)
+                .requestTimeoutSeconds(1)
+                .maxTurns(1)
+                .artifactRoot(tempDir)
+                .runId("run-id")
+                .runtimeMode(RuntimeMode.EXTERNAL_DEBUG)
+                .serverImage("server-image")
+                .baseServerImage("base-image")
+                .baseServerImageDigest("")
+                .modelMetadata(new LLME2EConfiguration.ModelMetadata("repository", "model.gguf", "Q4", "revision", "sha256"))
+                .build();
     }
 }
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityMetricCalculator.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityMetricCalculator.java
index e608adc55c6..1f21a613c72 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityMetricCalculator.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityMetricCalculator.java
@@ -89,9 +89,27 @@ public final class LLMUsabilityMetricCalculator {
         boolean approvalViolation = traceMetrics.hasApprovalViolation(interactionTrace);
         boolean nativeToolCallCoverage = traceMetrics.hasNativeRequiredToolCoverage(scenario.getLlmScenario().getRequiredToolNames(), interactionTrace);
         boolean harnessRecoveryUsed = traceMetrics.hasHarnessRecovery(interactionTrace);
-        return new LLMUsabilityScenarioResult(scenario.getScenarioId(), scenario.getDimension(), scenario.getRuntimeKind(), scenario.getTags(), success, failureType, message,
-                firstCorrectAction, invalidCallCount, interactionTrace.size(), resourceHit, recoveredAfterError, queryAnswerFidelity,
-                boundaryConfusion, nextActionFollowed, approvalViolation, nativeToolCallCoverage, harnessRecoveryUsed, interactionTrace);
+        return LLMUsabilityScenarioResult.builder()
+                .scenarioId(scenario.getScenarioId())
+                .dimension(scenario.getDimension())
+                .runtimeKind(scenario.getRuntimeKind())
+                .tags(scenario.getTags())
+                .success(success)
+                .failureType(failureType)
+                .message(message)
+                .firstCorrectAction(firstCorrectAction)
+                .invalidCallCount(invalidCallCount)
+                .roundTripCount(interactionTrace.size())
+                .resourceHit(resourceHit)
+                .recoveredAfterError(recoveredAfterError)
+                .queryAnswerFidelity(queryAnswerFidelity)
+                .boundaryConfusion(boundaryConfusion)
+                .nextActionFollowed(nextActionFollowed)
+                .approvalViolation(approvalViolation)
+                .nativeToolCallCoverage(nativeToolCallCoverage)
+                .harnessRecoveryUsed(harnessRecoveryUsed)
+                .interactionTrace(interactionTrace)
+                .build();
     }
     
     /**
@@ -119,10 +137,27 @@ public final class LLMUsabilityMetricCalculator {
         double harnessRecoveryRate = calculateRate(scenarioResults, LLMUsabilityScenarioResult::isHarnessRecoveryUsed);
         double overallScore = calculateOverallScore(taskSuccessRate, firstCorrectActionRate, invalidCallRate, queryAnswerFidelity,
                 boundaryConfusionRate, resourceHitRate, recoveryRate, nextActionFollowRate, approvalViolationRate);
-        return new LLMUsabilityScorecard(suiteId, runId, overallScore,
-                isFullScore(overallScore, nativeToolCallRate, harnessRecoveryRate, naturalTaskSuccessRate, protocolContractSuccessRate), taskSuccessRate,
-                naturalTaskSuccessRate, protocolContractSuccessRate, firstCorrectActionRate, invalidCallRate, averageRoundTrips, queryAnswerFidelity, boundaryConfusionRate,
-                resourceHitRate, recoveryRate, nextActionFollowRate, approvalViolationRate, nativeToolCallRate, harnessRecoveryRate, scenarioResults);
+        return LLMUsabilityScorecard.builder()
+                .suiteId(suiteId)
+                .runId(runId)
+                .overallScore(overallScore)
+                .fullScore(isFullScore(overallScore, nativeToolCallRate, harnessRecoveryRate, naturalTaskSuccessRate, protocolContractSuccessRate))
+                .taskSuccessRate(taskSuccessRate)
+                .naturalTaskSuccessRate(naturalTaskSuccessRate)
+                .protocolContractSuccessRate(protocolContractSuccessRate)
+                .firstCorrectActionRate(firstCorrectActionRate)
+                .invalidCallRate(invalidCallRate)
+                .averageRoundTrips(averageRoundTrips)
+                .queryAnswerFidelity(queryAnswerFidelity)
+                .boundaryConfusionRate(boundaryConfusionRate)
+                .resourceHitRate(resourceHitRate)
+                .recoveryRate(recoveryRate)
+                .nextActionFollowRate(nextActionFollowRate)
+                .approvalViolationRate(approvalViolationRate)
+                .nativeToolCallRate(nativeToolCallRate)
+                .harnessRecoveryRate(harnessRecoveryRate)
+                .scenarioResults(scenarioResults)
+                .build();
     }
     
     private double calculateOverallScore(final double taskSuccessRate, final double firstCorrectActionRate, final double invalidCallRate,
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityMetricCalculatorTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityMetricCalculatorTest.java
index 2675d8dd86d..d2748a06d9c 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityMetricCalculatorTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityMetricCalculatorTest.java
@@ -258,46 +258,121 @@ class LLMUsabilityMetricCalculatorTest {
     }
     
     private LLME2EArtifactBundle createArtifactBundle(final List<MCPInteractionTraceRecord> interactionTrace) {
-        return new LLME2EArtifactBundle("scenario", "", "", "", "", "", List.of(), interactionTrace, List.of(), LLME2EAssertionReport.isSuccess("ok"));
+        return createArtifactBundle(interactionTrace, LLME2EAssertionReport.success("ok"));
+    }
+    
+    private LLME2EArtifactBundle createArtifactBundle(final List<MCPInteractionTraceRecord> interactionTrace, final LLME2EAssertionReport assertionReport) {
+        return LLME2EArtifactBundle.builder()
+                .scenarioId("scenario")
+                .systemPrompt("")
+                .userPrompt("")
+                .modelProvider("")
+                .modelName("")
+                .finalAnswerJson("")
+                .rawModelOutputs(List.of())
+                .interactionTrace(interactionTrace)
+                .mcpRuntimeLogLines(List.of())
+                .assertionReport(assertionReport)
+                .build();
     }
     
     private LLME2EArtifactBundle createFailedArtifactBundle(final List<MCPInteractionTraceRecord> interactionTrace) {
-        return new LLME2EArtifactBundle("scenario", "", "", "", "", "", List.of(), interactionTrace, List.of(), LLME2EAssertionReport.failure("failed", "failed"));
+        return createArtifactBundle(interactionTrace, LLME2EAssertionReport.failure("failed", "failed"));
     }
     
     private LLMUsabilityScenario createScenario() {
         LLME2EScenario llmScenario = new LLME2EScenario("scenario", "", "", null, List.of(), List.of());
-        return new LLMUsabilityScenario("scenario", LLMUsabilityDimension.TOOL, "runtime", List.of(LLMUsabilityScenario.NATURAL_TASK_TAG, "natural"), llmScenario,
-                List.of(MCPInteractionActionNames.READ_RESOURCE, "database_gateway_execute_update"), List.of(), false, false, "");
+        return LLMUsabilityScenario.builder()
+                .scenarioId("scenario")
+                .dimension(LLMUsabilityDimension.TOOL)
+                .runtimeKind("runtime")
+                .tags(List.of(LLMUsabilityScenario.NATURAL_TASK_TAG, "natural"))
+                .llmScenario(llmScenario)
+                .expectedFirstActionNames(List.of(MCPInteractionActionNames.READ_RESOURCE, "database_gateway_execute_update"))
+                .expectedResourceUris(List.of())
+                .resourceHitRequired(false)
+                .recoveryExpected(false)
+                .expectedRecoveryCategory("")
+                .build();
     }
     
     private LLMUsabilityScenario createRequiredToolScenario() {
         LLME2EScenario llmScenario = new LLME2EScenario("scenario", "", "", null, List.of("database_gateway_execute_update"), List.of("database_gateway_execute_update"));
-        return new LLMUsabilityScenario("scenario", LLMUsabilityDimension.TOOL, "runtime", List.of(LLMUsabilityScenario.NATURAL_TASK_TAG, "natural"), llmScenario,
-                List.of("database_gateway_execute_update"), List.of(), false, false, "");
+        return LLMUsabilityScenario.builder()
+                .scenarioId("scenario")
+                .dimension(LLMUsabilityDimension.TOOL)
+                .runtimeKind("runtime")
+                .tags(List.of(LLMUsabilityScenario.NATURAL_TASK_TAG, "natural"))
+                .llmScenario(llmScenario)
+                .expectedFirstActionNames(List.of("database_gateway_execute_update"))
+                .expectedResourceUris(List.of())
+                .resourceHitRequired(false)
+                .recoveryExpected(false)
+                .expectedRecoveryCategory("")
+                .build();
     }
     
     private LLMUsabilityScenario createProtocolScenario() {
         LLME2EScenario llmScenario = new LLME2EScenario("scenario", "", "", null, List.of("mcp_read_resource"), List.of("mcp_read_resource"));
-        return new LLMUsabilityScenario("scenario", LLMUsabilityDimension.RESOURCE, "runtime", List.of(LLMUsabilityScenario.PROTOCOL_CONTRACT_TAG, "protocol"), llmScenario,
-                List.of("mcp_read_resource"), List.of(), false, false, "");
+        return LLMUsabilityScenario.builder()
+                .scenarioId("scenario")
+                .dimension(LLMUsabilityDimension.RESOURCE)
+                .runtimeKind("runtime")
+                .tags(List.of(LLMUsabilityScenario.PROTOCOL_CONTRACT_TAG, "protocol"))
+                .llmScenario(llmScenario)
+                .expectedFirstActionNames(List.of("mcp_read_resource"))
+                .expectedResourceUris(List.of())
+                .resourceHitRequired(false)
+                .recoveryExpected(false)
+                .expectedRecoveryCategory("")
+                .build();
     }
     
     private LLMUsabilityScenario createRecoveryScenario() {
         LLME2EScenario llmScenario = new LLME2EScenario("scenario", "", "", null, List.of(), List.of());
-        return new LLMUsabilityScenario("scenario", LLMUsabilityDimension.RECOVERY, "runtime", List.of(LLMUsabilityScenario.NATURAL_TASK_TAG, "extended", "recovery"), llmScenario,
-                List.of(MCPInteractionActionNames.READ_RESOURCE), List.of("shardingsphere://databases/logic_db/schemas/public/tables/orders"), true, true, "not_found");
+        return LLMUsabilityScenario.builder()
+                .scenarioId("scenario")
+                .dimension(LLMUsabilityDimension.RECOVERY)
+                .runtimeKind("runtime")
+                .tags(List.of(LLMUsabilityScenario.NATURAL_TASK_TAG, "extended", "recovery"))
+                .llmScenario(llmScenario)
+                .expectedFirstActionNames(List.of(MCPInteractionActionNames.READ_RESOURCE))
+                .expectedResourceUris(List.of("shardingsphere://databases/logic_db/schemas/public/tables/orders"))
+                .resourceHitRequired(true)
+                .recoveryExpected(true)
+                .expectedRecoveryCategory("not_found")
+                .build();
     }
     
     private LLMUsabilityScenario createExpectedRecoveryScenario(final String expectedRecoveryCategory) {
         LLME2EScenario llmScenario = new LLME2EScenario("scenario", "", "", null, List.of(), List.of());
-        return new LLMUsabilityScenario("scenario", LLMUsabilityDimension.RECOVERY, "runtime", List.of(LLMUsabilityScenario.NATURAL_TASK_TAG, "extended", "recovery"), llmScenario,
-                List.of("database_gateway_execute_query", "database_gateway_search_metadata"), List.of(), false, true, expectedRecoveryCategory);
+        return LLMUsabilityScenario.builder()
+                .scenarioId("scenario")
+                .dimension(LLMUsabilityDimension.RECOVERY)
+                .runtimeKind("runtime")
+                .tags(List.of(LLMUsabilityScenario.NATURAL_TASK_TAG, "extended", "recovery"))
+                .llmScenario(llmScenario)
+                .expectedFirstActionNames(List.of("database_gateway_execute_query", "database_gateway_search_metadata"))
+                .expectedResourceUris(List.of())
+                .resourceHitRequired(false)
+                .recoveryExpected(true)
+                .expectedRecoveryCategory(expectedRecoveryCategory)
+                .build();
     }
     
     private LLMUsabilityScenario createContextRecoveryScenario() {
         LLME2EScenario llmScenario = new LLME2EScenario("scenario", "", "", null, List.of(), List.of());
-        return new LLMUsabilityScenario("scenario", LLMUsabilityDimension.RECOVERY, "runtime", List.of(LLMUsabilityScenario.PROTOCOL_CONTRACT_TAG, "extended", "workflow", "recovery"), llmScenario,
-                List.of("database_gateway_plan_mask_rule"), List.of(), false, false, "");
+        return LLMUsabilityScenario.builder()
+                .scenarioId("scenario")
+                .dimension(LLMUsabilityDimension.RECOVERY)
+                .runtimeKind("runtime")
+                .tags(List.of(LLMUsabilityScenario.PROTOCOL_CONTRACT_TAG, "extended", "workflow", "recovery"))
+                .llmScenario(llmScenario)
+                .expectedFirstActionNames(List.of("database_gateway_plan_mask_rule"))
+                .expectedResourceUris(List.of())
+                .resourceHitRequired(false)
+                .recoveryExpected(false)
+                .expectedRecoveryCategory("")
+                .build();
     }
 }
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityReportWriterTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityReportWriterTest.java
index e4d417b2d03..46d4461127f 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityReportWriterTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityReportWriterTest.java
@@ -35,11 +35,48 @@ class LLMUsabilityReportWriterTest {
     
     @Test
     void assertWriteScorecardSummary() throws IOException {
-        LLMUsabilityScenarioResult scenarioResult = new LLMUsabilityScenarioResult("scenario-1", LLMUsabilityDimension.TOOL, "mysql",
-                List.of("natural-task"), false, "wrong_tool", "Expected database_gateway_execute_query.", false, 1, 3, false, false, 0.0D, true,
-                false, false, false, true, List.of());
-        LLMUsabilityScorecard scorecard = new LLMUsabilityScorecard("suite-1", "run-1", 75.0D, false, 0.0D, 0.0D, 1.0D, 0.0D,
-                0.33D, 3.0D, 0.0D, 1.0D, 0.0D, 0.0D, 0.0D, 0.0D, 0.0D, 1.0D, List.of(scenarioResult));
+        LLMUsabilityScenarioResult scenarioResult = LLMUsabilityScenarioResult.builder()
+                .scenarioId("scenario-1")
+                .dimension(LLMUsabilityDimension.TOOL)
+                .runtimeKind("mysql")
+                .tags(List.of("natural-task"))
+                .success(false)
+                .failureType("wrong_tool")
+                .message("Expected database_gateway_execute_query.")
+                .firstCorrectAction(false)
+                .invalidCallCount(1)
+                .roundTripCount(3)
+                .resourceHit(false)
+                .recoveredAfterError(false)
+                .queryAnswerFidelity(0.0D)
+                .boundaryConfusion(true)
+                .nextActionFollowed(false)
+                .approvalViolation(false)
+                .nativeToolCallCoverage(false)
+                .harnessRecoveryUsed(true)
+                .interactionTrace(List.of())
+                .build();
+        LLMUsabilityScorecard scorecard = LLMUsabilityScorecard.builder()
+                .suiteId("suite-1")
+                .runId("run-1")
+                .overallScore(75.0D)
+                .fullScore(false)
+                .taskSuccessRate(0.0D)
+                .naturalTaskSuccessRate(0.0D)
+                .protocolContractSuccessRate(1.0D)
+                .firstCorrectActionRate(0.0D)
+                .invalidCallRate(0.33D)
+                .averageRoundTrips(3.0D)
+                .queryAnswerFidelity(0.0D)
+                .boundaryConfusionRate(1.0D)
+                .resourceHitRate(0.0D)
+                .recoveryRate(0.0D)
+                .nextActionFollowRate(0.0D)
+                .approvalViolationRate(0.0D)
+                .nativeToolCallRate(0.0D)
+                .harnessRecoveryRate(1.0D)
+                .scenarioResults(List.of(scenarioResult))
+                .build();
         new LLMUsabilityReportWriter().writeScorecard(tempDir, scorecard);
         assertThat(Files.readString(tempDir.resolve("summary.md")), is(createExpectedSummary()));
     }
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityScenarioResult.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityScenarioResult.java
index 10e8b706502..a56b9a72f52 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityScenarioResult.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityScenarioResult.java
@@ -17,13 +17,16 @@
 
 package org.apache.shardingsphere.test.e2e.mcp.llm.suite.usability.assessment;
 
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
 import org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPInteractionTraceRecord;
 
 import java.util.List;
 
-@RequiredArgsConstructor
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Builder
 @Getter
 public final class LLMUsabilityScenarioResult {
     
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityScorecard.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityScorecard.java
index abcf97b0506..4a1128ae04d 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityScorecard.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityScorecard.java
@@ -17,12 +17,15 @@
 
 package org.apache.shardingsphere.test.e2e.mcp.llm.suite.usability.assessment;
 
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
 
 import java.util.List;
 
-@RequiredArgsConstructor
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Builder
 @Getter
 public final class LLMUsabilityScorecard {
     
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityTraceMetrics.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityTraceMetrics.java
index 736014b68c5..8ce0d21dbb1 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityTraceMetrics.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/assessment/LLMUsabilityTraceMetrics.java
@@ -88,18 +88,16 @@ final class LLMUsabilityTraceMetrics {
     }
     
     boolean isNextActionFollowed(final List<MCPInteractionTraceRecord> interactionTrace) {
-        boolean hasActionableGuidance = false;
         for (int index = 0; index < interactionTrace.size() - 1; index++) {
             List<Map<?, ?>> actions = getImmediateMachineNextActions(interactionTrace.get(index));
             if (actions.isEmpty()) {
                 continue;
             }
-            hasActionableGuidance = true;
             if (!matchesAnyNextAction(actions, interactionTrace.get(index), interactionTrace.get(index + 1))) {
                 return false;
             }
         }
-        return !hasActionableGuidance || !interactionTrace.isEmpty();
+        return true;
     }
     
     boolean hasApprovalViolation(final List<MCPInteractionTraceRecord> interactionTrace) {
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/scenario/LLMUsabilityScenario.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/scenario/LLMUsabilityScenario.java
index 8e1f9554144..1d7be6d3673 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/scenario/LLMUsabilityScenario.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/scenario/LLMUsabilityScenario.java
@@ -17,14 +17,17 @@
 
 package org.apache.shardingsphere.test.e2e.mcp.llm.suite.usability.scenario;
 
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
 import org.apache.shardingsphere.test.e2e.mcp.llm.scenario.LLME2EScenario;
 import org.apache.shardingsphere.test.e2e.mcp.llm.suite.usability.assessment.LLMUsabilityDimension;
 
 import java.util.List;
 
-@RequiredArgsConstructor
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Builder
 @Getter
 public final class LLMUsabilityScenario {
     
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/scenario/LLMUsabilityScenarioCatalog.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/scenario/LLMUsabilityScenarioCatalog.java
index 8aaeccb6dc4..4020b5468d1 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/scenario/LLMUsabilityScenarioCatalog.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/scenario/LLMUsabilityScenarioCatalog.java
@@ -208,7 +208,17 @@ public final class LLMUsabilityScenarioCatalog {
                                                 final List<String> tags, final LLME2EScenario llmScenario,
                                                 final List<String> expectedFirstActionNames, final List<String> expectedResourceUris,
                                                 final boolean resourceHitRequired, final boolean recoveryExpected, final String expectedRecoveryCategory) {
-        return new LLMUsabilityScenario(scenarioId, dimension, runtimeKind, tags, llmScenario, expectedFirstActionNames, expectedResourceUris, resourceHitRequired, recoveryExpected,
-                expectedRecoveryCategory);
+        return LLMUsabilityScenario.builder()
+                .scenarioId(scenarioId)
+                .dimension(dimension)
+                .runtimeKind(runtimeKind)
+                .tags(tags)
+                .llmScenario(llmScenario)
+                .expectedFirstActionNames(expectedFirstActionNames)
+                .expectedResourceUris(expectedResourceUris)
+                .resourceHitRequired(resourceHitRequired)
+                .recoveryExpected(recoveryExpected)
+                .expectedRecoveryCategory(expectedRecoveryCategory)
+                .build();
     }
 }
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java
index 2edf51b44fa..1cb3c2e1f7f 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java
@@ -165,13 +165,17 @@ public final class MySQLRuntimeTestSupport {
      * @throws SQLException SQL exception
      */
     public static LLMMySQLRuntimeFixture createLLMRuntimeFixture(final String logicalDatabase) throws SQLException {
-        GenericContainer<?> container = createContainer();
-        container.start();
-        initializeDatabase(container);
-        String schemaName = detectSchema(container);
-        String physicalSchemaName = schemaName.isEmpty() ? DATABASE_NAME : schemaName;
-        int totalOrders = querySingleInt(container, String.format(COUNT_ORDERS_SQL, physicalSchemaName));
-        return new LLMMySQLRuntimeFixture(container, logicalDatabase, totalOrders, createRuntimeDatabases(container, logicalDatabase));
+        try (ContainerStartupGuard startupGuard = new ContainerStartupGuard(createContainer())) {
+            GenericContainer<?> container = startupGuard.container;
+            container.start();
+            initializeDatabase(container);
+            String jdbcMetadataSchemaName = detectSchema(container);
+            String resolvedPhysicalSchemaName = jdbcMetadataSchemaName.isEmpty() ? DATABASE_NAME : jdbcMetadataSchemaName;
+            int totalOrders = querySingleInt(container, String.format(COUNT_ORDERS_SQL, resolvedPhysicalSchemaName));
+            LLMMySQLRuntimeFixture result = new LLMMySQLRuntimeFixture(container, logicalDatabase, totalOrders, createRuntimeDatabases(container, logicalDatabase));
+            startupGuard.complete();
+            return result;
+        }
     }
     
     /**
@@ -357,7 +361,26 @@ public final class MySQLRuntimeTestSupport {
         }
     }
     
-    @RequiredArgsConstructor
+    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+    private static final class ContainerStartupGuard implements AutoCloseable {
+        
+        private final GenericContainer<?> container;
+        
+        private boolean completed;
+        
+        private void complete() {
+            completed = true;
+        }
+        
+        @Override
+        public void close() {
+            if (!completed) {
+                container.stop();
+            }
+        }
+    }
+    
+    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
     @Getter
     public static final class LLMMySQLRuntimeFixture implements AutoCloseable {
         
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupportTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupportTest.java
index 648e96e474a..ceac136ac3b 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupportTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupportTest.java
@@ -18,12 +18,19 @@
 package org.apache.shardingsphere.test.e2e.mcp.support.runtime;
 
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.testcontainers.containers.GenericContainer;
 
 import java.util.Properties;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
 
 class MySQLRuntimeTestSupportTest {
     
@@ -50,4 +57,20 @@ class MySQLRuntimeTestSupportTest {
         IllegalStateException actualException = assertThrows(IllegalStateException.class, () -> MySQLRuntimeTestSupport.getMySQLImage(new Properties()));
         assertThat(actualException.getMessage(), is("MCP E2E MySQL image property `mcp.e2e.mysql.image` is required."));
     }
+    
+    @Test
+    void assertCreateLLMRuntimeFixturePreservesInitializationFailure() {
+        GenericContainer<?> container = mock(GenericContainer.class);
+        IllegalStateException initializationFailure = new IllegalStateException("start failed");
+        IllegalStateException cleanupFailure = new IllegalStateException("stop failed");
+        doThrow(initializationFailure).when(container).start();
+        doThrow(cleanupFailure).when(container).stop();
+        try (MockedStatic<MySQLRuntimeTestSupport> mocked = mockStatic(MySQLRuntimeTestSupport.class, CALLS_REAL_METHODS)) {
+            mocked.when(MySQLRuntimeTestSupport::createContainer).thenReturn(container);
+            IllegalStateException actual = assertThrows(IllegalStateException.class, () -> MySQLRuntimeTestSupport.createLLMRuntimeFixture("logic_db"));
+            assertThat(actual, sameInstance(initializationFailure));
+            assertThat(actual.getSuppressed().length, is(1));
+            assertThat(actual.getSuppressed()[0], sameInstance(cleanupFailure));
+        }
+    }
 }