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/10/14 16:30:36 UTC

(shardingsphere) branch master updated: Add MCP unit tests (#40167)

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 01446d4fad4 Add MCP unit tests (#40167)
01446d4fad4 is described below

commit 01446d4fad48450e7fc3eb3b3631b25d6bf411df
Author: Liang Zhang <zh...@apache.org>
AuthorDate: Thu Oct 15 00:30:27 2026 +0800

    Add MCP unit tests (#40167)
    
    * Add MCP unit tests
    
    * Add MCP unit tests
---
 .../transport/HttpTransportOriginUtilsTest.java    |  27 +-
 .../transport/MCPTransportErrorFactoryTest.java    |   8 +
 .../MCPCompletionSpecificationFactoryTest.java     |  23 +
 .../MCPResourceSpecificationFactoryTest.java       |  19 +
 .../tool/MCPToolSpecificationFactoryTest.java      |  20 +
 .../http/SessionAttributionResolverTest.java       |  13 +
 .../SessionManagedStdioTransportProviderTest.java  |  86 +++
 .../core/completion/MCPCompletionServiceTest.java  |  11 +
 .../core/handler/core/CoreHandlerProviderTest.java |  98 +++
 .../GovernanceMetadataQueryServiceTest.java        |  25 +
 .../core/protocol/error/MCPErrorConverterTest.java |  61 ++
 .../core/resource/MCPResourceControllerTest.java   |  10 +
 .../tool/handler/ToolDefinitionRegistryTest.java   | 145 +++++
 .../execute/ExecuteUpdateToolHandlerTest.java      |  36 ++
 .../execute/MCPJdbcStatementExecutorTest.java      |  84 +++
 .../MCPJdbcTransactionResourceManagerTest.java     |  30 +
 .../MCPJdbcTransactionStatementExecutorTest.java   |  23 +
 .../handler/execute/MCPSQLExecutionFacadeTest.java | 665 +++++++++++++++++++++
 .../tool/model/EncryptWorkflowRequestTest.java     |  12 +
 .../EncryptWorkflowApplyArtifactValidatorTest.java |  21 +
 .../EncryptWorkflowValidationServiceTest.java      |  33 +
 .../ReadwriteSplittingRuleWorkflowRequestTest.java |   7 +
 ...eadwriteSplittingStatusWorkflowRequestTest.java |  12 +
 ...plittingAlgorithmRecommendationServiceTest.java |  41 +-
 .../ReadwriteSplittingInspectionServiceTest.java   |  14 +
 ...SplittingStatusWorkflowPlanningServiceTest.java |  10 +
 .../ShadowAlgorithmCleanupWorkflowRequestTest.java |  12 +
 .../ShadowDefaultAlgorithmWorkflowRequestTest.java |  19 +
 .../tool/model/ShadowRuleWorkflowRequestTest.java  |  20 +
 .../ShadowAlgorithmRecommendationServiceTest.java  |  32 +-
 .../tool/service/ShadowInspectionServiceTest.java  |  28 +
 .../handler/ShardingTableResourceHandlerTest.java  |   3 +-
 .../PlanShardingTableRuleToolHandlerTest.java      |   3 +-
 ...ShardingAlgorithmRecommendationServiceTest.java |  25 +
 .../ShardingDistSQLPlanningServiceTest.java        |  37 ++
 .../ShardingWorkflowPlanningServiceTest.java       |  60 +-
 .../registry/MCPRegistryMetadataCommandTest.java   |  83 ++-
 .../jdbc/MCPJdbcDatabaseProfileLoaderTest.java     | 147 +++++
 .../RuntimeDatabaseValidationServiceTest.java      |  43 ++
 ...MCPCompletionTargetDescriptorValidatorTest.java |  63 +-
 .../descriptor/MCPDescriptorCatalogIndexTest.java  |  12 +
 .../descriptor/MCPDescriptorCatalogLoaderTest.java | 116 ++++
 .../MCPPromptDescriptorValidatorTest.java          |  31 +-
 .../MCPResourceDescriptorValidatorTest.java        |  49 +-
 ...PResourceNavigationDescriptorValidatorTest.java |  46 +-
 .../MCPToolDescriptorCatalogValidatorTest.java     | 142 ++++-
 .../MCPToolInputSchemaValidatorTest.java           |  48 +-
 .../MCPToolOutputSchemaValidatorTest.java          |  65 ++
 .../validator/ISOOffsetDateTimeValidatorTest.java  |  60 ++
 .../validator/MCPDescriptionValidatorTest.java     |  70 +++
 .../MCPPromptGuidanceMetaValidatorTest.java        |  65 ++
 .../resource/MCPUriPathSegmentUtilsTest.java       |  20 +-
 .../mcp/support/resource/MCPUriTemplateTest.java   |  34 +-
 .../service/WorkflowLifecycleUtilsTest.java        |  45 +-
 .../service/WorkflowPlanPayloadBuilderTest.java    |  15 +
 55 files changed, 2784 insertions(+), 143 deletions(-)

diff --git a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/HttpTransportOriginUtilsTest.java b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/HttpTransportOriginUtilsTest.java
index 8a45de63b8d..283d424258f 100644
--- a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/HttpTransportOriginUtilsTest.java
+++ b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/HttpTransportOriginUtilsTest.java
@@ -17,25 +17,28 @@
 
 package org.apache.shardingsphere.mcp.bootstrap.transport;
 
-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.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 
 class HttpTransportOriginUtilsTest {
     
-    @Test
-    void assertNormalizeOrigin() {
-        assertThat(HttpTransportOriginUtils.normalizeOrigin("HTTPS://Gateway.Example.Test:8443"), is("https://gateway.example.test:8443"));
-    }
-    
-    @Test
-    void assertNormalizeOriginWithIpv6Origin() {
-        assertThat(HttpTransportOriginUtils.normalizeOrigin("http://[::1]:8080"), is("http://[::1]:8080"));
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("assertNormalizeOriginArguments")
+    void assertNormalizeOrigin(final String name, final String origin, final String expected) {
+        assertThat(HttpTransportOriginUtils.normalizeOrigin(origin), is(expected));
     }
     
-    @Test
-    void assertNormalizeOriginWithMalformedOrigin() {
-        assertThat(HttpTransportOriginUtils.normalizeOrigin("://bad-origin"), is(""));
+    private static Stream<Arguments> assertNormalizeOriginArguments() {
+        return Stream.of(
+                Arguments.of("HTTPS host normalization", "HTTPS://Gateway.Example.Test:8443", "https://gateway.example.test:8443"),
+                Arguments.of("IPv6 origin", "http://[::1]:8080", "http://[::1]:8080"),
+                Arguments.of("malformed origin", "://bad-origin", ""),
+                Arguments.of("unsupported scheme", "ftp://foo.example.test", ""));
     }
 }
diff --git a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/MCPTransportErrorFactoryTest.java b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/MCPTransportErrorFactoryTest.java
index 722f79e38cf..71e85596ccb 100644
--- a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/MCPTransportErrorFactoryTest.java
+++ b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/MCPTransportErrorFactoryTest.java
@@ -31,6 +31,7 @@ import org.apache.shardingsphere.test.infra.framework.extension.log.LogCaptureSe
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 
+import java.sql.SQLException;
 import java.util.Map;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -107,4 +108,11 @@ class MCPTransportErrorFactoryTest {
         assertThat(actual.getJsonRpcError().code(), is(McpSchema.ErrorCodes.INVALID_PARAMS));
         assertThat(actual.getJsonRpcError().message(), is("Invalid request."));
     }
+    
+    @Test
+    void assertCreateErrorWithDatabaseFailure() {
+        McpError actual = MCPTransportErrorFactory.createError(new SQLException("foo_message"));
+        assertThat(actual.getJsonRpcError().code(), is(McpSchema.ErrorCodes.INTERNAL_ERROR));
+        assertThat(actual.getJsonRpcError().message(), is("MCP query failed."));
+    }
 }
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 22e0b0f85a4..ad3705edf9d 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
@@ -23,7 +23,10 @@ import io.modelcontextprotocol.spec.McpError;
 import io.modelcontextprotocol.spec.McpSchema;
 import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicyFactory;
 import org.apache.shardingsphere.infra.metadata.identifier.DatabaseIdentifierContext;
+import org.apache.shardingsphere.mcp.api.capability.completion.MCPCompletionTargetDescriptor;
 import org.apache.shardingsphere.mcp.api.session.MCPSessionIdentity;
+import org.apache.shardingsphere.mcp.core.completion.MCPCompletionResult;
+import org.apache.shardingsphere.mcp.core.completion.MCPCompletionService;
 import org.apache.shardingsphere.mcp.core.context.MCPRuntimeContext;
 import org.apache.shardingsphere.mcp.core.session.MCPSessionManager;
 import org.apache.shardingsphere.mcp.core.workflow.InMemoryWorkflowSessionStore;
@@ -36,6 +39,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnaps
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowKind;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedConstruction;
 
 import java.time.Instant;
 import java.util.List;
@@ -46,7 +50,10 @@ import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
 import static org.mockito.Mockito.when;
 
 class MCPCompletionSpecificationFactoryTest {
@@ -193,6 +200,22 @@ class MCPCompletionSpecificationFactoryTest {
         assertThat(actual.completion().values(), is(List.of("plan-new", "plan-old")));
     }
     
+    @Test
+    void assertCompleteWithContextArguments() {
+        McpSchema.ResourceReference reference = new McpSchema.ResourceReference("shardingsphere://databases/{database}/schemas/{schema}/tables/{table}");
+        Map<String, String> contextArgs = Map.of("database", "foo_db", "schema", "foo_schema");
+        try (
+                MockedConstruction<MCPCompletionService> mocked = mockConstruction(MCPCompletionService.class,
+                        (mock, context) -> when(mock.complete(eq("session-1"), any(MCPCompletionTargetDescriptor.class), eq("table"), eq("foo"), eq(contextArgs)))
+                                .thenReturn(new MCPCompletionResult(List.of("foo_table"), 1, false, Map.of())))) {
+            SyncCompletionSpecification actualSpecification = findCompletion(new MCPCompletionSpecificationFactory(mock(MCPRuntimeContext.class)).createCompletionSpecifications(), reference);
+            McpSchema.CompleteResult actual = actualSpecification.completionHandler().apply(createExchange("session-1"),
+                    new McpSchema.CompleteRequest(reference, new McpSchema.CompleteRequest.CompleteArgument("table", "foo"), new McpSchema.CompleteRequest.CompleteContext(contextArgs)));
+            assertThat(actual.completion().values(), is(List.of("foo_table")));
+            assertThat(actual.completion().total(), is(1));
+        }
+    }
+    
     private MCPCompletionSpecificationFactory createFactory(final MCPRuntimeContext runtimeContext) {
         return new MCPCompletionSpecificationFactory(runtimeContext);
     }
diff --git a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/resource/MCPResourceSpecificationFactoryTest.java b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/resource/MCPResourceSpecificationFactoryTest.java
index 8cf70dbe4dc..ac06b5ef8cd 100644
--- a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/resource/MCPResourceSpecificationFactoryTest.java
+++ b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/resource/MCPResourceSpecificationFactoryTest.java
@@ -192,6 +192,25 @@ class MCPResourceSpecificationFactoryTest {
         assertNotNull(actualSpecification.readHandler());
     }
     
+    @Test
+    void assertCreateResourceTemplateSpecificationsWithAnnotations() {
+        MCPResourceDescriptor descriptor = new MCPResourceDescriptor("shardingsphere://foo/{database}", "foo_resource", "Foo Resource", "Foo description.", "application/json",
+                new MCPResourceAnnotations(List.of("user"), 0.6D, "2025-01-01T00:00:00Z"), Map.of("foo_key", "foo_value"));
+        try (MockedStatic<ResourceDefinitionRegistry> mocked = mockStatic(ResourceDefinitionRegistry.class)) {
+            mocked.when(ResourceDefinitionRegistry::getSupportedResourceDescriptors).thenReturn(List.of(descriptor));
+            mocked.when(() -> ResourceDefinitionRegistry.dispatch(any(MCPFeatureRuntimeRequestContext.class), eq("shardingsphere://foo/foo_db")))
+                    .thenReturn(Optional.of(new MCPMapPayload(Map.of("foo_field", "foo_value"))));
+            SyncResourceTemplateSpecification actualSpecification = new MCPResourceSpecificationFactory(createRuntimeContext()).createResourceTemplateSpecifications().getFirst();
+            assertThat(actualSpecification.resourceTemplate().annotations().priority(), is(0.6D));
+            assertThat(actualSpecification.resourceTemplate().meta(), is(Map.of("foo_key", "foo_value")));
+            ReadResourceResult actual = actualSpecification.readHandler().apply(createExchange(), new ReadResourceRequest("shardingsphere://foo/foo_db"));
+            assertThat(actual.contents().getFirst().uri(), is("shardingsphere://foo/foo_db"));
+            ArgumentCaptor<MCPFeatureRuntimeRequestContext> requestContextCaptor = ArgumentCaptor.forClass(MCPFeatureRuntimeRequestContext.class);
+            mocked.verify(() -> ResourceDefinitionRegistry.dispatch(requestContextCaptor.capture(), eq("shardingsphere://foo/foo_db")));
+            assertThat(requestContextCaptor.getValue().getSessionIdentity().getSessionId(), is("session-1"));
+        }
+    }
+    
     private SyncResourceSpecification findResourceSpecification(final Collection<SyncResourceSpecification> specifications, final String uri) {
         return specifications.stream().filter(each -> uri.equals(each.resource().uri())).findFirst().orElseThrow();
     }
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 075a95db4bc..e2323fe1479 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
@@ -19,7 +19,9 @@ package org.apache.shardingsphere.mcp.bootstrap.transport.capability.tool;
 
 import ch.qos.logback.classic.spi.ThrowableProxy;
 import io.modelcontextprotocol.server.McpServerFeatures.SyncToolSpecification;
+import io.modelcontextprotocol.server.McpSyncServerExchange;
 import io.modelcontextprotocol.spec.McpError;
+import io.modelcontextprotocol.spec.McpSchema;
 import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
 import io.modelcontextprotocol.spec.McpSchema.ErrorCodes;
 import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolAnnotations;
@@ -180,4 +182,22 @@ class MCPToolSpecificationFactoryTest extends AbstractMCPToolSpecificationFactor
             assertTrue(actual.isError());
         }
     }
+    
+    @Test
+    void assertCreateToolSpecificationsPreservesNestedArgumentsAfterClarification() {
+        try (MockedStatic<ToolDefinitionRegistry> mocked = mockStatic(ToolDefinitionRegistry.class)) {
+            String toolName = "database_gateway_plan_encrypt_rule";
+            MCPToolDefinition definition = mockSupportedTool(mocked,
+                    createPlanningToolDescriptor(toolName, Map.of("plan_id", Map.of("type", "string", "description", "Existing workflow plan identifier."))));
+            mocked.when(() -> ToolDefinitionRegistry.dispatch(any(MCPFeatureRuntimeRequestContext.class), eq(definition), any()))
+                    .thenReturn(new MCPMapPayload(createClarifyingPayload(createClarifyingQuestion("custom_properties.display-name", "string", false, "Provide display name."))),
+                            new MCPMapPayload(Map.of("status", "planned")));
+            McpSyncServerExchange exchange = createElicitationExchange(new McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of("field_1", "foo_display")));
+            SyncToolSpecification actualSpecification = new MCPToolSpecificationFactory(createRuntimeContext(MCPTransportType.STDIO)).createToolSpecifications().getFirst();
+            CallToolResult actual = actualSpecification.callHandler().apply(exchange, new McpSchema.CallToolRequest(toolName, Map.of("custom_properties", Map.of("foo_key", "foo_value"))));
+            assertThat(actual.structuredContent(), is(Map.of("status", "planned")));
+            mocked.verify(() -> ToolDefinitionRegistry.dispatch(any(MCPFeatureRuntimeRequestContext.class), eq(definition),
+                    eq(Map.of("plan_id", "plan-1", "custom_properties", Map.of("foo_key", "foo_value", "display-name", "foo_display")))));
+        }
+    }
 }
diff --git a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/SessionAttributionResolverTest.java b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/SessionAttributionResolverTest.java
index 4e907625891..31471b2b795 100644
--- a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/SessionAttributionResolverTest.java
+++ b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/SessionAttributionResolverTest.java
@@ -62,6 +62,19 @@ class SessionAttributionResolverTest {
         assertSessionIdentity(new SessionAttributionResolver(null).resolve(Map.of(), "session-1"), "", "", Map.of());
     }
     
+    @Test
+    void assertResolveFromRequestWithoutSubject() {
+        SessionAttributionResolver resolver = new SessionAttributionResolver(new SessionAttributionSourceConfiguration("X-Test-Subject", "X-Test-Source", "X-Test-Attr-"));
+        MCPSessionIdentity actual = resolver.resolve(mock(HttpServletRequest.class), "session-1");
+        assertSessionIdentity(actual, "", "", Map.of());
+    }
+    
+    @Test
+    void assertGetSummary() {
+        SessionAttributionResolver resolver = new SessionAttributionResolver(new SessionAttributionSourceConfiguration("X-Test-Subject", "X-Test-Source", "X-Test-Attr-"));
+        assertThat(resolver.getSummary(), is("trusted-header:X-Test-Subject"));
+    }
+    
     private void assertSessionIdentity(final MCPSessionIdentity actual, final String subject, final String source, final Map<String, String> attributes) {
         assertThat(actual.getSessionId(), is("session-1"));
         assertThat(actual.getSubject(), is(subject));
diff --git a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/stdio/SessionManagedStdioTransportProviderTest.java b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/stdio/SessionManagedStdioTransportProviderTest.java
index 896f40110f6..df48cac96bb 100644
--- a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/stdio/SessionManagedStdioTransportProviderTest.java
+++ b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/stdio/SessionManagedStdioTransportProviderTest.java
@@ -17,27 +17,42 @@
 
 package org.apache.shardingsphere.mcp.bootstrap.transport.server.stdio;
 
+import io.modelcontextprotocol.json.McpJsonMapper;
+import io.modelcontextprotocol.json.TypeRef;
+import io.modelcontextprotocol.spec.McpSchema.JSONRPCNotification;
 import io.modelcontextprotocol.spec.McpServerSession;
 import io.modelcontextprotocol.spec.McpServerTransport;
 import org.apache.shardingsphere.mcp.bootstrap.transport.MCPTransportConstants;
 import org.apache.shardingsphere.mcp.bootstrap.transport.MCPTransportJsonMapperFactory;
 import org.apache.shardingsphere.mcp.core.session.MCPSessionManager;
 import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import reactor.core.publisher.Flux;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
 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.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 class SessionManagedStdioTransportProviderTest {
@@ -154,6 +169,77 @@ class SessionManagedStdioTransportProviderTest {
         assertFalse(sessionManager.hasSession("session-id"));
     }
     
+    @Test
+    void assertSendMessage() throws IOException {
+        McpJsonMapper jsonMapper = mock(McpJsonMapper.class);
+        JSONRPCNotification message = new JSONRPCNotification("2.0", "foo_method", Map.of());
+        String expectedMessage = "{\"jsonrpc\":\"2.0\",\"method\":\"foo_method\",\"params\":{}}";
+        when(jsonMapper.writeValueAsString(message)).thenReturn(expectedMessage);
+        ByteArrayOutputStream actualOutput = new ByteArrayOutputStream();
+        try (PipedOutputStream inputWriter = new PipedOutputStream(); PipedInputStream input = new PipedInputStream(inputWriter); PrintStream output = new PrintStream(actualOutput)) {
+            InputStream originalInput = System.in;
+            PrintStream originalOutput = System.out;
+            SessionManagedStdioTransportProvider provider;
+            try {
+                System.setIn(input);
+                System.setOut(output);
+                provider = new SessionManagedStdioTransportProvider(new MCPSessionManager(Map.of()), jsonMapper);
+            } finally {
+                System.setIn(originalInput);
+                System.setOut(originalOutput);
+            }
+            McpServerSession.Factory sessionFactory = mock(McpServerSession.Factory.class);
+            McpServerSession session = mock(McpServerSession.class);
+            when(sessionFactory.create(any(McpServerTransport.class))).thenReturn(session);
+            when(session.getId()).thenReturn("foo_session");
+            provider.setSessionFactory(sessionFactory);
+            ArgumentCaptor<McpServerTransport> transportCaptor = ArgumentCaptor.forClass(McpServerTransport.class);
+            verify(sessionFactory).create(transportCaptor.capture());
+            try {
+                transportCaptor.getValue().sendMessage(message).block(Duration.ofSeconds(5L));
+                String expectedOutput = expectedMessage + "\n";
+                String actual = Flux.interval(Duration.ofMillis(10L)).map(ignored -> actualOutput.toString(StandardCharsets.UTF_8))
+                        .filter(expectedOutput::equals).next().block(Duration.ofSeconds(5L));
+                assertThat(actual, is(expectedOutput));
+                verify(jsonMapper).writeValueAsString(message);
+            } finally {
+                input.close();
+                transportCaptor.getValue().closeGracefully().block(Duration.ofSeconds(5L));
+            }
+        }
+    }
+    
+    @Test
+    void assertUnmarshalFrom() {
+        McpJsonMapper jsonMapper = mock(McpJsonMapper.class);
+        Map<String, Object> data = Map.of("foo_key", "foo_value");
+        TypeRef<Map<String, Object>> typeRef = new TypeRef<>() {
+        };
+        Map<String, Object> expected = Map.of("foo_result", "foo_value");
+        when(jsonMapper.convertValue(data, typeRef)).thenReturn(expected);
+        InputStream originalInput = System.in;
+        SessionManagedStdioTransportProvider provider;
+        try {
+            System.setIn(InputStream.nullInputStream());
+            provider = new SessionManagedStdioTransportProvider(new MCPSessionManager(Map.of()), jsonMapper);
+        } finally {
+            System.setIn(originalInput);
+        }
+        McpServerSession.Factory sessionFactory = mock(McpServerSession.Factory.class);
+        McpServerSession session = mock(McpServerSession.class);
+        when(sessionFactory.create(any(McpServerTransport.class))).thenReturn(session);
+        when(session.getId()).thenReturn("foo_session");
+        provider.setSessionFactory(sessionFactory);
+        ArgumentCaptor<McpServerTransport> transportCaptor = ArgumentCaptor.forClass(McpServerTransport.class);
+        verify(sessionFactory).create(transportCaptor.capture());
+        try {
+            Map<String, Object> actual = transportCaptor.getValue().unmarshalFrom(data, typeRef);
+            assertThat(actual, sameInstance(expected));
+        } finally {
+            transportCaptor.getValue().closeGracefully().block(Duration.ofSeconds(5L));
+        }
+    }
+    
     private SessionManagedStdioTransportProvider createProvider(final MCPSessionManager sessionManager) {
         InputStream originalInput = System.in;
         try {
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 6aa75d69d23..be15915f9ea 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
@@ -74,6 +74,17 @@ import static org.mockito.Mockito.when;
 
 class MCPCompletionServiceTest {
     
+    @Test
+    void assertCompleteWithoutSupportingHandler() {
+        MCPCompletionService completionService = createCompletionService(createRuntimeContext(mock(WorkflowSessionContext.class)), List.of());
+        MCPCompletionResult actual = completionService.complete("session-1", createDescriptor("foo_prompt", "value", 50), "value", "", Map.of());
+        assertTrue(actual.getValues().isEmpty());
+        assertThat(actual.getTotal(), is(0));
+        assertFalse(actual.hasMore());
+        assertThat(actual.getMeta().get(MCPShardingSphereMetadataKeys.DIAGNOSTIC), is("no_candidates"));
+        assertThat(actual.getMeta().get(MCPShardingSphereMetadataKeys.CANDIDATE_COUNT), is(0));
+    }
+    
     @Test
     void assertCompleteDatabaseValues() {
         MCPCompletionResult actual = complete(mock(WorkflowSessionContext.class), createDescriptor("inspect_metadata", "database", 1), "database", "", new LinkedHashMap<>());
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/handler/core/CoreHandlerProviderTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/handler/core/CoreHandlerProviderTest.java
index ad5d306f9e3..9c9733d35cd 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/handler/core/CoreHandlerProviderTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/handler/core/CoreHandlerProviderTest.java
@@ -17,22 +17,120 @@
 
 package org.apache.shardingsphere.mcp.core.handler.core;
 
+import org.apache.shardingsphere.infra.spi.ShardingSphereServiceLoader;
+import org.apache.shardingsphere.mcp.api.MCPHandlerProvider;
 import org.apache.shardingsphere.mcp.api.capability.completion.MCPCompletionHandler;
 import org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceHandler;
+import org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceURIVariables;
 import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolHandler;
 import org.apache.shardingsphere.mcp.core.completion.handler.MetadataCompletionHandler;
 import org.apache.shardingsphere.mcp.core.completion.handler.WorkflowPlanIdCompletionHandler;
+import org.apache.shardingsphere.mcp.core.resource.ResourceTestDataFactory;
+import org.apache.shardingsphere.mcp.core.resource.ResourceTestDataFactory.RequestContextFixture;
+import org.apache.shardingsphere.mcp.core.resource.handler.metadata.MetadataResourceHandler;
+import org.apache.shardingsphere.mcp.support.MCPFeatureRequestContext;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.resource.MCPUriTemplate;
 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.Collection;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 class CoreHandlerProviderTest {
     
+    private final CoreHandlerProvider provider = ShardingSphereServiceLoader.getServiceInstances(MCPHandlerProvider.class).stream()
+            .filter(CoreHandlerProvider.class::isInstance).map(CoreHandlerProvider.class::cast).findFirst().orElseThrow();
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("metadataResourceCases")
+    void assertGetResourceHandlersWithMetadata(final String name, final String template, final String uri, final String nameField, final Collection<String> expectedNames) {
+        MetadataResourceHandler actualHandler = (MetadataResourceHandler) provider.getResourceHandlers().stream()
+                .filter(handler -> template.equals(handler.getResourceUriTemplate())).findFirst().orElseThrow();
+        try (RequestContextFixture fixture = ResourceTestDataFactory.createRequestContextFixture(ResourceTestDataFactory.createDatabaseMetadata())) {
+            MCPResourceURIVariables variables = new MCPUriTemplate(template).parse(uri).orElseThrow();
+            Map<String, Object> actual = actualHandler.handle(fixture.getRequestContext(), variables).toPayload();
+            assertThat(actual.get("count"), is(expectedNames.size()));
+            assertThat(((Collection<?>) actual.get("items")).stream().map(item -> ((Map<?, ?>) item).get(nameField)).toList(), is(expectedNames));
+            assertThat(((Map<?, ?>) actual.get("self_resource")).get("uri"), is(uri));
+        }
+    }
+    
+    private static Stream<Arguments> metadataResourceCases() {
+        return Stream.of(
+                Arguments.of("databases", "shardingsphere://databases", "shardingsphere://databases", "database", List.of("logic_db", "runtime_db", "warehouse")),
+                Arguments.of("database", "shardingsphere://databases/{database}", "shardingsphere://databases/logic_db", "database", List.of("logic_db")),
+                Arguments.of("schemas", "shardingsphere://databases/{database}/schemas", "shardingsphere://databases/logic_db/schemas", "schema", List.of("public")),
+                Arguments.of("schema", "shardingsphere://databases/{database}/schemas/{schema}", "shardingsphere://databases/logic_db/schemas/public", "schema", List.of("public")),
+                Arguments.of("sequences", "shardingsphere://databases/{database}/schemas/{schema}/sequences", "shardingsphere://databases/runtime_db/schemas/public/sequences", "sequence",
+                        List.of("order_seq")),
+                Arguments.of("sequence", "shardingsphere://databases/{database}/schemas/{schema}/sequences/{sequence}",
+                        "shardingsphere://databases/runtime_db/schemas/public/sequences/order_seq", "sequence", List.of("order_seq")),
+                Arguments.of("tables", "shardingsphere://databases/{database}/schemas/{schema}/tables", "shardingsphere://databases/logic_db/schemas/public/tables", "table", List.of("order_items",
+                        "orders")),
+                Arguments.of("table", "shardingsphere://databases/{database}/schemas/{schema}/tables/{table}", "shardingsphere://databases/logic_db/schemas/public/tables/orders", "table",
+                        List.of("orders")),
+                Arguments.of("views", "shardingsphere://databases/{database}/schemas/{schema}/views", "shardingsphere://databases/logic_db/schemas/public/views", "view", List.of("orders_view")),
+                Arguments.of("view", "shardingsphere://databases/{database}/schemas/{schema}/views/{view}", "shardingsphere://databases/logic_db/schemas/public/views/orders_view", "view",
+                        List.of("orders_view")),
+                Arguments.of("table columns", "shardingsphere://databases/{database}/schemas/{schema}/tables/{table}/columns",
+                        "shardingsphere://databases/logic_db/schemas/public/tables/orders/columns", "column", List.of("order_id")),
+                Arguments.of("table column", "shardingsphere://databases/{database}/schemas/{schema}/tables/{table}/columns/{column}",
+                        "shardingsphere://databases/logic_db/schemas/public/tables/orders/columns/order_id", "column", List.of("order_id")),
+                Arguments.of("view columns", "shardingsphere://databases/{database}/schemas/{schema}/views/{view}/columns",
+                        "shardingsphere://databases/logic_db/schemas/public/views/orders_view/columns", "column", List.of("order_id")),
+                Arguments.of("view column", "shardingsphere://databases/{database}/schemas/{schema}/views/{view}/columns/{column}",
+                        "shardingsphere://databases/logic_db/schemas/public/views/orders_view/columns/order_id", "column", List.of("order_id")),
+                Arguments.of("indexes", "shardingsphere://databases/{database}/schemas/{schema}/tables/{table}/indexes", "shardingsphere://databases/logic_db/schemas/public/tables/orders/indexes",
+                        "index", List.of("order_idx")),
+                Arguments.of("index", "shardingsphere://databases/{database}/schemas/{schema}/tables/{table}/indexes/{index}",
+                        "shardingsphere://databases/logic_db/schemas/public/tables/orders/indexes/order_idx", "index", List.of("order_idx")));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("governanceResourceCases")
+    void assertGetResourceHandlersWithGovernanceMetadata(final String name, final String template, final String uri, final String sql, final Map<String, Object> expectedRow) {
+        MCPFeatureRequestContext requestContext = mock(MCPFeatureRequestContext.class);
+        MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+        when(requestContext.getQueryFacade()).thenReturn(queryFacade);
+        when(queryFacade.query("foo_db", sql)).thenReturn(List.of(expectedRow));
+        MetadataResourceHandler actualHandler = (MetadataResourceHandler) provider.getResourceHandlers().stream()
+                .filter(handler -> template.equals(handler.getResourceUriTemplate())).findFirst().orElseThrow();
+        Map<String, Object> actual = actualHandler.handle(requestContext, new MCPUriTemplate(template).parse(uri).orElseThrow()).toPayload();
+        assertThat(actual.get("items"), is(List.of(expectedRow)));
+        assertThat(((Map<?, ?>) actual.get("self_resource")).get("uri"), is(uri));
+        verify(queryFacade).query("foo_db", sql);
+    }
+    
+    private static Stream<Arguments> governanceResourceCases() {
+        return Stream.of(
+                Arguments.of("storage units", "shardingsphere://databases/{database}/storage-units", "shardingsphere://databases/foo_db/storage-units",
+                        "SHOW STORAGE UNITS FROM foo_db", Map.of("name", "foo_ds")),
+                Arguments.of("storage unit", "shardingsphere://databases/{database}/storage-units/{storageUnit}", "shardingsphere://databases/foo_db/storage-units/foo_ds",
+                        "SHOW STORAGE UNITS FROM foo_db", Map.of("name", "foo_ds")),
+                Arguments.of("storage unit rules", "shardingsphere://databases/{database}/storage-units/{storageUnit}/used-by-rules",
+                        "shardingsphere://databases/foo_db/storage-units/foo_ds/used-by-rules",
+                        "SHOW RULES USED STORAGE UNIT foo_ds FROM foo_db", Map.of("name", "foo_rule")),
+                Arguments.of("single tables", "shardingsphere://databases/{database}/single-tables", "shardingsphere://databases/foo_db/single-tables",
+                        "SHOW SINGLE TABLES FROM foo_db", Map.of("table_name", "foo_table")),
+                Arguments.of("single table", "shardingsphere://databases/{database}/single-tables/{table}", "shardingsphere://databases/foo_db/single-tables/foo_table",
+                        "SHOW SINGLE TABLE foo_table FROM foo_db", Map.of("table_name", "foo_table")),
+                Arguments.of("default single storage unit", "shardingsphere://databases/{database}/single-table/default-storage-unit",
+                        "shardingsphere://databases/foo_db/single-table/default-storage-unit",
+                        "SHOW DEFAULT SINGLE TABLE STORAGE UNIT FROM foo_db", Map.of("storage_unit_name", "foo_ds")));
+    }
+    
     @Test
     void assertGetToolHandlers() {
         Collection<MCPToolHandler<?>> actual = new CoreHandlerProvider().getToolHandlers();
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/metadata/GovernanceMetadataQueryServiceTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/metadata/GovernanceMetadataQueryServiceTest.java
index d1620567fe3..c6f41d795fb 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/metadata/GovernanceMetadataQueryServiceTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/metadata/GovernanceMetadataQueryServiceTest.java
@@ -19,9 +19,13 @@ package org.apache.shardingsphere.mcp.core.metadata;
 
 import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
 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.List;
 import java.util.Map;
+import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -32,6 +36,27 @@ class GovernanceMetadataQueryServiceTest {
     
     private final GovernanceMetadataQueryService service = new GovernanceMetadataQueryService();
     
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("storageUnitAttributeCases")
+    void assertQueryStorageUnitsWithOtherAttributes(final String name, final Object attributes, final Object expected) {
+        MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+        when(queryFacade.query("foo_db", "SHOW STORAGE UNITS FROM foo_db")).thenReturn(List.of(Map.of("name", "foo_ds", "other_attributes", attributes)));
+        List<Map<String, Object>> actual = service.queryStorageUnits(queryFacade, "foo_db");
+        assertThat(actual, is(List.of(Map.of("name", "foo_ds", "other_attributes", expected))));
+    }
+    
+    private static Stream<Arguments> storageUnitAttributeCases() {
+        return Stream.of(
+                Arguments.of("nested collections", List.of(Map.of("PASSWORD", "foo_password"), List.of(Map.of("pool", 8))), List.of(Map.of("PASSWORD", "******"), List.of(Map.of("pool", 8)))),
+                Arguments.of("JSON array", "[{\"secret\":\"foo_secret\"},7]", List.of(Map.of("secret", "******"), 7)),
+                Arguments.of("ordinary text", "foo_pool=8", "foo_pool=8"),
+                Arguments.of("sensitive text", "PASSWORD=foo_password", "******"),
+                Arguments.of("malformed sensitive JSON", "{\"token\":", "******"),
+                Arguments.of("malformed ordinary JSON", "{\"pool\":", "{\"pool\":"),
+                Arguments.of("ordinary scalar", 8, 8),
+                Arguments.of("ordinary map", Map.of("pool", 8), Map.of("pool", 8)));
+    }
+    
     @Test
     void assertQueryStorageUnits() {
         MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverterTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverterTest.java
index 825877711a4..0a721d0bd64 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverterTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverterTest.java
@@ -61,6 +61,7 @@ import java.sql.SQLTransientConnectionException;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Stream;
 
@@ -73,6 +74,66 @@ import static org.mockito.Mockito.when;
 
 class MCPErrorConverterTest {
     
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("argumentContractRecoveryCases")
+    void assertConvertArgumentContractWithoutSafeRetry(final String name, final String category, final String expectedType, final String expectedMessage, final String expectedModelAction) {
+        Map<String, Object> actual = MCPErrorConverter.convert(new MCPToolArgumentContractViolationException("foo_tool", "foo_field", category, expectedType, List.of(), Map.of())).toPayload();
+        assertThat(actual.get("summary"), is(expectedMessage));
+        Map<?, ?> actualRecovery = (Map<?, ?>) actual.get("recovery");
+        assertThat(actualRecovery.get("category"), is(category));
+        assertThat(actualRecovery.get("field"), is("foo_field"));
+        assertThat(actualRecovery.get("model_action"), is(expectedModelAction));
+        assertThat(Objects.toString(actualRecovery.get("expected_type"), ""), is(expectedType));
+        assertFalse(actualRecovery.containsKey("allowed_values"));
+        assertFalse(actualRecovery.containsKey("suggested_arguments"));
+        Map<?, ?> actualNextAction = (Map<?, ?>) ((List<?>) actual.get("next_actions")).getFirst();
+        assertThat(actualNextAction.get("type"), is("ask_user"));
+        assertThat(actualNextAction.get("required_inputs"), is(List.of("foo_field")));
+    }
+    
+    private static Stream<Arguments> argumentContractRecoveryCases() {
+        return Stream.of(
+                Arguments.of("unknown argument", "unknown_argument", "", "foo_field is not a supported argument for foo_tool.",
+                        "Remove arguments that are not declared by the tool inputSchema before retrying."),
+                Arguments.of("invalid argument type", "invalid_argument_type", "integer", "foo_field must be an integer.", "Retry with a value that matches the declared inputSchema type."),
+                Arguments.of("general schema mismatch", "invalid_argument", "", "foo_field does not match the input schema for foo_tool.",
+                        "Retry with a value that matches the declared inputSchema type."));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("missingArgumentRecoveryCases")
+    void assertConvertMissingArgumentRecovery(final String name, final String field, final String expectedResource, final String expectedAction) {
+        Map<String, Object> actual = MCPErrorConverter.convert(new MCPMissingToolArgumentException(field)).toPayload();
+        Map<?, ?> actualRecovery = (Map<?, ?>) actual.get("recovery");
+        assertThat(actualRecovery.get("category"), is("missing_argument"));
+        assertThat(actualRecovery.get("missing_fields"), is(List.of(field)));
+        Collection<?> actualResources = (Collection<?>) actualRecovery.get("resources_to_read");
+        assertThat(actualResources.isEmpty(), is(expectedResource.isEmpty()));
+        Map<?, ?> actualNextAction = (Map<?, ?>) ((List<?>) actual.get("next_actions")).getFirst();
+        assertThat(actualNextAction.get("type"), is(expectedAction));
+        assertThat(Objects.toString(actualNextAction.get("resource_uri"), ""), is(expectedResource));
+    }
+    
+    private static Stream<Arguments> missingArgumentRecoveryCases() {
+        return Stream.of(
+                Arguments.of("workflow identifier", "plan_id", "shardingsphere://capabilities", "resource_read"),
+                Arguments.of("SQL text", "sql", "", "ask_user"),
+                Arguments.of("schema name", "schema", "", "ask_user"));
+    }
+    
+    @Test
+    void assertConvertInvalidUpdateExecutionMode() {
+        Map<String, Object> actual = MCPErrorConverter.convert(new MCPInvalidExecutionModeException("database_gateway_execute_update", List.of("preview", "execute"),
+                Map.of("sql", "UPDATE foo SET id=1", "execution_mode", "preview"))).toPayload();
+        Map<?, ?> actualRecovery = (Map<?, ?>) actual.get("recovery");
+        assertThat(actualRecovery.get("model_action"), is("Retry with execution_mode=preview or execution_mode=execute."));
+        assertThat(actualRecovery.get("allowed_values"), is(List.of("preview", "execute")));
+        assertThat(actualRecovery.get("suggested_arguments"), is(Map.of("sql", "UPDATE foo SET id=1", "execution_mode", "preview")));
+        Map<?, ?> actualNextAction = (Map<?, ?>) ((List<?>) actual.get("next_actions")).getFirst();
+        assertThat(actualNextAction.get("tool_name"), is("database_gateway_execute_update"));
+        assertThat(actualNextAction.get("arguments"), is(Map.of("sql", "UPDATE foo SET id=1", "execution_mode", "preview")));
+    }
+    
     @ParameterizedTest(name = "{0}")
     @MethodSource("assertConvertCases")
     void assertConvert(final String name, final Throwable cause, final String expectedMessage) {
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/MCPResourceControllerTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/MCPResourceControllerTest.java
index 583b983a86b..4c1b7586ae7 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/MCPResourceControllerTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/MCPResourceControllerTest.java
@@ -62,6 +62,16 @@ class MCPResourceControllerTest {
         }
     }
     
+    @Test
+    void assertHandleWithEmptyResourceURI() {
+        try (MockedStatic<ResourceDefinitionRegistry> mocked = mockStatic(ResourceDefinitionRegistry.class)) {
+            mocked.when(() -> ResourceDefinitionRegistry.dispatch(any(MCPFeatureRuntimeRequestContext.class), eq(""))).thenReturn(Optional.empty());
+            UnsupportedResourceUriException actual = assertThrows(UnsupportedResourceUriException.class, () -> createController().handle("session-1", ""));
+            assertThat(actual.getResourceUri(), is(""));
+            assertThat(actual.getMessage(), is("Unsupported resource URI."));
+        }
+    }
+    
     @Test
     void assertHandleWithHandlerException() {
         try (MockedStatic<ResourceDefinitionRegistry> mocked = mockStatic(ResourceDefinitionRegistry.class)) {
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 bc7e2a3653e..551b319c10d 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
@@ -22,6 +22,7 @@ import org.apache.shardingsphere.infra.spi.ShardingSphereServiceLoader;
 import org.apache.shardingsphere.mcp.api.MCPHandlerProvider;
 import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolAnnotations;
 import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolDescriptor;
+import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolHandler;
 import org.apache.shardingsphere.mcp.api.exception.MCPInvalidRequestException;
 import org.apache.shardingsphere.mcp.api.payload.MCPSuccessPayload;
 import org.apache.shardingsphere.mcp.api.session.MCPSessionIdentity;
@@ -35,27 +36,171 @@ import org.apache.shardingsphere.mcp.core.protocol.exception.MCPToolArgumentCont
 import org.apache.shardingsphere.mcp.core.protocol.exception.UnsupportedToolException;
 import org.apache.shardingsphere.mcp.core.resource.ResourceTestDataFactory;
 import org.apache.shardingsphere.mcp.core.resource.ResourceTestDataFactory.RequestContextFixture;
+import org.apache.shardingsphere.mcp.support.MCPFeatureRequestContext;
 import org.apache.shardingsphere.mcp.support.security.MCPRuntimeProtectionPolicy;
 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 org.mockito.MockedStatic;
 import org.mockito.internal.configuration.plugins.Plugins;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.InvocationTargetException;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.isA;
+import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 class ToolDefinitionRegistryTest {
     
+    @Test
+    void assertGetToolDefinitionWithEmptyName() {
+        UnsupportedToolException actual = assertThrows(UnsupportedToolException.class, () -> ToolDefinitionRegistry.getToolDefinition(""));
+        assertThat(actual.getToolName(), is(""));
+        assertThat(actual.getMessage(), is("Unsupported tool."));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("registrationFailureCases")
+    @SuppressWarnings("unchecked")
+    void assertGetSupportedToolDescriptorsWithInvalidRegistration(final String name, final Collection<String> toolNames, final String expectedMessage) {
+        Collection<MCPToolHandler<?>> handlers = new ArrayList<>(toolNames.size());
+        for (String each : toolNames) {
+            MCPToolHandler<MCPFeatureRequestContext> handler = mock(MCPToolHandler.class);
+            when(handler.getToolName()).thenReturn(each);
+            if (!each.isEmpty()) {
+                when(handler.getContextType()).thenReturn(MCPFeatureRequestContext.class);
+            }
+            handlers.add(handler);
+        }
+        MCPHandlerProvider handlerProvider = mock(MCPHandlerProvider.class);
+        when(handlerProvider.getToolHandlers()).thenReturn(handlers);
+        try (MockedStatic<ShardingSphereServiceLoader> mocked = mockStatic(ShardingSphereServiceLoader.class)) {
+            mocked.when(() -> ShardingSphereServiceLoader.getServiceInstances(MCPHandlerProvider.class)).thenReturn(List.of(handlerProvider));
+            Class<?> registryClass = assertDoesNotThrow(() -> Class.forName(ToolDefinitionRegistry.class.getName(), false, createIsolatedToolDefinitionRegistryClassLoader()));
+            InvocationTargetException actual = assertThrows(InvocationTargetException.class, () -> Plugins.getMemberAccessor().invoke(registryClass.getMethod("getSupportedToolDescriptors"), null));
+            assertThat(actual.getCause(), isA(ExceptionInInitializerError.class));
+            assertThat(actual.getCause().getCause().getMessage(), is(String.format(expectedMessage, handlers.iterator().next().getClass().getName())));
+        }
+    }
+    
+    private static Stream<Arguments> registrationFailureCases() {
+        return Stream.of(
+                Arguments.of("empty tool name", List.of(""), "Tool name is required for `%1$s`."),
+                Arguments.of("duplicate tool name", List.of("database_gateway_search_metadata", "database_gateway_search_metadata"),
+                        "Duplicate tool name `database_gateway_search_metadata` with `%1$s` and `%1$s`."),
+                Arguments.of("descriptor without handler", List.of("database_gateway_search_metadata"), "MCP tool descriptor `database_gateway_validate_runtime_database` has no registered handler."));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("validArgumentCases")
+    @SuppressWarnings("unchecked")
+    void assertDispatchWithSchemaValue(final String name, final Map<String, Object> property, final Object value) {
+        Map<String, Object> schema = Map.of("type", "object", "properties", Map.of("foo_value", property), "additionalProperties", true);
+        MCPToolDescriptor descriptor = mock(MCPToolDescriptor.class);
+        when(descriptor.getName()).thenReturn("foo_tool");
+        when(descriptor.getInputSchema()).thenReturn(schema);
+        MCPToolHandler<MCPFeatureRequestContext> handler = mock(MCPToolHandler.class);
+        when(handler.getContextType()).thenReturn(MCPFeatureRequestContext.class);
+        MCPFeatureRuntimeRequestContext context = mock(MCPFeatureRuntimeRequestContext.class);
+        Map<String, Object> args = Map.of("foo_value", value, "foo_extra", "foo_text");
+        MCPSuccessPayload expected = mock(MCPSuccessPayload.class);
+        when(handler.handle(context, args)).thenReturn(expected);
+        MCPSuccessPayload actual = ToolDefinitionRegistry.dispatch(context, new MCPToolDefinition(descriptor, handler), args);
+        assertThat(actual, sameInstance(expected));
+        verify(handler).handle(context, args);
+    }
+    
+    private static Stream<Arguments> validArgumentCases() {
+        return Stream.of(
+                Arguments.of("byte integer", Map.of("type", "integer"), (byte) 1),
+                Arguments.of("short integer", Map.of("type", "integer"), (short) 2),
+                Arguments.of("long integer", Map.of("type", "integer"), 3L),
+                Arguments.of("big integer", Map.of("type", "integer", "minimum", 0, "maximum", 10), BigInteger.TEN),
+                Arguments.of("number", Map.of("type", "number"), 1.5D),
+                Arguments.of("array without item schema", Map.of("type", "array"), List.of("foo_text")),
+                Arguments.of("additional property schema", Map.of("type", "object", "additionalProperties", Map.of("type", "string")), Map.of("foo_key", "foo_text")),
+                Arguments.of("schema without type", Map.of("enum", List.of("foo_text")), "foo_text"));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("integerRangeCases")
+    void assertDispatchWithIntegerRangeViolation(final String name, final Map<String, Object> property, final Object value, final int expectedSuggestion) {
+        MCPToolDescriptor descriptor = mock(MCPToolDescriptor.class);
+        when(descriptor.getName()).thenReturn("foo_tool");
+        when(descriptor.getInputSchema()).thenReturn(Map.of("type", "object", "properties", Map.of("foo_value", property), "additionalProperties", false));
+        MCPInvalidToolArgumentException actual = assertThrows(MCPInvalidToolArgumentException.class,
+                () -> ToolDefinitionRegistry.dispatch(mock(MCPFeatureRuntimeRequestContext.class), new MCPToolDefinition(descriptor, mock(MCPToolHandler.class)), Map.of("foo_value", value)));
+        assertThat(actual.getArgumentPath(), is("foo_value"));
+        assertThat(actual.getSuggestedValue(), is(expectedSuggestion));
+        assertThat(actual.getMinimumValue(), is(0));
+        assertThat(actual.getMaximumValue(), is(10));
+    }
+    
+    private static Stream<Arguments> integerRangeCases() {
+        return Stream.of(
+                Arguments.of("below minimum", Map.of("type", "integer", "minimum", 0, "maximum", 10), -1, 0),
+                Arguments.of("above maximum", Map.of("type", "integer", "minimum", 0, "maximum", 10), BigInteger.valueOf(Long.MAX_VALUE), 10),
+                Arguments.of("valid default", Map.of("type", "integer", "minimum", 0, "maximum", 10, "default", 5), 11L, 5));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("requiredNestedArgumentCases")
+    void assertDispatchWithInvalidRequiredNestedArgument(final String name, final Map<String, Object> property, final Object value, final String expectedPath) {
+        MCPToolDescriptor descriptor = mock(MCPToolDescriptor.class);
+        when(descriptor.getName()).thenReturn("foo_tool");
+        when(descriptor.getInputSchema()).thenReturn(Map.of("type", "object", "properties", Map.of("foo_value", property), "required", List.of("foo_value"), "additionalProperties", false));
+        MCPToolArgumentContractViolationException actual = assertThrows(MCPToolArgumentContractViolationException.class,
+                () -> ToolDefinitionRegistry.dispatch(mock(MCPFeatureRuntimeRequestContext.class), new MCPToolDefinition(descriptor, mock(MCPToolHandler.class)), Map.of("foo_value", value)));
+        assertThat(actual.getArgumentPath(), is(expectedPath));
+        assertThat(actual.getCategory(), is("invalid_argument_type"));
+        assertTrue(actual.getSuggestedArguments().isEmpty());
+    }
+    
+    private static Stream<Arguments> requiredNestedArgumentCases() {
+        return Stream.of(
+                Arguments.of("array item", Map.of("type", "array", "items", Map.of("type", "integer")), List.of("foo_text"), "foo_value[0]"),
+                Arguments.of("object in array", Map.of("type", "array", "items", Map.of("type", "object", "properties", Map.of("foo_key", Map.of("type", "boolean")))),
+                        List.of(Map.of("foo_key", "foo_text")), "foo_value[0].foo_key"),
+                Arguments.of("array in object", Map.of("type", "object", "properties", Map.of("foo_key", Map.of("type", "array", "items", Map.of("type", "number")))),
+                        Map.of("foo_key", List.of("foo_text")), "foo_value.foo_key[0]"));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("safeExecutionModeCases")
+    void assertDispatchWithMissingExecutionModeSuggestion(final String name, final Map<String, Object> property, final Map<String, Object> expectedSuggestion) {
+        MCPToolDescriptor descriptor = mock(MCPToolDescriptor.class);
+        when(descriptor.getName()).thenReturn("foo_tool");
+        when(descriptor.getInputSchema()).thenReturn(Map.of("type", "object", "properties", Map.of("execution_mode", property), "required", List.of("execution_mode"), "additionalProperties", false));
+        MCPExecutionModeRequiredException actual = assertThrows(MCPExecutionModeRequiredException.class,
+                () -> ToolDefinitionRegistry.dispatch(mock(MCPFeatureRuntimeRequestContext.class), new MCPToolDefinition(descriptor, mock(MCPToolHandler.class)), Map.of()));
+        assertThat(actual.getSuggestedArguments(), is(expectedSuggestion));
+    }
+    
+    private static Stream<Arguments> safeExecutionModeCases() {
+        return Stream.of(
+                Arguments.of("manual review", Map.of("type", "string", "enum", List.of("execute", "manual-only")), Map.of("execution_mode", "manual-only")),
+                Arguments.of("first declared mode", Map.of("type", "string", "enum", List.of("execute")), Map.of("execution_mode", "execute")),
+                Arguments.of("no declared mode", Map.of("type", "string"), Map.of()));
+    }
+    
     @Test
     void assertGetSupportedToolDescriptors() {
         List<MCPToolDescriptor> actual = ToolDefinitionRegistry.getSupportedToolDescriptors();
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteUpdateToolHandlerTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteUpdateToolHandlerTest.java
index 3be679d3ecf..d95054d57ee 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteUpdateToolHandlerTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteUpdateToolHandlerTest.java
@@ -22,6 +22,7 @@ import org.apache.shardingsphere.mcp.api.exception.MCPUnsupportedException;
 import org.apache.shardingsphere.mcp.api.payload.MCPSuccessPayload;
 import org.apache.shardingsphere.mcp.api.session.MCPSessionIdentity;
 import org.apache.shardingsphere.mcp.core.protocol.exception.MCPInvalidToolArgumentException;
+import org.apache.shardingsphere.mcp.core.tool.request.MCPToolArguments;
 import org.apache.shardingsphere.mcp.support.MCPFeatureRequestContext;
 import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapability;
 import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPStatement;
@@ -29,25 +30,60 @@ import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureCapabilityFa
 import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureExecutionFacade;
 import org.apache.shardingsphere.mcp.support.database.tool.request.SQLExecutionRequest;
 import org.apache.shardingsphere.mcp.support.database.tool.result.SQLExecutionResult;
+import org.apache.shardingsphere.mcp.support.descriptor.CoreToolNames;
 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 org.mockito.ArgumentCaptor;
+import org.mockito.MockedStatic;
 
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.stream.Stream;
 
 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.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
 class ExecuteUpdateToolHandlerTest {
     
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("previewSideEffectCases")
+    void assertHandlePreviewSideEffectScope(final String name, final String sql, final SupportedMCPStatement statementClass, final String statementType, final String savepointName,
+                                            final String expectedClass, final String expectedScope) {
+        MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
+        MCPFeatureRequestContext requestContext = mock(MCPFeatureRequestContext.class);
+        when(requestContext.getExecutionFacade()).thenReturn(executionFacade);
+        ClassificationResult classificationResult = new ClassificationResult(statementClass, statementType, sql, savepointName, List.of(), false);
+        try (MockedStatic<SQLExecutionToolHandlerSupport> mocked = mockStatic(SQLExecutionToolHandlerSupport.class)) {
+            mocked.when(() -> SQLExecutionToolHandlerSupport.analyze(eq(requestContext), any(MCPToolArguments.class), eq(sql))).thenReturn(classificationResult);
+            mocked.when(() -> SQLExecutionToolHandlerSupport.checkExecutionArguments(any(MCPToolArguments.class), eq(CoreToolNames.EXECUTE_UPDATE))).thenCallRealMethod();
+            mocked.when(() -> SQLExecutionToolHandlerSupport.isQueryStatement(any(ClassificationResult.class))).thenCallRealMethod();
+            Map<String, Object> actual = new ExecuteUpdateToolHandler().handle(requestContext, Map.of("database", "logic_db", "sql", sql, "execution_mode", "preview")).toPayload();
+            assertThat(actual.get("statement_class"), is(expectedClass));
+            assertThat(actual.get("side_effect_scope"), is(List.of(expectedScope)));
+            assertFalse((boolean) actual.get("would_execute"));
+            verifyNoInteractions(executionFacade);
+        }
+    }
+    
+    private static Stream<Arguments> previewSideEffectCases() {
+        return Stream.of(
+                Arguments.of("privilege metadata", "GRANT SELECT ON foo_table TO foo_user", SupportedMCPStatement.DCL, "GRANT", "", "dcl", "privilege-metadata"),
+                Arguments.of("transaction state", "BEGIN", SupportedMCPStatement.TRANSACTION_CONTROL, "BEGIN", "", "transaction_control", "transaction-state"),
+                Arguments.of("savepoint state", "SAVEPOINT foo_savepoint", SupportedMCPStatement.SAVEPOINT, "SAVEPOINT", "foo_savepoint", "savepoint", "transaction-state"));
+    }
+    
     @Test
     void assertHandleUpdateStatement() {
         MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
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 d95abe9b1d6..fdb2995e0fc 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
@@ -51,6 +51,8 @@ import java.sql.SQLSyntaxErrorException;
 import java.sql.SQLTimeoutException;
 import java.sql.SQLTransientConnectionException;
 import java.sql.Statement;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -60,6 +62,7 @@ import java.util.stream.Stream;
 
 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.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -72,6 +75,87 @@ import static org.mockito.Mockito.when;
 
 class MCPJdbcStatementExecutorTest {
     
+    @Test
+    void assertExecuteWithOwnedConnectionStateFailure() throws SQLException {
+        RuntimeDatabaseConfiguration databaseConfig = mock(RuntimeDatabaseConfiguration.class);
+        IllegalStateException cause = new IllegalStateException("foo connection state failed");
+        when(databaseConfig.openConnection("foo_db")).thenThrow(cause);
+        MCPJdbcStatementExecutor executor = new MCPJdbcStatementExecutor(Map.of("foo_db", databaseConfig), mock(MCPJdbcTransactionResourceManager.class));
+        SQLExecutionRequest request = new SQLExecutionRequest("foo_session", "foo_db", "", "SELECT 1", 0, 0);
+        ClassificationResult classification = new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT 1", "", List.of(), false);
+        MCPTransactionStateException actual = assertThrows(MCPTransactionStateException.class,
+                () -> executor.execute(request, classification, createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
+        assertThat(actual.getMessage(), is(cause.getMessage()));
+        assertThat(actual.getCause(), sameInstance(cause));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("readOnlyFailureCases")
+    void assertExecuteReadOnlyWithFailure(final String name, final boolean executionFailure, final boolean rollbackFailure, final boolean autoCommitFailure, final boolean readOnlyFailure,
+                                          final String expectedMessage, final Collection<String> expectedSuppressed) throws SQLException {
+        Statement statement = mock(Statement.class);
+        Connection connection = createStatementConnection(statement);
+        when(connection.getAutoCommit()).thenReturn(true);
+        if (executionFailure) {
+            when(statement.execute("SELECT 1")).thenThrow(new SQLException("execution failed"));
+        } else {
+            when(statement.execute("SELECT 1")).thenReturn(true);
+            ResultSet resultSet = createResultSet(createColumns(), List.of(List.of(1, "foo_status")));
+            when(statement.getResultSet()).thenReturn(resultSet);
+        }
+        if (rollbackFailure) {
+            doThrow(new SQLException("rollback failed")).when(connection).rollback();
+        }
+        if (autoCommitFailure) {
+            doThrow(new SQLException("autoCommit failed")).when(connection).setAutoCommit(true);
+        }
+        if (readOnlyFailure) {
+            doThrow(new SQLException("readOnly failed")).when(connection).setReadOnly(false);
+        }
+        RuntimeDatabaseConfiguration databaseConfig = mock(RuntimeDatabaseConfiguration.class);
+        when(databaseConfig.openConnection("foo_db")).thenReturn(connection);
+        MCPJdbcStatementExecutor executor = new MCPJdbcStatementExecutor(Map.of("foo_db", databaseConfig), mock(MCPJdbcTransactionResourceManager.class));
+        SQLExecutionRequest request = new SQLExecutionRequest("foo_session", "foo_db", "", "SELECT 1", 0, 0, true);
+        ClassificationResult classification = new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT 1", "", List.of(), false);
+        MCPDatabaseQueryFailedException actual = assertThrows(MCPDatabaseQueryFailedException.class,
+                () -> executor.execute(request, classification, createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
+        assertThat(actual.getCause().getMessage(), is(expectedMessage));
+        assertThat(Arrays.stream(actual.getCause().getSuppressed()).map(Throwable::getMessage).toList(), is(expectedSuppressed));
+        verify(connection).rollback();
+        verify(connection).setAutoCommit(true);
+        verify(connection).setReadOnly(false);
+        verify(connection).close();
+    }
+    
+    private static Stream<Arguments> readOnlyFailureCases() {
+        return Stream.of(
+                Arguments.of("execution failure", true, false, false, false, "execution failed", List.of()),
+                Arguments.of("rollback failure", false, true, false, false, "rollback failed", List.of()),
+                Arguments.of("autoCommit restoration failure", false, false, true, false, "autoCommit failed", List.of()),
+                Arguments.of("readOnly restoration failure", false, false, false, true, "readOnly failed", List.of()),
+                Arguments.of("execution and cleanup failures", true, true, true, true, "execution failed", List.of("rollback failed", "autoCommit failed", "readOnly failed")));
+    }
+    
+    @Test
+    void assertExecuteReadOnlyWithAutoCommitAlreadyDisabled() throws SQLException {
+        Statement statement = mock(Statement.class);
+        when(statement.execute("SELECT 1")).thenReturn(true);
+        ResultSet resultSet = createResultSet(createColumns(), List.of(List.of(1, "foo_status")));
+        when(statement.getResultSet()).thenReturn(resultSet);
+        Connection connection = createStatementConnection(statement);
+        RuntimeDatabaseConfiguration databaseConfig = mock(RuntimeDatabaseConfiguration.class);
+        when(databaseConfig.openConnection("foo_db")).thenReturn(connection);
+        MCPJdbcStatementExecutor executor = new MCPJdbcStatementExecutor(Map.of("foo_db", databaseConfig), mock(MCPJdbcTransactionResourceManager.class));
+        SQLExecutionResult actual = executor.execute(new SQLExecutionRequest("foo_session", "foo_db", "", "SELECT 1", 0, 0, true),
+                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT 1", "", List.of(), false), createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE));
+        assertThat(actual.getRows(), is(List.of(List.of(1, "foo_status"))));
+        verify(connection).rollback();
+        verify(connection, never()).setAutoCommit(true);
+        verify(connection, never()).setAutoCommit(false);
+        verify(connection).setReadOnly(false);
+        verify(connection).close();
+    }
+    
     @ParameterizedTest(name = "{0}")
     @MethodSource("assertExecuteCases")
     void assertExecute(final String name, final SQLExecutionRequest executionRequest, final ClassificationResult classificationResult, final Connection connection,
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionResourceManagerTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionResourceManagerTest.java
index 64b303ccee5..7e6a87d6646 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionResourceManagerTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionResourceManagerTest.java
@@ -46,6 +46,36 @@ import static org.mockito.Mockito.when;
 
 class MCPJdbcTransactionResourceManagerTest {
     
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("commitCleanupFailureCases")
+    void assertCommitTransactionWithCleanupFailure(final String name, final boolean resetFailure, final boolean closeFailure, final String expectedMessage,
+                                                   final int expectedSuppressed) throws SQLException {
+        Connection connection = mock(Connection.class);
+        RuntimeDatabaseConfiguration databaseConfig = mock(RuntimeDatabaseConfiguration.class);
+        when(databaseConfig.openConnection("foo_db")).thenReturn(connection);
+        MCPJdbcTransactionResourceManager manager = new MCPJdbcTransactionResourceManager(Map.of("foo_db", databaseConfig));
+        manager.beginTransaction("foo_session", "foo_db");
+        if (resetFailure) {
+            doThrow(new SQLException("reset failed")).when(connection).setAutoCommit(true);
+        }
+        if (closeFailure) {
+            doThrow(new SQLException("close failed")).when(connection).close();
+        }
+        IllegalStateException actual = assertThrows(IllegalStateException.class, () -> manager.commitTransaction("foo_session"));
+        assertThat(actual.getMessage(), is(expectedMessage));
+        assertThat(actual.getCause().getSuppressed().length, is(expectedSuppressed));
+        assertTrue(manager.findTransactionConnection("foo_session", "foo_db").isEmpty());
+        verify(connection).commit();
+        verify(connection).close();
+    }
+    
+    private static Stream<Arguments> commitCleanupFailureCases() {
+        return Stream.of(
+                Arguments.of("reset failure", true, false, "reset failed", 0),
+                Arguments.of("close failure", false, true, "close failed", 0),
+                Arguments.of("reset and close failure", true, true, "reset failed", 1));
+    }
+    
     @ParameterizedTest(name = "{0}")
     @MethodSource("assertFindTransactionConnectionCases")
     void assertFindTransactionConnection(final String name,
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 6013dff935d..07e1ac4b561 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
@@ -48,11 +48,34 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 class MCPJdbcTransactionStatementExecutorTest {
     
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("unsupportedTransactionCases")
+    void assertExecuteWithUnsupportedTransactionCapability(final String name, final SupportedMCPStatement statementClass, final String statementType, final String expectedMessage) {
+        MCPSessionManager sessionManager = mock(MCPSessionManager.class);
+        when(sessionManager.hasSession("foo_session")).thenReturn(true);
+        MCPDatabaseCapability capability = mock(MCPDatabaseCapability.class);
+        ClassificationResult classification = new ClassificationResult(statementClass, statementType, statementType, "foo_savepoint", List.of(), false);
+        MCPUnsupportedException actual = assertThrows(MCPUnsupportedException.class,
+                () -> new MCPJdbcTransactionStatementExecutor(sessionManager).execute("foo_session", "foo_db", capability, classification));
+        assertThat(actual.getMessage(), is(expectedMessage));
+        verify(sessionManager, never()).getTransactionResourceManager();
+    }
+    
+    private static Stream<Arguments> unsupportedTransactionCases() {
+        return Stream.of(
+                Arguments.of("begin", SupportedMCPStatement.TRANSACTION_CONTROL, "BEGIN", "Transaction control is not supported."),
+                Arguments.of("commit", SupportedMCPStatement.TRANSACTION_CONTROL, "COMMIT", "Transaction control is not supported."),
+                Arguments.of("rollback", SupportedMCPStatement.TRANSACTION_CONTROL, "ROLLBACK", "Transaction control is not supported."),
+                Arguments.of("rollback savepoint", SupportedMCPStatement.SAVEPOINT, "ROLLBACK TO SAVEPOINT", "Savepoint is not supported."),
+                Arguments.of("release savepoint", SupportedMCPStatement.SAVEPOINT, "RELEASE SAVEPOINT", "Savepoint is not supported."));
+    }
+    
     @ParameterizedTest(name = "{0}")
     @MethodSource("assertExecuteCases")
     void assertExecute(final String name, final String sql, final SupportedMCPStatement statementClass, final String expectedStatementType,
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPSQLExecutionFacadeTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPSQLExecutionFacadeTest.java
index d1502f48eeb..89959bcf41b 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPSQLExecutionFacadeTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPSQLExecutionFacadeTest.java
@@ -17,12 +17,19 @@
 
 package org.apache.shardingsphere.mcp.core.tool.handler.execute;
 
+import org.apache.shardingsphere.database.connector.core.metadata.database.enums.NullsOrderType;
 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.spi.DatabaseTypedSPILoader;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
 import org.apache.shardingsphere.infra.metadata.identifier.DatabaseIdentifierContext;
+import org.apache.shardingsphere.infra.parser.ShardingSphereSQLParserEngine;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 import org.apache.shardingsphere.mcp.api.exception.MCPInvalidRequestException;
 import org.apache.shardingsphere.mcp.api.exception.MCPQueryFailedException;
+import org.apache.shardingsphere.mcp.api.exception.MCPUnsupportedException;
 import org.apache.shardingsphere.mcp.core.protocol.exception.MCPBannedSQLStatementException;
+import org.apache.shardingsphere.mcp.core.protocol.exception.MCPUnsupportedSQLStatementException;
 import org.apache.shardingsphere.mcp.core.session.MCPSessionExecutionCoordinator;
 import org.apache.shardingsphere.mcp.core.session.MCPSessionNotExistedException;
 import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapability;
@@ -33,11 +40,108 @@ import org.apache.shardingsphere.mcp.support.database.exception.DatabaseCapabili
 import org.apache.shardingsphere.mcp.support.database.exception.StatementClassNotSupportedException;
 import org.apache.shardingsphere.mcp.support.database.tool.request.SQLExecutionRequest;
 import org.apache.shardingsphere.mcp.support.database.tool.result.SQLExecutionResult;
+import org.apache.shardingsphere.sql.parser.spi.SQLStatementVisitorFacade;
+import org.apache.shardingsphere.sql.parser.statement.core.enums.AggregationType;
+import org.apache.shardingsphere.sql.parser.statement.core.enums.CombineType;
+import org.apache.shardingsphere.sql.parser.statement.core.enums.OrderDirection;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.ddl.index.IndexNameSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.ddl.index.IndexSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.ddl.routine.FunctionNameSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.ReturningSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.assignment.ColumnAssignmentSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.assignment.InsertValuesSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.assignment.SetAssignmentSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.OnDuplicateKeyColumnsSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.combine.CombineSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.datetime.DatetimeExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.exec.ExecSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.BetweenExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.BinaryOperationExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.CaseWhenExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.CollateExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.ExpressionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.ExpressionWithParamsSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.FunctionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.InExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.IntervalExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.IntervalUnit;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.KeyValueSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.ListExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.NotExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.QuantifySubqueryExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.RowExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.TypeCastExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.UnaryOperationExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.ValuesExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.complex.CommonExpressionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.simple.LiteralExpressionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.subquery.SubqueryExpressionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.subquery.SubquerySegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.interval.IntervalDayToSecondExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.AggregationProjectionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.DatetimeProjectionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ExpressionProjectionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.IntervalExpressionProjection;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ProjectionsSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ShorthandProjectionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.SubqueryProjectionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.merge.MergeWhenAndThenSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.GroupBySegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.OrderBySegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.item.ExpressionOrderByItemSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.pagination.limit.ExpressionLimitValueSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.pagination.limit.LimitSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate.HavingSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate.HierarchicalQuerySegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate.WhereSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.table.MultiTableConditionalIntoElseSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.table.MultiTableConditionalIntoSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.table.MultiTableConditionalIntoThenSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.table.MultiTableConditionalIntoWhenThenSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.table.MultiTableInsertIntoSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.table.MultiTableInsertType;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.xml.XmlElementFunctionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.xml.XmlPiFunctionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.xml.XmlQueryAndExistsFunctionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.xml.XmlSerializeFunctionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.xml.XmlTableColumnSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.xml.XmlTableFunctionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.xml.XmlTableOptionsSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.ModelColumnSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.ModelSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.OutputSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.OwnerSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.WindowItemSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.WindowSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.match.MatchAgainstExpression;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.CollectionTableSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.DeleteMultiTableSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.FunctionTableSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.JoinTableSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.SimpleTableSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.SubqueryTableSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.TableNameSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dal.EmptyStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.database.AlterDatabaseStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.database.DropDatabaseStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.index.AlterIndexStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.view.AlterViewStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.view.CreateMaterializedViewStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.DeleteStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.InsertStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.MergeStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.SelectStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.UpdateStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
 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 org.mockito.ArgumentCaptor;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
 
 import java.sql.SQLException;
 import java.sql.SQLSyntaxErrorException;
@@ -45,22 +149,583 @@ import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.hasItems;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
 class MCPSQLExecutionFacadeTest {
     
+    @ParameterizedTest(name = "{0}")
+    @MethodSource({"parsedNamespaceStatements", "parsedExpressionStatements", "parsedXMLStatements"})
+    void assertExecuteWithParsedCrossSchemaObjects(final String name, final String databaseType, final String sql, final SQLStatement statement, final String expectedObject) {
+        MCPDatabaseCapabilityProvider capabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
+        MCPSessionExecutionCoordinator coordinator = mock(MCPSessionExecutionCoordinator.class);
+        MCPJdbcTransactionStatementExecutor transactionExecutor = mock(MCPJdbcTransactionStatementExecutor.class);
+        MCPJdbcStatementExecutor statementExecutor = mock(MCPJdbcStatementExecutor.class);
+        MCPDatabaseCapability capability = createCapability(Set.of(SupportedMCPStatement.QUERY, SupportedMCPStatement.DML, SupportedMCPStatement.DDL, SupportedMCPStatement.DCL));
+        when(capability.getDatabaseType()).thenReturn(databaseType);
+        when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
+        mockSessionLock(coordinator);
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor);
+        try (
+                MockedConstruction<ShardingSphereSQLParserEngine> ignored = mockConstruction(ShardingSphereSQLParserEngine.class,
+                        (mock, context) -> when(mock.parse(anyString(), eq(false))).thenReturn(statement))) {
+            MCPInvalidRequestException actual = assertThrows(MCPInvalidRequestException.class, () -> facade.execute(createExecutionRequest(sql)));
+            assertThat(actual.getMessage(), is(String.format("Cross-schema SQL is not supported for database `logic_db`: `%s`.", expectedObject)));
+            verifyNoInteractions(transactionExecutor, statementExecutor);
+        }
+    }
+    
+    private static Stream<Arguments> parsedNamespaceStatements() {
+        DatabaseType oracle = TypedSPILoader.getService(DatabaseType.class, "Oracle");
+        CreateMaterializedViewStatement materializedView = new CreateMaterializedViewStatement(oracle);
+        materializedView.setView(createTable("logic_db", "foo_view"));
+        materializedView.setSelect(selectExpression(oracle, createForeignFunction()));
+        IndexSegment index = new IndexSegment(0, 0, new IndexNameSegment(0, 0, new IdentifierValue("foo_index")));
+        index.setOwner(new OwnerSegment(0, 0, new IdentifierValue("other_db")));
+        DatabaseType postgresql = TypedSPILoader.getService(DatabaseType.class, "PostgreSQL");
+        AlterDatabaseStatement alterDatabase = new AlterDatabaseStatement(postgresql);
+        alterDatabase.setDatabaseName("other_db");
+        alterDatabase.setRenameDatabaseName("foo_new_database");
+        AlterViewStatement renameView = new AlterViewStatement(postgresql);
+        renameView.setView(createTable("other_db", "foo_view"));
+        renameView.setRenameView(new SimpleTableSegment(new TableNameSegment(0, 0, new IdentifierValue("foo_new_view"))));
+        DatabaseType sqlserver = TypedSPILoader.getService(DatabaseType.class, "SQLServer");
+        AlterViewStatement redefineView = new AlterViewStatement(sqlserver);
+        redefineView.setView(createTable("logic_db", "foo_view"));
+        redefineView.setSelect(SelectStatement.builder().databaseType(sqlserver).projections(starProjection()).from(createTable("other_db", "foo_table")).build());
+        DeleteMultiTableSegment deleteTables = new DeleteMultiTableSegment();
+        deleteTables.getActualDeleteTables().add(createTable("logic_db", "foo_orders"));
+        JoinTableSegment relation = new JoinTableSegment();
+        relation.setLeft(createTable("logic_db", "foo_orders"));
+        DatabaseType mysql = TypedSPILoader.getService(DatabaseType.class, "MySQL");
+        relation.setRight(new SubqueryTableSegment(0, 0, new SubquerySegment(0, 0,
+                SelectStatement.builder().databaseType(mysql).projections(starProjection()).from(createTable("other_db", "foo_table")).build(), "SELECT * FROM other_db.foo_table")));
+        relation.setCondition(new LiteralExpressionSegment(0, 0, true));
+        deleteTables.setRelationTable(relation);
+        IndexSegment renameIndex = new IndexSegment(0, 0, new IndexNameSegment(0, 0, new IdentifierValue("foo_new_index")));
+        return Stream.of(
+                Arguments.of("materialized view body", "Oracle", "CREATE MATERIALIZED VIEW logic_db.foo_view AS SELECT other_db.foo_read() FROM dual", materializedView, "other_db.foo_read"),
+                Arguments.of("alter and rename index", "PostgreSQL", "ALTER INDEX other_db.foo_index RENAME TO foo_new_index", new AlterIndexStatement(postgresql, index, renameIndex, null),
+                        "other_db.foo_index"),
+                Arguments.of("alter and rename database", "PostgreSQL", "ALTER DATABASE other_db RENAME TO foo_new_database", alterDatabase, "other_db"),
+                Arguments.of("drop database", "PostgreSQL", "DROP DATABASE other_db", new DropDatabaseStatement(postgresql, "other_db", false), "other_db"),
+                Arguments.of("rename view", "PostgreSQL", "ALTER VIEW other_db.foo_view RENAME TO foo_new_view", renameView, "other_db.foo_view"),
+                Arguments.of("redefine view", "SQLServer", "ALTER VIEW logic_db.foo_view AS SELECT * FROM other_db.foo_table", redefineView, "other_db.foo_table"),
+                Arguments.of("delete via subquery relation", "MySQL", "DELETE logic_db.foo_orders FROM logic_db.foo_orders JOIN (SELECT * FROM other_db.foo_table) foo_source ON 1=1",
+                        DeleteStatement.builder().databaseType(mysql).table(deleteTables).build(), "other_db.foo_table"));
+    }
+    
+    private static SimpleTableSegment createTable(final String owner, final String name) {
+        SimpleTableSegment result = new SimpleTableSegment(new TableNameSegment(0, 0, new IdentifierValue(name)));
+        result.setOwner(new OwnerSegment(0, 0, new IdentifierValue(owner)));
+        return result;
+    }
+    
+    private static SelectStatement selectExpression(final DatabaseType databaseType, final ExpressionSegment expression) {
+        ProjectionsSegment projections = new ProjectionsSegment(0, 0);
+        projections.getProjections().add(new ExpressionProjectionSegment(0, 0, expression.getText(), expression));
+        return SelectStatement.builder().databaseType(databaseType).projections(projections).build();
+    }
+    
+    private static FunctionSegment createForeignFunction() {
+        FunctionSegment result = new FunctionSegment(0, 0, "foo_read", "other_db.foo_read()");
+        result.setOwner(new OwnerSegment(0, 0, new IdentifierValue("other_db")));
+        return result;
+    }
+    
+    private static ProjectionsSegment starProjection() {
+        ProjectionsSegment result = new ProjectionsSegment(0, 0);
+        result.getProjections().add(new ShorthandProjectionSegment(0, 0));
+        return result;
+    }
+    
+    private static Stream<Arguments> parsedExpressionStatements() {
+        DatabaseType oracle = TypedSPILoader.getService(DatabaseType.class, "Oracle");
+        FunctionSegment function = createForeignFunction();
+        KeyValueSegment keyValue = new KeyValueSegment(0, 0, "'foo':other_db.foo_read()");
+        keyValue.setKey(new LiteralExpressionSegment(0, 0, "foo"));
+        keyValue.setValue(function);
+        FunctionSegment json = new FunctionSegment(0, 0, "JSON_OBJECT", "JSON_OBJECT('foo':other_db.foo_read())");
+        json.getParameters().add(keyValue);
+        MatchAgainstExpression match = new MatchAgainstExpression(0, 0, function, "", "MATCH(foo_content) AGAINST(other_db.foo_read())");
+        match.getColumns().add(new ColumnSegment(0, 0, new IdentifierValue("foo_content")));
+        SubquerySegment subquery = new SubquerySegment(0, 0, selectExpression(oracle, function), "SELECT other_db.foo_read() FROM dual");
+        ProjectionsSegment subqueryProjections = new ProjectionsSegment(0, 0);
+        subqueryProjections.getProjections().add(new SubqueryProjectionSegment(subquery, "(SELECT other_db.foo_read() FROM dual)"));
+        LiteralExpressionSegment literal = new LiteralExpressionSegment(0, 0, 1);
+        ListExpression list = new ListExpression(0, 0);
+        list.getItems().add(function);
+        RowExpression row = new RowExpression(0, 0, "(other_db.foo_read(), 1)");
+        row.getItems().add(function);
+        row.getItems().add(literal);
+        RowExpression comparisonRow = new RowExpression(0, 0, "(1, 1)");
+        comparisonRow.getItems().addAll(List.of(literal, literal));
+        FunctionSegment datetimeFunction = new FunctionSegment(0, 0, "COALESCE", "COALESCE(other_db.foo_read() AT TIME ZONE 'UTC', NULL)");
+        datetimeFunction.getParameters().add(new DatetimeExpression(0, 0, function, new ExpressionProjectionSegment(0, 0, "'UTC'", new LiteralExpressionSegment(0, 0, "UTC")), "AT TIME ZONE"));
+        datetimeFunction.getParameters().add(new LiteralExpressionSegment(0, 0, null));
+        ValuesExpression values = new ValuesExpression(0, 0);
+        values.getRowConstructorList().add(new InsertValuesSegment(0, 0, List.of(function)));
+        DatabaseType postgresql = TypedSPILoader.getService(DatabaseType.class, "PostgreSQL");
+        DatabaseType mysql = TypedSPILoader.getService(DatabaseType.class, "MySQL");
+        DatabaseType sqlserver = TypedSPILoader.getService(DatabaseType.class, "SQLServer");
+        return Stream.of(
+                Arguments.of("JSON key value", "SQLServer", "SELECT JSON_OBJECT('foo':other_db.foo_read())", selectExpression(sqlserver, json), "other_db.foo_read"),
+                Arguments.of("match expression", "MySQL", "SELECT MATCH(foo_content) AGAINST(other_db.foo_read()) FROM foo_documents", selectExpression(mysql, match), "other_db.foo_read"),
+                Arguments.of("scalar subquery projection", "Oracle", "SELECT (SELECT other_db.foo_read() FROM dual) FROM dual",
+                        SelectStatement.builder().databaseType(oracle).projections(subqueryProjections).build(), "other_db.foo_read"),
+                Arguments.of("scalar subquery expression", "Oracle", "SELECT 1 + (SELECT other_db.foo_read() FROM dual) FROM dual",
+                        selectExpression(oracle, new BinaryOperationExpression(0, 0, new LiteralExpressionSegment(0, 0, 1), new SubqueryExpressionSegment(subquery), "+", "1 + subquery")),
+                        "other_db.foo_read"),
+                Arguments.of("collection table expression", "Oracle", "SELECT * FROM TABLE(other_db.foo_read())",
+                        SelectStatement.builder().databaseType(oracle).projections(starProjection()).from(new CollectionTableSegment(function)).build(), "other_db.foo_read"),
+                Arguments.of("between expression", "PostgreSQL", "SELECT 1 BETWEEN other_db.foo_read() AND 3",
+                        selectExpression(postgresql, new BetweenExpression(0, 0, literal, function, literal, false)), "other_db.foo_read"),
+                Arguments.of("in expression", "PostgreSQL", "SELECT 1 IN (other_db.foo_read())", selectExpression(postgresql, new InExpression(0, 0, literal, list, false)), "other_db.foo_read"),
+                Arguments.of("not expression", "PostgreSQL", "SELECT NOT other_db.foo_read()", selectExpression(postgresql, new NotExpression(0, 0, function, false)), "other_db.foo_read"),
+                Arguments.of("type cast expression", "PostgreSQL", "SELECT CAST(other_db.foo_read() AS INTEGER)",
+                        selectExpression(postgresql, new TypeCastExpression(0, 0, "CAST", function, "INTEGER")), "other_db.foo_read"),
+                Arguments.of("collate expression", "MySQL", "SELECT other_db.foo_read() COLLATE utf8_bin",
+                        selectExpression(mysql, new CollateExpression(0, 0, new LiteralExpressionSegment(0, 0, "utf8_bin"), function)), "other_db.foo_read"),
+                Arguments.of("datetime expression", "Oracle", "SELECT COALESCE(other_db.foo_read() AT TIME ZONE 'UTC', NULL) FROM dual",
+                        selectExpression(oracle, datetimeFunction), "other_db.foo_read"),
+                Arguments.of("interval expression", "MySQL", "SELECT INTERVAL other_db.foo_read() DAY", selectExpression(mysql, new IntervalExpression(0, 0, function, IntervalUnit.DAY, "INTERVAL")),
+                        "other_db.foo_read"),
+                Arguments.of("unary expression", "MySQL", "SELECT -other_db.foo_read()", selectExpression(mysql, new UnaryOperationExpression(0, 0, function, "-", "-other_db.foo_read()")),
+                        "other_db.foo_read"),
+                Arguments.of("row expression", "MySQL", "SELECT (other_db.foo_read(), 1) = (1, 1)",
+                        selectExpression(mysql, new BinaryOperationExpression(0, 0, row, comparisonRow, "=", "tuple comparison")), "other_db.foo_read"),
+                Arguments.of("values expression", "MySQL", "SELECT * FROM (VALUES ROW(other_db.foo_read())) foo_values",
+                        SelectStatement.builder().databaseType(mysql).projections(starProjection()).from(new SubqueryTableSegment(0, 0,
+                                new SubquerySegment(0, 0, selectExpression(mysql, values), "VALUES ROW(other_db.foo_read())"))).build(),
+                        "other_db.foo_read"),
+                Arguments.of("case expression", "PostgreSQL", "SELECT CASE other_db.foo_read() WHEN 1 THEN 1 ELSE 1 END",
+                        selectExpression(postgresql, new CaseWhenExpression(0, 0, function, List.of(literal), List.of(literal), literal, "CASE")), "other_db.foo_read"),
+                Arguments.of("quantified subquery", "MySQL", "SELECT 1 = ANY (SELECT other_db.foo_read())",
+                        selectExpression(mysql, new BinaryOperationExpression(0, 0, literal,
+                                new QuantifySubqueryExpression(0, 0, new SubquerySegment(0, 0, selectExpression(mysql, function), "SELECT other_db.foo_read()"), "ANY"), "=", "quantified comparison")),
+                        "other_db.foo_read"));
+    }
+    
+    private static Stream<Arguments> parsedXMLStatements() {
+        DatabaseType oracle = TypedSPILoader.getService(DatabaseType.class, "Oracle");
+        FunctionSegment function = createForeignFunction();
+        LiteralExpressionSegment literal = new LiteralExpressionSegment(0, 0, "foo");
+        XmlElementFunctionSegment element = new XmlElementFunctionSegment(0, 0, "XMLELEMENT", new IdentifierValue("foo_node"), "XMLELEMENT");
+        element.getXmlAttributes().add(function);
+        element.getParameters().add(literal);
+        XmlQueryAndExistsFunctionSegment query = new XmlQueryAndExistsFunctionSegment(0, 0, "XMLQUERY", "'/root'", "XMLQUERY");
+        query.getParameters().add(function);
+        XmlTableOptionsSegment options = new XmlTableOptionsSegment(0, 0, "PASSING other_db.foo_read() COLUMNS foo_value VARCHAR2(100) PATH '/root' DEFAULT 'foo'");
+        options.getParameters().add(function);
+        options.getXmlTableColumnSegments().add(new XmlTableColumnSegment(0, 0, "foo_value", "VARCHAR2(100)", "'/root'", literal, "foo_value"));
+        XmlTableFunctionSegment table = new XmlTableFunctionSegment(0, 0, "XMLTABLE", null, "'/root'", options, "XMLTABLE");
+        return Stream.of(
+                Arguments.of("XML attributes", "Oracle", "SELECT XMLELEMENT(foo_node, XMLATTRIBUTES(other_db.foo_read()), 'foo') FROM dual", selectExpression(oracle, element), "other_db.foo_read"),
+                Arguments.of("XML evaluated name", "Oracle", "SELECT XMLPI(EVALNAME other_db.foo_read(), 'foo') FROM dual",
+                        selectExpression(oracle, new XmlPiFunctionSegment(0, 0, "XMLPI", function, literal, "XMLPI")), "other_db.foo_read"),
+                Arguments.of("XML static name", "Oracle", "SELECT XMLPI(NAME foo_node, other_db.foo_read()) FROM dual",
+                        selectExpression(oracle, new XmlPiFunctionSegment(0, 0, "XMLPI", "foo_node", function, "XMLPI")), "other_db.foo_read"),
+                Arguments.of("XML query parameters", "Oracle", "SELECT XMLQUERY('/root' PASSING other_db.foo_read() RETURNING CONTENT) FROM dual", selectExpression(oracle, query),
+                        "other_db.foo_read"),
+                Arguments.of("XML serialize parameters", "Oracle", "SELECT XMLSERIALIZE(CONTENT other_db.foo_read() AS VARCHAR2(100)) FROM dual",
+                        selectExpression(oracle, new XmlSerializeFunctionSegment(0, 0, "XMLSERIALIZE", function, "VARCHAR2(100)", null, null, null, "XMLSERIALIZE")), "other_db.foo_read"),
+                Arguments.of("XML table parameters", "Oracle", "SELECT * FROM XMLTABLE('/root' PASSING other_db.foo_read() COLUMNS foo_value VARCHAR2(100) PATH '/root' DEFAULT 'foo')",
+                        SelectStatement.builder().databaseType(oracle).projections(starProjection()).from(new FunctionTableSegment(0, 0, table)).build(), "other_db.foo_read"));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource({"parsedSelectClauses", "parsedDMLClauses", "parsedMergeClauses"})
+    void assertExecuteWithParsedClauseReferences(final String name, final String databaseType, final String sql, final SQLStatement statement, final Set<String> expectedObjects) {
+        MCPDatabaseCapabilityProvider capabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
+        MCPSessionExecutionCoordinator coordinator = mock(MCPSessionExecutionCoordinator.class);
+        MCPJdbcStatementExecutor statementExecutor = mock(MCPJdbcStatementExecutor.class);
+        MCPDatabaseCapability capability = createCapability(Set.of(SupportedMCPStatement.QUERY, SupportedMCPStatement.DML, SupportedMCPStatement.DDL));
+        when(capability.getDatabaseType()).thenReturn(databaseType);
+        when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
+        mockSessionLock(coordinator);
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, mock(MCPJdbcTransactionStatementExecutor.class), statementExecutor);
+        SQLExecutionRequest request = createExecutionRequest(sql);
+        SQLExecutionResult expectedResult = mock(SQLExecutionResult.class);
+        when(statementExecutor.execute(eq(request), any(ClassificationResult.class), eq(capability))).thenReturn(expectedResult);
+        try (
+                MockedConstruction<ShardingSphereSQLParserEngine> ignored = mockConstruction(ShardingSphereSQLParserEngine.class,
+                        (mock, context) -> when(mock.parse(anyString(), eq(false))).thenReturn(statement))) {
+            assertThat(facade.execute(request), sameInstance(expectedResult));
+            ArgumentCaptor<ClassificationResult> actual = ArgumentCaptor.forClass(ClassificationResult.class);
+            verify(statementExecutor).execute(eq(request), actual.capture(), eq(capability));
+            assertThat(actual.getValue().getReferencedObjects().stream().map(SQLStatementObjectName::getObjectName).collect(Collectors.toSet()), hasItems(expectedObjects.toArray(new String[0])));
+        }
+    }
+    
+    private static Stream<Arguments> parsedSelectClauses() {
+        WindowItemSegment item = new WindowItemSegment(0, 0);
+        item.setPartitionListSegments(List.of(createCurrentFunction("foo_partition")));
+        item.setOrderBySegment(expressionOrder(createCurrentFunction("foo_order")));
+        item.setFrameClause(new CommonExpressionSegment(0, 0, "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"));
+        AggregationProjectionSegment aggregation = new AggregationProjectionSegment(0, 0, AggregationType.SUM, "SUM(logic_db.foo_value())");
+        aggregation.getParameters().add(createCurrentFunction("foo_value"));
+        aggregation.setWindow(item);
+        ProjectionsSegment aggregationProjections = new ProjectionsSegment(0, 0);
+        aggregationProjections.getProjections().add(aggregation);
+        WindowSegment window = new WindowSegment(0, 0);
+        window.getItemSegments().add(item);
+        DatabaseType oracle = TypedSPILoader.getService(DatabaseType.class, "Oracle");
+        ModelSegment model = new ModelSegment(0, 0);
+        model.getReferenceModelSelects().add(new SubquerySegment(0, 0, selectExpression(oracle, createCurrentFunction("foo_reference")), "SELECT logic_db.foo_reference() FROM dual"));
+        model.getCellAssignmentSelects().add(new SubquerySegment(0, 0, selectExpression(oracle, createCurrentFunction("foo_cell")), "SELECT logic_db.foo_cell() FROM dual"));
+        model.getOrderBySegments().add(expressionOrder(createCurrentFunction("foo_model_order")));
+        model.getPartitionColumns().add(new ModelColumnSegment(0, 0, createCurrentFunction("foo_model_partition")));
+        model.getDimensionColumns().add(new ModelColumnSegment(0, 0, createCurrentFunction("foo_dimension")));
+        model.getMeasureColumns().add(new ModelColumnSegment(0, 0, createCurrentFunction("foo_measure")));
+        model.getCellAssignmentColumns().add(new ColumnSegment(0, 0, new IdentifierValue("foo_measure")));
+        ProjectionsSegment datetime = new ProjectionsSegment(0, 0);
+        datetime.getProjections().add(new DatetimeProjectionSegment(0, 0, createCurrentFunction("foo_datetime"),
+                new ExpressionProjectionSegment(0, 0, "'UTC'", new LiteralExpressionSegment(0, 0, "UTC")), "AT TIME ZONE"));
+        ProjectionsSegment interval = new ProjectionsSegment(0, 0);
+        IntervalExpressionProjection intervalProjection = new IntervalExpressionProjection(0, 0, createCurrentFunction("foo_interval_left"), null,
+                createCurrentFunction("foo_interval_right"), "INTERVAL DAY TO SECOND");
+        intervalProjection.setDayToSecondExpression(new IntervalDayToSecondExpression(0, 0, "DAY", "TO", "SECOND"));
+        interval.getProjections().add(intervalProjection);
+        DatabaseType postgresql = TypedSPILoader.getService(DatabaseType.class, "PostgreSQL");
+        LimitSegment limit = new LimitSegment(0, 0, new ExpressionLimitValueSegment(0, 0, createCurrentFunction("foo_offset")),
+                new ExpressionLimitValueSegment(0, 0, createCurrentFunction("foo_limit")));
+        return Stream.of(
+                Arguments.of("aggregate window", "PostgreSQL",
+                        "SELECT SUM(logic_db.foo_value()) OVER (PARTITION BY logic_db.foo_partition() ORDER BY logic_db.foo_order() ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)",
+                        SelectStatement.builder().databaseType(postgresql).projections(aggregationProjections).build(), Set.of("logic_db.foo_value", "logic_db.foo_partition", "logic_db.foo_order")),
+                Arguments.of("window clauses", "PostgreSQL",
+                        "SELECT 1 WINDOW foo_window AS (PARTITION BY logic_db.foo_partition() ORDER BY logic_db.foo_order() ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)",
+                        SelectStatement.builder().databaseType(postgresql).projections(starProjection()).window(window).build(), Set.of("logic_db.foo_partition", "logic_db.foo_order")),
+                Arguments.of("pagination expressions", "PostgreSQL", "SELECT 1 LIMIT logic_db.foo_limit() OFFSET logic_db.foo_offset()",
+                        SelectStatement.builder().databaseType(postgresql).projections(starProjection()).limit(limit).build(), Set.of("logic_db.foo_offset", "logic_db.foo_limit")),
+                Arguments.of("model clauses", "Oracle", "SELECT * FROM dual MODEL REFERENCE foo_ref ON (SELECT logic_db.foo_reference() FROM dual) DIMENSION BY (foo_key) MEASURES (foo_value) "
+                        + "MAIN foo_main PARTITION BY (logic_db.foo_model_partition()) DIMENSION BY (logic_db.foo_dimension() foo_key) MEASURES (logic_db.foo_measure() foo_measure) "
+                        + "RULES (foo_measure[FOR foo_key IN (SELECT logic_db.foo_cell() FROM dual)] ORDER BY logic_db.foo_model_order() = 1)",
+                        SelectStatement.builder().databaseType(oracle).projections(starProjection()).model(model).build(),
+                        Set.of("logic_db.foo_reference", "logic_db.foo_cell", "logic_db.foo_model_order", "logic_db.foo_model_partition", "logic_db.foo_dimension", "logic_db.foo_measure")),
+                Arguments.of("datetime projections", "Oracle", "SELECT logic_db.foo_datetime() AT TIME ZONE 'UTC' FROM dual",
+                        SelectStatement.builder().databaseType(oracle).projections(datetime).build(), Set.of("logic_db.foo_datetime")),
+                Arguments.of("interval projections", "Oracle", "SELECT (logic_db.foo_interval_left() - logic_db.foo_interval_right()) DAY TO SECOND FROM dual",
+                        SelectStatement.builder().databaseType(oracle).projections(interval).build(), Set.of("logic_db.foo_interval_left", "logic_db.foo_interval_right")));
+    }
+    
+    private static FunctionSegment createCurrentFunction(final String name) {
+        FunctionSegment result = new FunctionSegment(0, 0, name, "logic_db." + name + "()");
+        result.setOwner(new OwnerSegment(0, 0, new IdentifierValue("logic_db")));
+        return result;
+    }
+    
+    private static OrderBySegment expressionOrder(final ExpressionSegment expression) {
+        return new OrderBySegment(0, 0, List.of(new ExpressionOrderByItemSegment(0, 0, expression.getText(), OrderDirection.ASC, NullsOrderType.FIRST, expression)));
+    }
+    
+    private static Stream<Arguments> parsedDMLClauses() {
+        DatabaseType mysql = TypedSPILoader.getService(DatabaseType.class, "MySQL");
+        DatabaseType postgresql = TypedSPILoader.getService(DatabaseType.class, "PostgreSQL");
+        DatabaseType sqlserver = TypedSPILoader.getService(DatabaseType.class, "SQLServer");
+        DatabaseType oracle = TypedSPILoader.getService(DatabaseType.class, "Oracle");
+        SimpleTableSegment table = createTable("logic_db", "foo_orders");
+        InsertStatement insertAssignment = InsertStatement.builder().databaseType(mysql).table(table).setAssignment(assignment("foo_insert"))
+                .onDuplicateKeyColumns(new OnDuplicateKeyColumnsSegment(0, 0, assignment("foo_duplicate").getAssignments())).build();
+        InsertStatement insertReturning = InsertStatement.builder().databaseType(postgresql).table(table).values(List.of(new InsertValuesSegment(0, 0, List.of(createCurrentFunction("foo_value")))))
+                .returning(new ReturningSegment(0, 0, expressionProjection(createCurrentFunction("foo_returning")))).build();
+        ExecSegment exec = new ExecSegment(0, 0);
+        exec.setProcedureName(new FunctionNameSegment(0, 0, new IdentifierValue("foo_procedure")));
+        exec.getExpressionSegments().add(new LiteralExpressionSegment(0, 0, 1));
+        InsertStatement insertExec = InsertStatement.builder().databaseType(sqlserver).table(table).exec(exec).output(output("foo_output")).build();
+        MultiTableInsertIntoSegment multi = new MultiTableInsertIntoSegment(0, 0);
+        multi.getInsertStatements().add(InsertStatement.builder().databaseType(oracle).table(table).values(List.of(new InsertValuesSegment(0, 0, List.of(createCurrentFunction("foo_all"))))).build());
+        MultiTableConditionalIntoSegment conditional = new MultiTableConditionalIntoSegment(0, 0);
+        conditional.getWhenThenSegments().add(new MultiTableConditionalIntoWhenThenSegment(0, 0, createCurrentFunction("foo_when"), new MultiTableConditionalIntoThenSegment(0, 0,
+                List.of(InsertStatement.builder().databaseType(oracle).table(table).values(List.of(new InsertValuesSegment(0, 0, List.of(createCurrentFunction("foo_then"))))).build()))));
+        conditional.setElseSegment(new MultiTableConditionalIntoElseSegment(0, 0,
+                List.of(InsertStatement.builder().databaseType(oracle).table(table).values(List.of(new InsertValuesSegment(0, 0, List.of(createCurrentFunction("foo_else"))))).build())));
+        UpdateStatement updateReturning = UpdateStatement.builder().databaseType(postgresql).table(table).setAssignment(assignment("foo_update"))
+                .from(createTable("logic_db", "foo_source")).returning(new ReturningSegment(0, 0, expressionProjection(createCurrentFunction("foo_returning")))).build();
+        UpdateStatement updateOrdered = UpdateStatement.builder().databaseType(mysql).table(table).setAssignment(assignment("foo_update"))
+                .where(new WhereSegment(0, 0, createCurrentFunction("foo_filter"))).orderBy(expressionOrder(createCurrentFunction("foo_order"))).build();
+        return Stream.of(
+                Arguments.of("insert assignments", "MySQL", "INSERT INTO logic_db.foo_orders SET foo_value=logic_db.foo_insert() ON DUPLICATE KEY UPDATE foo_value=logic_db.foo_duplicate()",
+                        insertAssignment, Set.of("logic_db.foo_orders", "logic_db.foo_insert", "logic_db.foo_duplicate")),
+                Arguments.of("insert returning", "PostgreSQL", "INSERT INTO logic_db.foo_orders VALUES (logic_db.foo_value()) RETURNING logic_db.foo_returning()",
+                        insertReturning, Set.of("logic_db.foo_orders", "logic_db.foo_value", "logic_db.foo_returning")),
+                Arguments.of("insert exec output", "SQLServer", "INSERT INTO logic_db.foo_orders OUTPUT logic_db.foo_output() EXEC foo_procedure 1",
+                        insertExec, Set.of("logic_db.foo_orders", "logic_db.foo_output")),
+                Arguments.of("insert into all", "Oracle", "INSERT ALL INTO logic_db.foo_orders VALUES (logic_db.foo_all()) SELECT 1 FROM dual",
+                        InsertStatement.builder().databaseType(oracle).multiTableInsertType(MultiTableInsertType.ALL).multiTableInsertInto(multi)
+                                .insertSelect(new SubquerySegment(0, 0, selectExpression(oracle, new LiteralExpressionSegment(0, 0, 1)), "SELECT 1 FROM dual")).build(),
+                        Set.of("logic_db.foo_all")),
+                Arguments.of("conditional insert into", "Oracle", "INSERT FIRST WHEN logic_db.foo_when()=1 THEN INTO logic_db.foo_orders VALUES (logic_db.foo_then()) "
+                        + "ELSE INTO logic_db.foo_orders VALUES (logic_db.foo_else()) SELECT 1 FROM dual",
+                        InsertStatement.builder().databaseType(oracle).multiTableInsertType(MultiTableInsertType.FIRST).multiTableConditionalInto(conditional)
+                                .insertSelect(new SubquerySegment(0, 0, selectExpression(oracle, new LiteralExpressionSegment(0, 0, 1)), "SELECT 1 FROM dual")).build(),
+                        Set.of("logic_db.foo_when", "logic_db.foo_then", "logic_db.foo_else")),
+                Arguments.of("update from returning", "PostgreSQL", "UPDATE logic_db.foo_orders SET foo_value=logic_db.foo_update() FROM logic_db.foo_source RETURNING logic_db.foo_returning()",
+                        updateReturning, Set.of("logic_db.foo_orders", "logic_db.foo_source", "logic_db.foo_update", "logic_db.foo_returning")),
+                Arguments.of("update ordered", "MySQL", "UPDATE logic_db.foo_orders SET foo_value=logic_db.foo_update() WHERE logic_db.foo_filter()=1 ORDER BY logic_db.foo_order()", updateOrdered,
+                        Set.of("logic_db.foo_orders", "logic_db.foo_update", "logic_db.foo_filter", "logic_db.foo_order")),
+                Arguments.of("update output", "SQLServer", "UPDATE logic_db.foo_orders SET foo_value=logic_db.foo_update() OUTPUT logic_db.foo_output()",
+                        UpdateStatement.builder().databaseType(sqlserver).table(table).setAssignment(assignment("foo_update")).output(output("foo_output")).build(),
+                        Set.of("logic_db.foo_orders", "logic_db.foo_update", "logic_db.foo_output")),
+                Arguments.of("delete ordered", "MySQL", "DELETE FROM logic_db.foo_orders ORDER BY logic_db.foo_order()",
+                        DeleteStatement.builder().databaseType(mysql).table(table).orderBy(expressionOrder(createCurrentFunction("foo_order"))).build(),
+                        Set.of("logic_db.foo_orders", "logic_db.foo_order")),
+                Arguments.of("delete returning", "PostgreSQL", "DELETE FROM logic_db.foo_orders RETURNING logic_db.foo_returning()",
+                        DeleteStatement.builder().databaseType(postgresql).table(table).returning(new ReturningSegment(0, 0, expressionProjection(createCurrentFunction("foo_returning")))).build(),
+                        Set.of("logic_db.foo_orders", "logic_db.foo_returning")),
+                Arguments.of("delete output", "SQLServer", "DELETE FROM logic_db.foo_orders OUTPUT logic_db.foo_output()",
+                        DeleteStatement.builder().databaseType(sqlserver).table(table).output(output("foo_output")).build(), Set.of("logic_db.foo_orders", "logic_db.foo_output")));
+    }
+    
+    private static SetAssignmentSegment assignment(final String functionName) {
+        return new SetAssignmentSegment(0, 0, List.of(new ColumnAssignmentSegment(0, 0, List.of(new ColumnSegment(0, 0, new IdentifierValue("foo_value"))), createCurrentFunction(functionName))));
+    }
+    
+    private static ProjectionsSegment expressionProjection(final ExpressionSegment expression) {
+        ProjectionsSegment result = new ProjectionsSegment(0, 0);
+        result.getProjections().add(new ExpressionProjectionSegment(0, 0, expression.getText(), expression));
+        return result;
+    }
+    
+    private static OutputSegment output(final String functionName) {
+        OutputSegment result = new OutputSegment(0, 0);
+        result.setOutputColumns(expressionProjection(createCurrentFunction(functionName)));
+        return result;
+    }
+    
+    private static Stream<Arguments> parsedMergeClauses() {
+        DatabaseType oracle = TypedSPILoader.getService(DatabaseType.class, "Oracle");
+        DatabaseType sqlserver = TypedSPILoader.getService(DatabaseType.class, "SQLServer");
+        SimpleTableSegment target = createTable("logic_db", "foo_orders");
+        SimpleTableSegment source = createTable("logic_db", "foo_source");
+        UpdateStatement update = UpdateStatement.builder().databaseType(oracle).setAssignment(assignment("foo_update"))
+                .deleteWhere(new WhereSegment(0, 0, createCurrentFunction("foo_delete"))).build();
+        InsertStatement insert = InsertStatement.builder().databaseType(oracle).values(List.of(new InsertValuesSegment(0, 0, List.of(createCurrentFunction("foo_insert")))))
+                .where(new WhereSegment(0, 0, createCurrentFunction("foo_where"))).build();
+        MergeWhenAndThenSegment updateClause = new MergeWhenAndThenSegment(0, 0, "WHEN MATCHED");
+        updateClause.setAndExpr(createCurrentFunction("foo_matched"));
+        updateClause.setUpdate(UpdateStatement.builder().databaseType(sqlserver).setAssignment(assignment("foo_update")).build());
+        MergeWhenAndThenSegment insertClause = new MergeWhenAndThenSegment(0, 0, "WHEN NOT MATCHED");
+        insertClause.setAndExpr(createCurrentFunction("foo_unmatched"));
+        insertClause.setInsert(InsertStatement.builder().databaseType(sqlserver).values(List.of(new InsertValuesSegment(0, 0, List.of(createCurrentFunction("foo_insert"))))).build());
+        DatabaseType postgresql = TypedSPILoader.getService(DatabaseType.class, "PostgreSQL");
+        CombineSegment combine = new CombineSegment(0, 0, new SubquerySegment(0, 0, selectExpression(postgresql, createCurrentFunction("foo_left")), "SELECT logic_db.foo_left()"),
+                CombineType.UNION, new SubquerySegment(0, 0, selectExpression(postgresql, createCurrentFunction("foo_right")), "SELECT logic_db.foo_right()"));
+        HierarchicalQuerySegment hierarchical = new HierarchicalQuerySegment(0, 0);
+        hierarchical.setStartWith(createCurrentFunction("foo_start"));
+        hierarchical.setConnectBy(createCurrentFunction("foo_connect"));
+        return Stream.of(
+                Arguments.of("merge update and insert", "Oracle", "MERGE INTO logic_db.foo_orders USING logic_db.foo_source ON (logic_db.foo_on()=1) "
+                        + "WHEN MATCHED THEN UPDATE SET foo_value=logic_db.foo_update() DELETE WHERE logic_db.foo_delete()=1 "
+                        + "WHEN NOT MATCHED THEN INSERT (foo_value) VALUES (logic_db.foo_insert()) WHERE logic_db.foo_where()=1",
+                        MergeStatement.builder().databaseType(oracle).target(target).source(source).expression(new ExpressionWithParamsSegment(0, 0, createCurrentFunction("foo_on")))
+                                .update(update).insert(insert).build(),
+                        Set.of("logic_db.foo_orders", "logic_db.foo_source", "logic_db.foo_on", "logic_db.foo_update", "logic_db.foo_delete", "logic_db.foo_insert", "logic_db.foo_where")),
+                Arguments.of("merge conditional clauses", "SQLServer", "MERGE INTO logic_db.foo_orders USING logic_db.foo_source ON logic_db.foo_on()=1 "
+                        + "WHEN MATCHED AND logic_db.foo_matched()=1 THEN UPDATE SET foo_value=logic_db.foo_update() "
+                        + "WHEN NOT MATCHED AND logic_db.foo_unmatched()=1 THEN INSERT (foo_value) VALUES (logic_db.foo_insert()) OUTPUT logic_db.foo_output()",
+                        MergeStatement.builder().databaseType(sqlserver).target(target).source(source).expression(new ExpressionWithParamsSegment(0, 0, createCurrentFunction("foo_on")))
+                                .whenAndThens(List.of(updateClause, insertClause)).output(output("foo_output")).build(),
+                        Set.of("logic_db.foo_orders", "logic_db.foo_source", "logic_db.foo_on", "logic_db.foo_matched", "logic_db.foo_unmatched", "logic_db.foo_update", "logic_db.foo_insert",
+                                "logic_db.foo_output")),
+                Arguments.of("combined selects", "PostgreSQL", "SELECT logic_db.foo_left() UNION SELECT logic_db.foo_right()",
+                        SelectStatement.builder().databaseType(postgresql).projections(expressionProjection(createCurrentFunction("foo_left"))).combine(combine).build(),
+                        Set.of("logic_db.foo_left", "logic_db.foo_right")),
+                Arguments.of("hierarchical select", "Oracle", "SELECT * FROM dual START WITH logic_db.foo_start()=1 CONNECT BY logic_db.foo_connect()=1",
+                        SelectStatement.builder().databaseType(oracle).projections(starProjection()).hierarchicalQuery(hierarchical).build(), Set.of("logic_db.foo_start", "logic_db.foo_connect")),
+                Arguments.of("select clause references", "PostgreSQL", "SELECT * FROM logic_db.foo_orders WHERE logic_db.foo_where()=1 GROUP BY logic_db.foo_group() "
+                        + "HAVING logic_db.foo_having()=1 ORDER BY logic_db.foo_order()",
+                        SelectStatement.builder().databaseType(postgresql).projections(starProjection()).from(target).where(new WhereSegment(0, 0, createCurrentFunction("foo_where")))
+                                .groupBy(new GroupBySegment(0, 0, expressionOrder(createCurrentFunction("foo_group")).getOrderByItems()))
+                                .having(new HavingSegment(0, 0, createCurrentFunction("foo_having"))).orderBy(expressionOrder(createCurrentFunction("foo_order"))).build(),
+                        Set.of("logic_db.foo_orders", "logic_db.foo_where", "logic_db.foo_group", "logic_db.foo_having", "logic_db.foo_order")));
+    }
+    
+    @Test
+    void assertExecuteWithMissingStatementVisitor() {
+        DatabaseType databaseType = TypedSPILoader.getService(DatabaseType.class, "MySQL");
+        MCPDatabaseCapabilityProvider capabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
+        MCPSessionExecutionCoordinator coordinator = mock(MCPSessionExecutionCoordinator.class);
+        MCPDatabaseCapability capability = mock(MCPDatabaseCapability.class);
+        when(capability.getDatabaseType()).thenReturn("MySQL");
+        when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
+        mockSessionLock(coordinator);
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, mock(MCPJdbcTransactionStatementExecutor.class), mock(MCPJdbcStatementExecutor.class));
+        try (MockedStatic<DatabaseTypedSPILoader> mocked = mockStatic(DatabaseTypedSPILoader.class, CALLS_REAL_METHODS)) {
+            mocked.when(() -> DatabaseTypedSPILoader.findService(SQLStatementVisitorFacade.class, databaseType)).thenReturn(Optional.empty());
+            MCPUnsupportedException actual = assertThrows(MCPUnsupportedException.class, () -> facade.execute(createExecutionRequest("SELECT 1")));
+            assertThat(actual.getMessage(), is("SQL parser is not available for database type `MySQL`."));
+        }
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("malformedDelimiterInputs")
+    void assertExecuteWithMalformedDelimiter(final String name, final String databaseType, final String sql) {
+        MCPDatabaseCapabilityProvider capabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
+        MCPSessionExecutionCoordinator coordinator = mock(MCPSessionExecutionCoordinator.class);
+        MCPDatabaseCapability capability = mock(MCPDatabaseCapability.class);
+        when(capability.getDatabaseType()).thenReturn(databaseType);
+        when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
+        mockSessionLock(coordinator);
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, mock(MCPJdbcTransactionStatementExecutor.class), mock(MCPJdbcStatementExecutor.class));
+        try (MockedConstruction<ShardingSphereSQLParserEngine> actual = mockConstruction(ShardingSphereSQLParserEngine.class)) {
+            assertThrows(MCPUnsupportedSQLStatementException.class, () -> facade.execute(createExecutionRequest(sql)));
+            assertTrue(actual.constructed().isEmpty());
+        }
+    }
+    
+    private static Stream<Arguments> malformedDelimiterInputs() {
+        return Stream.of(
+                Arguments.of("bare string quote", "MySQL", "SELECT '"),
+                Arguments.of("bare identifier quote", "MySQL", "SELECT `"),
+                Arguments.of("bare ANSI quote", "PostgreSQL", "SELECT \""),
+                Arguments.of("unterminated fallback block comment", "ClickHouse", "SELECT 1 /* foo"));
+    }
+    
+    @Test
+    void assertExecuteWithEmptyParsedStatement() {
+        MCPDatabaseCapabilityProvider capabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
+        MCPSessionExecutionCoordinator coordinator = mock(MCPSessionExecutionCoordinator.class);
+        MCPDatabaseCapability capability = mock(MCPDatabaseCapability.class);
+        when(capability.getDatabaseType()).thenReturn("PostgreSQL");
+        when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
+        mockSessionLock(coordinator);
+        MCPJdbcStatementExecutor statementExecutor = mock(MCPJdbcStatementExecutor.class);
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, mock(MCPJdbcTransactionStatementExecutor.class), statementExecutor);
+        try (
+                MockedConstruction<ShardingSphereSQLParserEngine> ignored = mockConstruction(ShardingSphereSQLParserEngine.class,
+                        (mock, context) -> when(mock.parse(anyString(), eq(false))).thenReturn(new EmptyStatement(TypedSPILoader.getService(DatabaseType.class, "PostgreSQL"))))) {
+            assertThrows(MCPUnsupportedSQLStatementException.class, () -> facade.execute(createExecutionRequest("/**/")));
+            verifyNoInteractions(statementExecutor);
+        }
+    }
+    
+    @Test
+    void assertExecuteWithParenthesizedStatement() {
+        MCPDatabaseCapabilityProvider capabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
+        MCPSessionExecutionCoordinator coordinator = mock(MCPSessionExecutionCoordinator.class);
+        MCPDatabaseCapability capability = mock(MCPDatabaseCapability.class);
+        when(capability.getDatabaseType()).thenReturn("MySQL");
+        when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
+        mockSessionLock(coordinator);
+        MCPJdbcStatementExecutor statementExecutor = mock(MCPJdbcStatementExecutor.class);
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, mock(MCPJdbcTransactionStatementExecutor.class), statementExecutor);
+        SQLStatement statement = selectExpression(TypedSPILoader.getService(DatabaseType.class, "MySQL"), new LiteralExpressionSegment(0, 0, 1));
+        try (
+                MockedConstruction<ShardingSphereSQLParserEngine> ignored = mockConstruction(ShardingSphereSQLParserEngine.class,
+                        (mock, context) -> when(mock.parse(anyString(), eq(false))).thenReturn(statement))) {
+            assertThrows(MCPUnsupportedSQLStatementException.class, () -> facade.execute(createExecutionRequest("(SELECT 1)")));
+            verifyNoInteractions(statementExecutor);
+        }
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("parserFailureInputs")
+    void assertExecuteWithParserFailure(final String name, final String databaseType, final String sql) {
+        MCPDatabaseCapabilityProvider capabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
+        MCPSessionExecutionCoordinator coordinator = mock(MCPSessionExecutionCoordinator.class);
+        MCPDatabaseCapability capability = mock(MCPDatabaseCapability.class);
+        when(capability.getDatabaseType()).thenReturn(databaseType);
+        when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
+        mockSessionLock(coordinator);
+        MCPJdbcStatementExecutor statementExecutor = mock(MCPJdbcStatementExecutor.class);
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, mock(MCPJdbcTransactionStatementExecutor.class), statementExecutor);
+        IllegalArgumentException expectedCause = new IllegalArgumentException("foo_invalid_sql");
+        try (
+                MockedConstruction<ShardingSphereSQLParserEngine> actual = mockConstruction(ShardingSphereSQLParserEngine.class,
+                        (mock, context) -> when(mock.parse(anyString(), eq(false))).thenThrow(expectedCause))) {
+            MCPUnsupportedSQLStatementException actualException = assertThrows(MCPUnsupportedSQLStatementException.class, () -> facade.execute(createExecutionRequest(sql)));
+            assertThat(actualException.getCause(), sameInstance(expectedCause));
+            assertThat(actual.constructed().size(), is(1));
+            verify(actual.constructed().get(0)).parse(sql, false);
+            verifyNoInteractions(statementExecutor);
+        }
+    }
+    
+    private static Stream<Arguments> parserFailureInputs() {
+        return Stream.of(
+                Arguments.of("fallback block comment masks semicolons", "ClickHouse", "SELECT 1 /* ; SELECT 2 */"),
+                Arguments.of("fallback line comment masks semicolons", "ClickHouse", "SELECT 1 -- ; SELECT 2\n +2"),
+                Arguments.of("fallback line comment without newline", "ClickHouse", "SELECT 1 -- ; SELECT 2"),
+                Arguments.of("incomplete division", "MySQL", "SELECT 1 /"),
+                Arguments.of("spaced comment delimiter", "MySQL", "SELECT 1 / *"));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("supportedLexicalQueries")
+    void assertExecuteWithSupportedLexicalQuery(final String name, final String databaseType, final String sql) {
+        MCPDatabaseCapabilityProvider capabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
+        MCPSessionExecutionCoordinator coordinator = mock(MCPSessionExecutionCoordinator.class);
+        MCPDatabaseCapability capability = createCapability(Set.of(SupportedMCPStatement.QUERY));
+        when(capability.getDatabaseType()).thenReturn(databaseType);
+        when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
+        mockSessionLock(coordinator);
+        MCPJdbcStatementExecutor statementExecutor = mock(MCPJdbcStatementExecutor.class);
+        SQLExecutionRequest request = createExecutionRequest(sql);
+        SQLExecutionResult expectedResult = mock(SQLExecutionResult.class);
+        when(statementExecutor.execute(eq(request), any(), eq(capability))).thenReturn(expectedResult);
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, mock(MCPJdbcTransactionStatementExecutor.class), statementExecutor);
+        SQLStatement statement = selectExpression(TypedSPILoader.getService(DatabaseType.class, databaseType), new LiteralExpressionSegment(0, 0, 1));
+        try (
+                MockedConstruction<ShardingSphereSQLParserEngine> ignored = mockConstruction(ShardingSphereSQLParserEngine.class,
+                        (mock, context) -> when(mock.parse(anyString(), eq(false))).thenReturn(statement))) {
+            assertThat(facade.execute(request), sameInstance(expectedResult));
+            ArgumentCaptor<ClassificationResult> actual = ArgumentCaptor.forClass(ClassificationResult.class);
+            verify(statementExecutor).execute(eq(request), actual.capture(), eq(capability));
+            assertThat(actual.getValue().getStatementClass(), is(SupportedMCPStatement.QUERY));
+            assertThat(actual.getValue().getNormalizedSql(), is(sql));
+        }
+    }
+    
+    private static Stream<Arguments> supportedLexicalQueries() {
+        return Stream.of(
+                Arguments.of("Oracle trailing fullwidth whitespace", "Oracle", "SELECT 1 FROM dual "),
+                Arguments.of("Oracle leading fullwidth whitespace", "Oracle", " SELECT 1 FROM dual"),
+                Arguments.of("division expression", "MySQL", "SELECT 1 / 2"));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("quotedExplainPrefixes")
+    void assertExecuteExplainWithQuotedKeyword(final String name, final String databaseType, final String sql) {
+        MCPDatabaseCapabilityProvider capabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
+        MCPSessionExecutionCoordinator coordinator = mock(MCPSessionExecutionCoordinator.class);
+        MCPDatabaseCapability capability = createCapability(Set.of(SupportedMCPStatement.EXPLAIN));
+        when(capability.getDatabaseType()).thenReturn(databaseType);
+        when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
+        mockSessionLock(coordinator);
+        MCPJdbcStatementExecutor statementExecutor = mock(MCPJdbcStatementExecutor.class);
+        ClassificationResult classification = new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT 1", "", List.of(), false);
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, mock(MCPJdbcTransactionStatementExecutor.class), statementExecutor, createMCPStatementAnalyzer(classification));
+        MCPInvalidRequestException actual = assertThrows(MCPInvalidRequestException.class, () -> facade.executeExplain(createExecutionRequest(sql), "SELECT 1"));
+        assertThat(actual.getMessage(), is("explain_sql must start with EXPLAIN."));
+        verifyNoInteractions(statementExecutor);
+    }
+    
+    private static Stream<Arguments> quotedExplainPrefixes() {
+        return Stream.of(
+                Arguments.of("ANSI quoted keyword", "PostgreSQL", "\"EXPLAIN\" SELECT 1"),
+                Arguments.of("backtick quoted keyword", "MySQL", "`EXPLAIN` SELECT 1"),
+                Arguments.of("bracket quoted keyword", "SQLServer", "[EXPLAIN] SELECT 1"));
+    }
+    
     @Test
     void assertExecuteWithMissingSession() {
         MCPDatabaseCapabilityProvider capabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
diff --git a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/model/EncryptWorkflowRequestTest.java b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/model/EncryptWorkflowRequestTest.java
index ed9b0faa5c9..a0f3822707f 100644
--- a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/model/EncryptWorkflowRequestTest.java
+++ b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/model/EncryptWorkflowRequestTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.shardingsphere.mcp.feature.encrypt.tool.model;
 
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
 import org.junit.jupiter.api.Test;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -70,4 +71,15 @@ class EncryptWorkflowRequestTest {
         assertThat(actualRequest.getPrimaryAlgorithmProperties().get("aes-key-value"), is("current-key"));
         assertThat(actualRequest.getOptions().getAssistedQueryAlgorithmProperties().get("digest-algorithm-name"), is("SHA-256"));
     }
+    
+    @Test
+    void assertMergeFromBaseRequest() {
+        WorkflowRequest previous = new WorkflowRequest();
+        previous.setDatabase("foo_db");
+        EncryptWorkflowRequest current = new EncryptWorkflowRequest();
+        current.setColumn("foo_column");
+        EncryptWorkflowRequest actual = EncryptWorkflowRequest.merge(previous, current);
+        assertThat(actual.getDatabase(), is("foo_db"));
+        assertThat(actual.getColumn(), is("foo_column"));
+    }
 }
diff --git a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowApplyArtifactValidatorTest.java b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowApplyArtifactValidatorTest.java
index 01897fcaf28..0354b3b3d78 100644
--- a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowApplyArtifactValidatorTest.java
+++ b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowApplyArtifactValidatorTest.java
@@ -90,6 +90,27 @@ class EncryptWorkflowApplyArtifactValidatorTest {
         assertTrue(actual.isEmpty());
     }
     
+    @Test
+    void assertValidateUnavailableQueryAlgorithms() {
+        EncryptWorkflowRequest request = new EncryptWorkflowRequest();
+        request.getOptions().setRequiresEqualityFilter(true);
+        request.getOptions().setAssistedQueryAlgorithmType("FOO_ASSISTED");
+        request.getOptions().setRequiresLikeQuery(true);
+        request.getOptions().setLikeQueryAlgorithmType("FOO_LIKE");
+        WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
+        snapshot.setRequest(request);
+        String sql = "CREATE ENCRYPT RULE `foo_table` (COLUMNS((NAME=`foo_column`, CIPHER=`foo_cipher`, ENCRYPT_ALGORITHM(TYPE(NAME='aes')))))";
+        try (MockedStatic<WorkflowAlgorithmUtils> mocked = mockStatic(WorkflowAlgorithmUtils.class, CALLS_REAL_METHODS)) {
+            mocked.when(() -> WorkflowAlgorithmUtils.isAlgorithmServiceAvailable(EncryptAlgorithm.class, "FOO_ASSISTED", Map.of())).thenReturn(false);
+            mocked.when(() -> WorkflowAlgorithmUtils.isAlgorithmServiceAvailable(EncryptAlgorithm.class, "FOO_LIKE", Map.of())).thenReturn(false);
+            List<Map<String, Object>> actual = new EncryptWorkflowApplyArtifactValidator().validate(snapshot, List.of(new ExecutableWorkflowArtifact(sql, sql)));
+            assertThat(actual.size(), is(2));
+            assertThat(actual.get(0).get("message"),
+                    is("Generated encrypt DistSQL references assisted query algorithm `FOO_ASSISTED`, but it cannot be loaded or initialized by EncryptAlgorithm SPI."));
+            assertThat(actual.get(1).get("message"), is("Generated encrypt DistSQL references like query algorithm `FOO_LIKE`, but it cannot be loaded or initialized by EncryptAlgorithm SPI."));
+        }
+    }
+    
     private ExecutableWorkflowArtifact createArtifact(final String sql, final String displaySql) {
         return new ExecutableWorkflowArtifact(sql, displaySql);
     }
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 a5af54a939b..cc8282f06d1 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
@@ -203,6 +203,39 @@ class EncryptWorkflowValidationServiceTest {
         assertThat(((Map<?, ?>) ((List<?>) actual.get("mismatches")).getFirst()).get("actual"), is("logic_column=email"));
     }
     
+    @Test
+    void assertValidateDropWithRemainingRule() {
+        WorkflowContextSnapshot snapshot = createSnapshot("plan-1", "session-1", "executed", "drop");
+        EncryptRuleInspectionService inspectionService = mock(EncryptRuleInspectionService.class);
+        when(inspectionService.queryEncryptRules(any(), any(), any())).thenReturn(List.of(createRuleRow()));
+        Map<String, Object> actual = createService(inspectionService).validate(snapshot, createQueryFacade()).toMap();
+        assertThat(actual.get("overall_status"), is("failed"));
+        assertThat(getValidationSection(actual, "rule").get("details"), is("Encrypt rule still exists."));
+        assertThat(((Map<?, ?>) ((List<?>) actual.get("mismatches")).getFirst()).get("code"), is(WorkflowIssueCode.RULE_STATE_MISMATCH));
+    }
+    
+    @Test
+    void assertValidateQueryAlgorithmMismatch() {
+        WorkflowContextSnapshot snapshot = createSnapshot("plan-1", "session-1", "executed", "create");
+        EncryptWorkflowRequest request = (EncryptWorkflowRequest) snapshot.getRequest();
+        request.getOptions().setRequiresEqualityFilter(true);
+        request.getOptions().setAssistedQueryColumnName("phone_assisted");
+        request.getOptions().setAssistedQueryAlgorithmType("MD5");
+        request.getOptions().setRequiresLikeQuery(true);
+        request.getOptions().setLikeQueryColumnName("phone_like");
+        request.getOptions().setLikeQueryAlgorithmType("CHAR_DIGEST_LIKE");
+        EncryptRuleInspectionService inspectionService = mock(EncryptRuleInspectionService.class);
+        when(inspectionService.queryEncryptRules(any(), any(), any())).thenReturn(List.of(Map.of(
+                "logic_column", "phone", "cipher_column", "phone_cipher", "encryptor_type", "AES", "assisted_query_column", "phone_assisted",
+                "assisted_query_type", "SM3", "like_query_column", "phone_like", "like_query_type", "CHAR_DIGEST_LIKE")));
+        MCPFeatureQueryFacade queryFacade = createQueryFacade();
+        when(queryFacade.isSameIdentifier("logic_db", IdentifierScope.COLUMN, "phone_assisted", "phone_assisted")).thenReturn(true);
+        when(queryFacade.isSameIdentifier("logic_db", IdentifierScope.COLUMN, "phone_like", "phone_like")).thenReturn(true);
+        Map<String, Object> actual = createService(inspectionService).validate(snapshot, queryFacade).toMap();
+        assertThat(actual.get("overall_status"), is("failed"));
+        assertThat(((Map<?, ?>) ((List<?>) actual.get("mismatches")).getFirst()).get("expected"), is("assisted_query_type=MD5"));
+    }
+    
     private MCPFeatureQueryFacade createQueryFacade() {
         MCPFeatureQueryFacade result = mock(MCPFeatureQueryFacade.class);
         when(result.isSameIdentifier("logic_db", IdentifierScope.COLUMN, "phone", "phone")).thenReturn(true);
diff --git a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/model/ReadwriteSplittingRuleWorkflowRequestTest.java b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/model/ReadwriteSplittingRuleWorkflowRequestTest.java
index b5994e77c98..771bcee4958 100644
--- a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/model/ReadwriteSplittingRuleWorkflowRequestTest.java
+++ b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/model/ReadwriteSplittingRuleWorkflowRequestTest.java
@@ -59,6 +59,13 @@ class ReadwriteSplittingRuleWorkflowRequestTest {
         assertThat(actual.getDatabase(), is("logic_db"));
     }
     
+    @Test
+    void assertSetReadStorageUnitsWithMissingListValues() {
+        ReadwriteSplittingRuleWorkflowRequest request = new ReadwriteSplittingRuleWorkflowRequest();
+        request.setReadStorageUnits(List.of(" ", " foo_read "));
+        assertThat(request.getReadStorageUnits(), is(List.of("foo_read")));
+    }
+    
     private ReadwriteSplittingRuleWorkflowRequest createRequest() {
         ReadwriteSplittingRuleWorkflowRequest result = new ReadwriteSplittingRuleWorkflowRequest();
         result.setRuleName("readwrite_ds");
diff --git a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/model/ReadwriteSplittingStatusWorkflowRequestTest.java b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/model/ReadwriteSplittingStatusWorkflowRequestTest.java
index e98ae80beab..784d746af99 100644
--- a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/model/ReadwriteSplittingStatusWorkflowRequestTest.java
+++ b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/model/ReadwriteSplittingStatusWorkflowRequestTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.shardingsphere.mcp.feature.readwritesplitting.tool.model;
 
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
 import org.junit.jupiter.api.Test;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -34,4 +35,15 @@ class ReadwriteSplittingStatusWorkflowRequestTest {
         assertThat(actual.getRuleName(), is("readwrite_ds"));
         assertThat(actual.getStorageUnit(), is("read_ds_0"));
     }
+    
+    @Test
+    void assertMergeFromBaseRequest() {
+        WorkflowRequest previous = new WorkflowRequest();
+        previous.setDatabase("foo_db");
+        ReadwriteSplittingStatusWorkflowRequest current = new ReadwriteSplittingStatusWorkflowRequest();
+        current.setStorageUnit("foo_read");
+        ReadwriteSplittingStatusWorkflowRequest actual = ReadwriteSplittingStatusWorkflowRequest.merge(previous, current);
+        assertThat(actual.getDatabase(), is("foo_db"));
+        assertThat(actual.getStorageUnit(), is("foo_read"));
+    }
 }
diff --git a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmRecommendationServiceTest.java b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmRecommendationServiceTest.java
index 7d016852e81..fce2f1c8efd 100644
--- a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmRecommendationServiceTest.java
+++ b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmRecommendationServiceTest.java
@@ -22,10 +22,14 @@ 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.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.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -35,24 +39,24 @@ class ReadwriteSplittingAlgorithmRecommendationServiceTest {
     
     private final ReadwriteSplittingAlgorithmRecommendationService service = new ReadwriteSplittingAlgorithmRecommendationService();
     
-    @Test
-    void assertRecommendSpecifiedAlgorithm() {
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("assertRecommendAlgorithmCases")
+    void assertRecommendAlgorithm(final String name, final String algorithmType, final List<Map<String, Object>> algorithmRows, final String expectedType, final String expectedReason) {
         ReadwriteSplittingRuleWorkflowRequest request = new ReadwriteSplittingRuleWorkflowRequest();
-        request.setLoadBalancerType("round_robin");
-        List<AlgorithmCandidate> actual = service.recommendLoadBalanceAlgorithms(
-                request, List.of(Map.of("type", "ROUND_ROBIN")), new LinkedList<>());
-        assertThat(actual.getFirst().getAlgorithmType(), is("ROUND_ROBIN"));
+        request.setLoadBalancerType(algorithmType);
+        List<AlgorithmCandidate> actual = service.recommendLoadBalanceAlgorithms(request, algorithmRows, new LinkedList<>());
+        assertThat(actual.getFirst().getAlgorithmType(), is(expectedType));
         assertThat(actual.getFirst().getAlgorithmRole(), is("primary"));
+        assertThat(actual.getFirst().getRecommendationReason(), is(expectedReason));
     }
     
-    @Test
-    void assertRecommendDefaultAlgorithm() {
-        ReadwriteSplittingRuleWorkflowRequest request = new ReadwriteSplittingRuleWorkflowRequest();
-        List<AlgorithmCandidate> actual = service.recommendLoadBalanceAlgorithms(
-                request, List.of(Map.of("type", "RANDOM"), Map.of("type", "ROUND_ROBIN")), new LinkedList<>());
-        assertThat(actual.getFirst().getAlgorithmType(), is("ROUND_ROBIN"));
-        assertThat(actual.getFirst().getRecommendationReason(), is(
-                "Selected from algorithms reported by the current Proxy using MCP's built-in load-balance preference order."));
+    private static Stream<Arguments> assertRecommendAlgorithmCases() {
+        return Stream.of(
+                Arguments.of("specified algorithm", "round_robin", List.of(Map.of("type", "ROUND_ROBIN")), "ROUND_ROBIN", "User specified load-balance algorithm."),
+                Arguments.of("default algorithm", "", List.of(Map.of("type", "RANDOM"), Map.of("type", "ROUND_ROBIN")), "ROUND_ROBIN",
+                        "Selected from algorithms reported by the current Proxy using MCP's built-in load-balance preference order."),
+                Arguments.of("first available algorithm", "", List.of(Map.of("type", "FOO_ALGORITHM")), "FOO_ALGORITHM",
+                        "Selected from algorithms reported by the current Proxy using MCP's built-in load-balance preference order."));
     }
     
     @Test
@@ -63,4 +67,13 @@ class ReadwriteSplittingAlgorithmRecommendationServiceTest {
         assertTrue(service.recommendLoadBalanceAlgorithms(request, List.of(Map.of("type", "RANDOM")), issues).isEmpty());
         assertThat(issues.getFirst().getCode(), is(WorkflowIssueCode.ALGORITHM_NOT_FOUND));
     }
+    
+    @Test
+    void assertRecommendWithoutAlgorithms() {
+        List<WorkflowIssue> issues = new LinkedList<>();
+        List<AlgorithmCandidate> actual = service.recommendLoadBalanceAlgorithms(new ReadwriteSplittingRuleWorkflowRequest(), List.of(), issues);
+        assertTrue(actual.isEmpty());
+        assertThat(issues.getFirst().getCode(), is(WorkflowIssueCode.ALGORITHM_NOT_FOUND));
+        assertThat(issues.getFirst().getMessage(), is("No load-balance algorithm is available from the current Proxy."));
+    }
 }
diff --git a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingInspectionServiceTest.java b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingInspectionServiceTest.java
index b2c6623cedf..18f3de1bfea 100644
--- a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingInspectionServiceTest.java
+++ b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingInspectionServiceTest.java
@@ -89,4 +89,18 @@ class ReadwriteSplittingInspectionServiceTest {
         when(queryFacade.query(eq("logic_db"), eq("COUNT READWRITE_SPLITTING RULE FROM logic_db"))).thenReturn(List.of(Map.of("count", 1)));
         assertThat(new ReadwriteSplittingInspectionService().queryRuleCount(queryFacade, "logic_db"), is(List.of(Map.of("count", 1))));
     }
+    
+    @Test
+    void assertQueryRule() {
+        MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+        new ReadwriteSplittingInspectionService().queryRule(queryFacade, "foo_db", "foo_rule");
+        verify(queryFacade).query("foo_db", "SHOW READWRITE_SPLITTING RULE foo_rule FROM foo_db");
+    }
+    
+    @Test
+    void assertQueryStatuses() {
+        MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+        new ReadwriteSplittingInspectionService().queryStatuses(queryFacade, "foo_db");
+        verify(queryFacade).query("foo_db", "SHOW STATUS FROM READWRITE_SPLITTING RULES FROM foo_db");
+    }
 }
diff --git a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningServiceTest.java b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningServiceTest.java
index 272779ff8ab..7981a5665e8 100644
--- a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningServiceTest.java
+++ b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningServiceTest.java
@@ -90,6 +90,16 @@ class ReadwriteSplittingStatusWorkflowPlanningServiceTest {
         verify(queryFacade, never()).query("logic_db", "SHOW STATUS FROM READWRITE_SPLITTING RULE readwrite_ds FROM logic_db");
     }
     
+    @Test
+    void assertPlanClarifiesMissingDatabase() {
+        ReadwriteSplittingStatusWorkflowRequest request = new ReadwriteSplittingStatusWorkflowRequest();
+        request.setTargetStatus("enable");
+        WorkflowContextSnapshot actual = planningService.plan(new TestWorkflowSessionContext(), mock(MCPFeatureQueryFacade.class), request);
+        assertThat(actual.getStatus(), is(WorkflowLifecycle.STATUS_CLARIFYING));
+        assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.DATABASE_REQUIRED));
+        assertTrue(actual.getRuleArtifacts().isEmpty());
+    }
+    
     private MCPFeatureQueryFacade createQueryFacade(final List<Map<String, Object>> statuses) {
         MCPFeatureQueryFacade result = mock(MCPFeatureQueryFacade.class);
         when(result.isSameIdentifier("logic_db", IdentifierScope.TABLE, "readwrite_ds", "readwrite_ds")).thenReturn(true);
diff --git a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowAlgorithmCleanupWorkflowRequestTest.java b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowAlgorithmCleanupWorkflowRequestTest.java
index 4840dcd4074..e92332e7fbf 100644
--- a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowAlgorithmCleanupWorkflowRequestTest.java
+++ b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowAlgorithmCleanupWorkflowRequestTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.shardingsphere.mcp.feature.shadow.tool.model;
 
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
 import org.junit.jupiter.api.Test;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -34,4 +35,15 @@ class ShadowAlgorithmCleanupWorkflowRequestTest {
         assertThat(actual.getDatabase(), is("logic_db"));
         assertThat(actual.getAlgorithmName(), is("shadow_algorithm"));
     }
+    
+    @Test
+    void assertMergeFromBaseRequest() {
+        WorkflowRequest previous = new WorkflowRequest();
+        previous.setDatabase("foo_db");
+        ShadowAlgorithmCleanupWorkflowRequest current = new ShadowAlgorithmCleanupWorkflowRequest();
+        current.setAlgorithmName("foo_algorithm");
+        ShadowAlgorithmCleanupWorkflowRequest actual = ShadowAlgorithmCleanupWorkflowRequest.merge(previous, current);
+        assertThat(actual.getDatabase(), is("foo_db"));
+        assertThat(actual.getAlgorithmName(), is("foo_algorithm"));
+    }
 }
diff --git a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowDefaultAlgorithmWorkflowRequestTest.java b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowDefaultAlgorithmWorkflowRequestTest.java
index 8806285aa5f..4198b52401b 100644
--- a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowDefaultAlgorithmWorkflowRequestTest.java
+++ b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowDefaultAlgorithmWorkflowRequestTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.shardingsphere.mcp.feature.shadow.tool.model;
 
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
 import org.junit.jupiter.api.Test;
 
 import java.util.Map;
@@ -36,4 +37,22 @@ class ShadowDefaultAlgorithmWorkflowRequestTest {
         assertThat(actual.getAlgorithmType(), is("SQL_HINT"));
         assertThat(actual.getAlgorithmProperties().get("k"), is("v"));
     }
+    
+    @Test
+    void assertMergeFromBaseRequest() {
+        WorkflowRequest previous = new WorkflowRequest();
+        previous.setDatabase("foo_db");
+        ShadowDefaultAlgorithmWorkflowRequest current = new ShadowDefaultAlgorithmWorkflowRequest();
+        current.setAlgorithmType("SQL_HINT");
+        ShadowDefaultAlgorithmWorkflowRequest actual = ShadowDefaultAlgorithmWorkflowRequest.merge(previous, current);
+        assertThat(actual.getDatabase(), is("foo_db"));
+        assertThat(actual.getAlgorithmType(), is("SQL_HINT"));
+    }
+    
+    @Test
+    void assertGetAlgorithmProperties() {
+        ShadowDefaultAlgorithmWorkflowRequest request = new ShadowDefaultAlgorithmWorkflowRequest();
+        request.putAlgorithmProperties(Map.of("foo_key", "foo_value"));
+        assertThat(request.getAlgorithmProperties("primary"), is(Map.of("foo_key", "foo_value")));
+    }
 }
diff --git a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowRuleWorkflowRequestTest.java b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowRuleWorkflowRequestTest.java
index 7677c0537df..9fea3b60f07 100644
--- a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowRuleWorkflowRequestTest.java
+++ b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowRuleWorkflowRequestTest.java
@@ -17,12 +17,14 @@
 
 package org.apache.shardingsphere.mcp.feature.shadow.tool.model;
 
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
 import org.junit.jupiter.api.Test;
 
 import java.util.Map;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class ShadowRuleWorkflowRequestTest {
     
@@ -41,4 +43,22 @@ class ShadowRuleWorkflowRequestTest {
         assertThat(actual.getTable(), is("t_order"));
         assertThat(actual.getAlgorithmProperties().get("operation"), is("insert"));
     }
+    
+    @Test
+    void assertMergeFromBaseRequest() {
+        WorkflowRequest previous = new WorkflowRequest();
+        previous.setDatabase("foo_db");
+        ShadowRuleWorkflowRequest current = new ShadowRuleWorkflowRequest();
+        current.setTableName("foo_table");
+        ShadowRuleWorkflowRequest actual = ShadowRuleWorkflowRequest.merge(previous, current);
+        assertThat(actual.getDatabase(), is("foo_db"));
+        assertThat(actual.getTable(), is("foo_table"));
+    }
+    
+    @Test
+    void assertGetAlgorithmProperties() {
+        ShadowRuleWorkflowRequest request = new ShadowRuleWorkflowRequest();
+        request.putAlgorithmProperties(Map.of("foo_key", "foo_value"));
+        assertTrue(request.getAlgorithmProperties("assisted").isEmpty());
+    }
 }
diff --git a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmRecommendationServiceTest.java b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmRecommendationServiceTest.java
index fa8a5d64839..612758f9585 100644
--- a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmRecommendationServiceTest.java
+++ b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmRecommendationServiceTest.java
@@ -22,10 +22,14 @@ 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.WorkflowRequest;
 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.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -35,23 +39,23 @@ class ShadowAlgorithmRecommendationServiceTest {
     
     private final ShadowAlgorithmRecommendationService service = new ShadowAlgorithmRecommendationService();
     
-    @Test
-    void assertRecommendSpecifiedAlgorithm() {
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("assertRecommendAlgorithmCases")
+    void assertRecommendAlgorithm(final String name, final String algorithmType, final List<Map<String, Object>> algorithmRows, final String expectedType, final String expectedReason) {
         WorkflowRequest request = new WorkflowRequest();
-        request.setAlgorithmType("value_match");
-        List<AlgorithmCandidate> actual = service.recommendShadowAlgorithms(
-                request, List.of(Map.of("type", "VALUE_MATCH")), new LinkedList<>());
-        assertThat(actual.getFirst().getAlgorithmType(), is("VALUE_MATCH"));
+        request.setAlgorithmType(algorithmType);
+        List<AlgorithmCandidate> actual = service.recommendShadowAlgorithms(request, algorithmRows, new LinkedList<>());
+        assertThat(actual.getFirst().getAlgorithmType(), is(expectedType));
+        assertThat(actual.getFirst().getRecommendationReason(), is(expectedReason));
     }
     
-    @Test
-    void assertRecommendSqlHintDefault() {
-        WorkflowRequest request = new WorkflowRequest();
-        List<AlgorithmCandidate> actual = service.recommendShadowAlgorithms(
-                request, List.of(Map.of("type", "VALUE_MATCH"), Map.of("type", "SQL_HINT")), new LinkedList<>());
-        assertThat(actual.getFirst().getAlgorithmType(), is("SQL_HINT"));
-        assertThat(actual.getFirst().getRecommendationReason(), is(
-                "Selected from algorithms reported by the current Proxy using MCP's built-in shadow preference order."));
+    private static Stream<Arguments> assertRecommendAlgorithmCases() {
+        return Stream.of(
+                Arguments.of("specified algorithm", "value_match", List.of(Map.of("type", "VALUE_MATCH")), "VALUE_MATCH", "User specified shadow algorithm."),
+                Arguments.of("default algorithm", "", List.of(Map.of("type", "VALUE_MATCH"), Map.of("type", "SQL_HINT")), "SQL_HINT",
+                        "Selected from algorithms reported by the current Proxy using MCP's built-in shadow preference order."),
+                Arguments.of("first available algorithm", "", List.of(Map.of("type", "FOO_ALGORITHM")), "FOO_ALGORITHM",
+                        "Selected from algorithms reported by the current Proxy using MCP's built-in shadow preference order."));
     }
     
     @Test
diff --git a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowInspectionServiceTest.java b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowInspectionServiceTest.java
index 896bb2f1263..7b690b1a587 100644
--- a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowInspectionServiceTest.java
+++ b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowInspectionServiceTest.java
@@ -74,4 +74,32 @@ class ShadowInspectionServiceTest {
         MCPQueryFailedException actual = assertThrows(MCPQueryFailedException.class, () -> new ShadowInspectionService().queryAlgorithmPlugins(queryFacade));
         assertThat(actual, is(expected));
     }
+    
+    @Test
+    void assertQueryTableRules() {
+        MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+        new ShadowInspectionService().queryTableRules(queryFacade, "foo_db");
+        verify(queryFacade).query("foo_db", "SHOW SHADOW TABLE RULES FROM foo_db");
+    }
+    
+    @Test
+    void assertQueryAlgorithms() {
+        MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+        new ShadowInspectionService().queryAlgorithms(queryFacade, "foo_db");
+        verify(queryFacade).query("foo_db", "SHOW SHADOW ALGORITHMS FROM foo_db");
+    }
+    
+    @Test
+    void assertQueryDefaultAlgorithm() {
+        MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+        new ShadowInspectionService().queryDefaultAlgorithm(queryFacade, "foo_db");
+        verify(queryFacade).query("foo_db", "SHOW DEFAULT SHADOW ALGORITHM FROM foo_db");
+    }
+    
+    @Test
+    void assertQueryRuleCount() {
+        MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+        new ShadowInspectionService().queryRuleCount(queryFacade, "foo_db");
+        verify(queryFacade).query("foo_db", "COUNT SHADOW RULE FROM foo_db");
+    }
 }
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingTableResourceHandlerTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingTableResourceHandlerTest.java
index 6dd4edfad40..730031e9a93 100644
--- a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingTableResourceHandlerTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingTableResourceHandlerTest.java
@@ -51,7 +51,8 @@ class ShardingTableResourceHandlerTest {
                         ShardingInspectionService.class, (mock, context) -> stubInspectionService(mock, rows))) {
             MCPFeatureRequestContext requestContext = mock(MCPFeatureRequestContext.class);
             when(requestContext.getQueryFacade()).thenReturn(mock(MCPFeatureQueryFacade.class));
-            MCPSuccessPayload actual = handlerSupplier.get().handle(requestContext, uriVariables);
+            ShardingTableResourceHandler handler = handlerSupplier.get();
+            MCPSuccessPayload actual = handler.handle(handler.getContextType().cast(requestContext), uriVariables);
             assertThat(((List<?>) actual.toPayload().get("items")).size(), is(rows.size()));
             assertThat(((Map<?, ?>) actual.toPayload().get("self_resource")).get("uri"), is(expectedSelfUri));
         }
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingTableRuleToolHandlerTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingTableRuleToolHandlerTest.java
index 988f80be1bc..4786d80a41e 100644
--- a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingTableRuleToolHandlerTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingTableRuleToolHandlerTest.java
@@ -47,7 +47,8 @@ class PlanShardingTableRuleToolHandlerTest {
                                 ShardingFeatureDefinition.TABLE_RULE_WORKFLOW_KIND,
                                 "CREATE SHARDING TABLE RULE `t_order`(DATANODES('ds_${0..1}.t_order_${0..1}'))")))) {
             WorkflowContextFixture fixture = ShardingWorkflowToolHandlerTestFixture.createWorkflowContextFixture();
-            MCPSuccessPayload actual = new PlanShardingTableRuleToolHandler().handle(fixture.requestContext(), Map.of(
+            PlanShardingTableRuleToolHandler handler = new PlanShardingTableRuleToolHandler();
+            MCPSuccessPayload actual = handler.handle(handler.getContextType().cast(fixture.requestContext()), Map.of(
                     "database", "logic_db",
                     "table", "t_order",
                     "column", "order_id",
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmRecommendationServiceTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmRecommendationServiceTest.java
index 46c472a3bfe..2291424ee5e 100644
--- a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmRecommendationServiceTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmRecommendationServiceTest.java
@@ -22,10 +22,15 @@ 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.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.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -65,4 +70,24 @@ class ShardingAlgorithmRecommendationServiceTest {
         assertTrue(service.recommend(request, List.of(Map.of("type", "INLINE")), List.of(), true, false, issues).isEmpty());
         assertThat(issues.getFirst().getCode(), is(WorkflowIssueCode.ALGORITHM_NOT_FOUND));
     }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("assertRecommendMissingOrFallbackAlgorithmsArguments")
+    void assertRecommendMissingOrFallbackAlgorithms(final String name, final String keyGeneratorType, final List<Map<String, Object>> algorithms,
+                                                    final List<Map<String, Object>> keyGenerators, final boolean includeSharding, final List<String> expectedTypes,
+                                                    final List<String> expectedIssueCodes) {
+        ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+        request.setKeyGeneratorType(keyGeneratorType);
+        List<WorkflowIssue> issues = new ArrayList<>(expectedIssueCodes.size());
+        List<AlgorithmCandidate> actual = service.recommend(request, algorithms, keyGenerators, includeSharding, true, issues);
+        assertThat(actual.stream().map(AlgorithmCandidate::getAlgorithmType).toList(), is(expectedTypes));
+        assertThat(issues.stream().map(WorkflowIssue::getCode).toList(), is(expectedIssueCodes));
+    }
+    
+    private static Stream<Arguments> assertRecommendMissingOrFallbackAlgorithmsArguments() {
+        return Stream.of(
+                Arguments.of("empty catalogs", "", List.of(), List.of(), true, List.of(), List.of(WorkflowIssueCode.ALGORITHM_NOT_FOUND, WorkflowIssueCode.ALGORITHM_NOT_FOUND)),
+                Arguments.of("fallback algorithms", "", List.of(Map.of("type", "FOO_ALGORITHM")), List.of(Map.of("type", "FOO_GENERATOR")), true, List.of("FOO_ALGORITHM", "FOO_GENERATOR"), List.of()),
+                Arguments.of("unavailable key generator", "FOO_GENERATOR", List.of(), List.of(Map.of("type", "SNOWFLAKE")), false, List.of(), List.of(WorkflowIssueCode.ALGORITHM_NOT_FOUND)));
+    }
 }
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingDistSQLPlanningServiceTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingDistSQLPlanningServiceTest.java
index b6b1993be19..ca92b6e1990 100644
--- a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingDistSQLPlanningServiceTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingDistSQLPlanningServiceTest.java
@@ -196,6 +196,43 @@ class ShardingDistSQLPlanningServiceTest {
         assertThat(new ShardingDistSQLPlanningService().planComponentCleanup(request).getSql(), is("DROP SHARDING KEY GENERATOR `snowflake_generator`"));
     }
     
+    @Test
+    void assertPlanTableReferenceRuleDrop() {
+        ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+        request.setRuleName("foo_reference");
+        RuleArtifact actual = new ShardingDistSQLPlanningService().planTableReferenceRule(request, "drop");
+        assertThat(actual.getSql(), is("DROP SHARDING TABLE REFERENCE RULE `foo_reference`"));
+    }
+    
+    @Test
+    void assertPlanDefaultStrategyDrop() {
+        ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+        request.setDefaultStrategyType("table");
+        RuleArtifact actual = new ShardingDistSQLPlanningService().planDefaultStrategy(request, "drop");
+        assertThat(actual.getSql(), is("DROP DEFAULT SHARDING TABLE STRATEGY"));
+    }
+    
+    @Test
+    void assertPlanKeyGeneratorDrop() {
+        ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+        request.setKeyGeneratorName("foo_generator");
+        RuleArtifact actual = new ShardingDistSQLPlanningService().planKeyGenerator(request, "drop");
+        assertThat(actual.getSql(), is("DROP SHARDING KEY GENERATOR `foo_generator`"));
+    }
+    
+    @Test
+    void assertPlanTableRuleWithAuditors() {
+        ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+        request.setTable("foo_table");
+        request.setDataNodes("foo_ds.foo_table");
+        request.setColumn("foo_column");
+        request.setAlgorithmType("INLINE");
+        request.getAuditorNames().addAll(List.of("foo_auditor", "bar_auditor"));
+        RuleArtifact actual = new ShardingDistSQLPlanningService().planTableRule(request, "create");
+        assertThat(actual.getSql(), is("CREATE SHARDING TABLE RULE `foo_table`(DATANODES('foo_ds.foo_table'), TABLE_STRATEGY(TYPE='standard', SHARDING_COLUMN=`foo_column`, "
+                + "SHARDING_ALGORITHM(TYPE(NAME='inline'))), AUDIT_STRATEGY(TYPE(NAME='foo_auditor'), TYPE(NAME='bar_auditor'), ALLOW_HINT_DISABLE=FALSE))"));
+    }
+    
     private ShardingWorkflowRequest createTableRuleRequest() {
         ShardingWorkflowRequest result = new ShardingWorkflowRequest();
         result.setTable("t_order");
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningServiceTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningServiceTest.java
index cbe3df79557..53ffed3ee81 100644
--- a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningServiceTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningServiceTest.java
@@ -330,11 +330,27 @@ class ShardingWorkflowPlanningServiceTest {
         assertThat(((ShardingWorkflowRequest) actual.getRequest()).getKeyGeneratorProperties(), is(Map.of("worker-id", "1")));
     }
     
-    @Test
-    void assertPlanTableRuleRejectsDataNodeAndStorageUnitInputs() {
-        ShardingWorkflowRequest request = createTableRuleRequest();
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("assertPlanTableRuleConflictingAutoTableInputsArguments")
+    void assertPlanTableRuleConflictingAutoTableInputs(final String name, final String dataNodes, final String strategyType, final String shardingColumns,
+                                                       final List<String> expectedConflictingInputs) {
+        ShardingWorkflowRequest request = createDatabaseRequest();
+        request.setTable("t_order");
+        request.setDataNodes(dataNodes);
         request.setStorageUnits("ds_0");
-        assertTableRuleInputConflict(request, List.of("data_nodes", "storage_units"));
+        request.setStrategyType(strategyType);
+        request.setShardingColumns(shardingColumns);
+        WorkflowContextSnapshot actual = planningService.planTableRule(new TestWorkflowSessionContext(), mock(MCPFeatureQueryFacade.class), request);
+        assertThat(actual.getStatus(), is(WorkflowLifecycle.STATUS_FAILED));
+        assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.RULE_INPUT_CONFLICT));
+        assertThat(actual.getIssues().getFirst().getDetails().get("conflicting_inputs"), is(expectedConflictingInputs));
+    }
+    
+    private static Stream<Arguments> assertPlanTableRuleConflictingAutoTableInputsArguments() {
+        return Stream.of(
+                Arguments.of("data nodes and storage units", "ds_0.t_order", "none", "", List.of("data_nodes", "storage_units")),
+                Arguments.of("complex auto-table strategy", "", "complex", "", List.of("storage_units", "strategy_type")),
+                Arguments.of("auto-table sharding columns", "", "standard", "order_id,user_id", List.of("storage_units", "sharding_columns")));
     }
     
     @Test
@@ -608,6 +624,42 @@ class ShardingWorkflowPlanningServiceTest {
         assertThat(actual.getRuleArtifacts().getFirst().getSql(), is(expectedSQL));
     }
     
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("assertPlanTableRuleRequiredStrategyInputsArguments")
+    void assertPlanTableRuleRequiredStrategyInputs(final String name, final String table, final String dataNodes, final String storageUnits, final String strategyType,
+                                                   final String column, final String shardingColumns, final String algorithmType, final String expectedField, final String expectedMessage) {
+        ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+        request.setDatabase("logic_db");
+        request.setTable(table);
+        request.setDataNodes(dataNodes);
+        request.setStorageUnits(storageUnits);
+        request.setStrategyType(strategyType);
+        request.setColumn(column);
+        request.setShardingColumns(shardingColumns);
+        request.setAlgorithmType(algorithmType);
+        WorkflowContextSnapshot actual = planningService.planTableRule(new TestWorkflowSessionContext(), mock(MCPFeatureQueryFacade.class), request);
+        assertThat(actual.getStatus(), is(WorkflowLifecycle.STATUS_CLARIFYING));
+        assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.RULE_INPUT_REQUIRED));
+        assertThat(actual.getClarifiedIntent().getUnresolvedFields(), is(List.of(expectedField)));
+        assertThat(actual.getClarifiedIntent().getClarificationMessages(), is(List.of(expectedMessage)));
+    }
+    
+    private static Stream<Arguments> assertPlanTableRuleRequiredStrategyInputsArguments() {
+        return Stream.of(
+                Arguments.of("missing table", "", "ds_0.t_order", "", "standard", "order_id", "", "INLINE", "table", "Please provide target logical table."),
+                Arguments.of("missing auto-table column", "t_order", "", "ds_0", "standard", "", "", "HASH_MOD", "column", "Please provide auto table sharding column."),
+                Arguments.of("missing auto-table algorithm", "t_order", "", "ds_0", "standard", "order_id", "", "", "algorithm_type", "Please provide auto table sharding algorithm type."),
+                Arguments.of("missing complex columns", "t_order", "ds_0.t_order", "", "complex", "", "", "COMPLEX_INLINE", "sharding_columns",
+                        "Please provide at least two sharding columns for complex strategy."),
+                Arguments.of("one complex column", "t_order", "ds_0.t_order", "", "complex", "", "order_id", "COMPLEX_INLINE", "sharding_columns",
+                        "Please provide at least two sharding columns for complex strategy."),
+                Arguments.of("missing complex algorithm", "t_order", "ds_0.t_order", "", "complex", "", "order_id,user_id", "", "algorithm_type", "Please provide sharding algorithm type."),
+                Arguments.of("missing hint algorithm", "t_order", "ds_0.t_order", "", "hint", "", "", "", "algorithm_type", "Please provide sharding algorithm type."),
+                Arguments.of("unknown strategy", "t_order", "ds_0.t_order", "", "foo_strategy", "", "", "", "strategy_type", "Please provide strategy_type as standard, complex, hint, or none."),
+                Arguments.of("missing standard column", "t_order", "ds_0.t_order", "", "standard", "", "", "INLINE", "column", "Please provide sharding column."),
+                Arguments.of("missing standard algorithm", "t_order", "ds_0.t_order", "", "standard", "order_id", "", "", "algorithm_type", "Please provide sharding algorithm type."));
+    }
+    
     private ShardingWorkflowRequest createDatabaseRequest() {
         ShardingWorkflowRequest result = new ShardingWorkflowRequest();
         result.setDatabase("logic_db");
diff --git a/mcp/registry/src/test/java/org/apache/shardingsphere/mcp/registry/MCPRegistryMetadataCommandTest.java b/mcp/registry/src/test/java/org/apache/shardingsphere/mcp/registry/MCPRegistryMetadataCommandTest.java
index 357e9e4c160..ef224a59c20 100644
--- a/mcp/registry/src/test/java/org/apache/shardingsphere/mcp/registry/MCPRegistryMetadataCommandTest.java
+++ b/mcp/registry/src/test/java/org/apache/shardingsphere/mcp/registry/MCPRegistryMetadataCommandTest.java
@@ -21,13 +21,18 @@ import org.apache.shardingsphere.infra.util.json.JsonEngine;
 import org.apache.shardingsphere.infra.util.json.JsonTypeReference;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 
 import java.io.IOException;
+import java.net.URISyntaxException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -75,13 +80,85 @@ class MCPRegistryMetadataCommandTest {
     }
     
     @Test
-    void assertExecuteRejectsMissingHttpUrl() throws IOException {
+    void assertExecuteRejectsUnknownArgument() {
+        IllegalArgumentException actual = assertThrows(IllegalArgumentException.class, () -> MCPRegistryMetadataCommand.execute("--unknown"));
+        assertThat(actual.getMessage(), is("Unknown argument: --unknown"));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("assertExecuteRejectsServerFieldsCases")
+    void assertExecuteRejectsServerFields(final String name, final Map<String, Object> fields, final String expectedMessage) throws IOException {
+        Map<String, Object> server = createServerMetadata();
+        server.putAll(fields);
+        Path serverPath = createServerJson(server);
+        IllegalArgumentException actual = assertThrows(IllegalArgumentException.class,
+                () -> MCPRegistryMetadataCommand.execute("--path", serverPath.toString(), "--validate-only", "--allow-snapshot"));
+        assertThat(actual.getMessage(), is(expectedMessage));
+    }
+    
+    private static Stream<Arguments> assertExecuteRejectsServerFieldsCases() {
+        return Stream.of(
+                Arguments.of("schema", Map.of("$schema", "unsupported"), "server.json must use the official MCP Registry schema."),
+                Arguments.of("name", Map.of("name", "io.github.apache/other-mcp"), "server.json name must match the published ShardingSphere MCP server name."),
+                Arguments.of("description type", Map.of("description", 1), "server.json field description must be a non-empty string."),
+                Arguments.of("description length", Map.of("description", "x".repeat(101)), "server.json field description must be at most 100 characters."),
+                Arguments.of("null version", Map.of("version", "null"), "server version must be a non-empty string."),
+                Arguments.of("latest version", Map.of("version", "latest"), "server version must not use latest."),
+                Arguments.of("version range", Map.of("version", "5.5.x"), "server version must be a specific version, not a range."),
+                Arguments.of("packages type", Map.of("packages", "invalid"), "server.json packages must be a non-empty array."),
+                Arguments.of("package type", Map.of("packages", List.of("invalid")), "MCP Registry package must be an object."));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("assertExecuteRejectsPackageFieldsCases")
+    void assertExecuteRejectsPackageFields(final String name, final Map<String, Object> fields, final String expectedMessage) throws IOException {
+        Map<String, Object> server = createServerMetadata();
+        getPackages(server).get(0).putAll(fields);
+        Path serverPath = createServerJson(server);
+        IllegalArgumentException actual = assertThrows(IllegalArgumentException.class,
+                () -> MCPRegistryMetadataCommand.execute("--path", serverPath.toString(), "--validate-only", "--allow-snapshot"));
+        assertThat(actual.getMessage(), is(expectedMessage));
+    }
+    
+    private static Stream<Arguments> assertExecuteRejectsPackageFieldsCases() {
+        return Stream.of(
+                Arguments.of("registry type", Map.of("registryType", "npm"), "MCP Registry package registryType must be oci."),
+                Arguments.of("identifier repository", Map.of("identifier", "ghcr.io/apache/other-mcp:5.5.4-SNAPSHOT"), "OCI identifier must target ghcr.io/apache/shardingsphere-mcp:<tag>."),
+                Arguments.of("identifier version", Map.of("identifier", "ghcr.io/apache/shardingsphere-mcp:5.5.3"), "OCI identifier tag must match the server version."),
+                Arguments.of("transport type", Map.of("transport", Map.of("type", "unsupported")), "MCP Registry package transport type must be stdio or streamable-http."),
+                Arguments.of("environment variables type", Map.of("environmentVariables", "invalid"), "MCP Registry package must define SHARDINGSPHERE_MCP_TRANSPORT."),
+                Arguments.of("missing config environment variable", Map.of("environmentVariables",
+                        List.of(Map.of("name", "SHARDINGSPHERE_MCP_TRANSPORT", "description", "Transport mode", "isRequired", false, "isSecret", false, "format", "string"))),
+                        "MCP Registry package must define SHARDINGSPHERE_MCP_CONFIG."));
+    }
+    
+    @Test
+    void assertExecuteRejectsEmptyRuntimeArguments() throws IOException {
         Map<String, Object> server = createServerMetadata();
-        getPackages(server).get(1).put("transport", Map.of("type", "streamable-http"));
+        getPackages(server).get(1).put("runtimeArguments", List.of());
         Path serverPath = createServerJson(server);
         IllegalArgumentException actual = assertThrows(IllegalArgumentException.class,
                 () -> MCPRegistryMetadataCommand.execute("--path", serverPath.toString(), "--validate-only", "--allow-snapshot"));
-        assertThat(actual.getMessage(), is("streamable-http transport must define a URL."));
+        assertThat(actual.getMessage(), is("streamable-http OCI package must define one Docker port runtime argument."));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("assertExecuteRejectsHttpUrlCases")
+    void assertExecuteRejectsHttpUrl(final String name, final Map<String, Object> transport, final String expectedMessage, final boolean expectedCause) throws IOException {
+        Map<String, Object> server = createServerMetadata();
+        getPackages(server).get(1).put("transport", transport);
+        Path serverPath = createServerJson(server);
+        IllegalArgumentException actual = assertThrows(IllegalArgumentException.class,
+                () -> MCPRegistryMetadataCommand.execute("--path", serverPath.toString(), "--validate-only", "--allow-snapshot"));
+        assertThat(actual.getMessage(), is(expectedMessage));
+        assertThat(actual.getCause() instanceof URISyntaxException, is(expectedCause));
+    }
+    
+    private static Stream<Arguments> assertExecuteRejectsHttpUrlCases() {
+        return Stream.of(
+                Arguments.of("missing URL", Map.of("type", "streamable-http"), "streamable-http transport must define a URL.", false),
+                Arguments.of("unsupported scheme", Map.of("type", "streamable-http", "url", "ftp://127.0.0.1"), "streamable-http transport URL must be an HTTP URL.", false),
+                Arguments.of("invalid URI", Map.of("type", "streamable-http", "url", "http://["), "streamable-http transport URL must be an HTTP URL.", true));
     }
     
     @Test
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 e1db7576f1b..5392934d157 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
@@ -30,14 +30,18 @@ import org.junit.jupiter.api.Test;
 import org.mockito.MockedStatic;
 
 import javax.sql.DataSource;
+import java.io.PrintWriter;
+import java.io.Writer;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
 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.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -52,6 +56,149 @@ import static org.mockito.Mockito.when;
 
 class MCPJdbcDatabaseProfileLoaderTest {
     
+    @Test
+    void assertLoadDataSourceWithUnsupportedCredentials() throws SQLException {
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = createRuntimeDatabaseConfiguration(SupportDatabaseTypeFactoryMocker.createJdbcUrl("FixtureDB"), "1.0", true, true);
+        try (
+                MockedStatic<DatabaseTypeFactory> ignored = SupportDatabaseTypeFactoryMocker.mockByConnectionMetadata();
+                MockedStatic<DatabaseIdentifierContextFactory> mocked = mockStatic(DatabaseIdentifierContextFactory.class)) {
+            mocked.when(() -> DatabaseIdentifierContextFactory.create(any(), any(DataSource.class))).thenAnswer(invocation -> {
+                DataSource actual = invocation.getArgument(1, DataSource.class);
+                assertThrows(SQLFeatureNotSupportedException.class, () -> actual.getConnection("foo_user", "foo_password"));
+                return new DatabaseIdentifierContext(IdentifierCasePolicyFactory.newSensitivePolicySet());
+            });
+            new MCPJdbcDatabaseProfileLoader().load("logic_db", runtimeDatabaseConfig);
+        }
+    }
+    
+    @Test
+    void assertLoadDataSourceWithUnsupportedLogWriter() throws SQLException {
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = createRuntimeDatabaseConfiguration(SupportDatabaseTypeFactoryMocker.createJdbcUrl("FixtureDB"), "1.0", true, true);
+        try (
+                MockedStatic<DatabaseTypeFactory> ignored = SupportDatabaseTypeFactoryMocker.mockByConnectionMetadata();
+                MockedStatic<DatabaseIdentifierContextFactory> mocked = mockStatic(DatabaseIdentifierContextFactory.class)) {
+            mocked.when(() -> DatabaseIdentifierContextFactory.create(any(), any(DataSource.class))).thenAnswer(invocation -> {
+                DataSource actual = invocation.getArgument(1, DataSource.class);
+                assertThrows(SQLFeatureNotSupportedException.class, actual::getLogWriter);
+                return new DatabaseIdentifierContext(IdentifierCasePolicyFactory.newSensitivePolicySet());
+            });
+            new MCPJdbcDatabaseProfileLoader().load("logic_db", runtimeDatabaseConfig);
+        }
+    }
+    
+    @Test
+    void assertLoadDataSourceWithUnsupportedSetLogWriter() throws SQLException {
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = createRuntimeDatabaseConfiguration(SupportDatabaseTypeFactoryMocker.createJdbcUrl("FixtureDB"), "1.0", true, true);
+        try (
+                MockedStatic<DatabaseTypeFactory> ignored = SupportDatabaseTypeFactoryMocker.mockByConnectionMetadata();
+                MockedStatic<DatabaseIdentifierContextFactory> mocked = mockStatic(DatabaseIdentifierContextFactory.class)) {
+            mocked.when(() -> DatabaseIdentifierContextFactory.create(any(), any(DataSource.class))).thenAnswer(invocation -> {
+                DataSource actual = invocation.getArgument(1, DataSource.class);
+                assertThrows(SQLFeatureNotSupportedException.class, () -> actual.setLogWriter(new PrintWriter(Writer.nullWriter())));
+                return new DatabaseIdentifierContext(IdentifierCasePolicyFactory.newSensitivePolicySet());
+            });
+            new MCPJdbcDatabaseProfileLoader().load("logic_db", runtimeDatabaseConfig);
+        }
+    }
+    
+    @Test
+    void assertLoadDataSourceWithUnsupportedLoginTimeout() throws SQLException {
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = createRuntimeDatabaseConfiguration(SupportDatabaseTypeFactoryMocker.createJdbcUrl("FixtureDB"), "1.0", true, true);
+        try (
+                MockedStatic<DatabaseTypeFactory> ignored = SupportDatabaseTypeFactoryMocker.mockByConnectionMetadata();
+                MockedStatic<DatabaseIdentifierContextFactory> mocked = mockStatic(DatabaseIdentifierContextFactory.class)) {
+            mocked.when(() -> DatabaseIdentifierContextFactory.create(any(), any(DataSource.class))).thenAnswer(invocation -> {
+                DataSource actual = invocation.getArgument(1, DataSource.class);
+                assertThrows(SQLFeatureNotSupportedException.class, actual::getLoginTimeout);
+                return new DatabaseIdentifierContext(IdentifierCasePolicyFactory.newSensitivePolicySet());
+            });
+            new MCPJdbcDatabaseProfileLoader().load("logic_db", runtimeDatabaseConfig);
+        }
+    }
+    
+    @Test
+    void assertLoadDataSourceWithUnsupportedSetLoginTimeout() throws SQLException {
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = createRuntimeDatabaseConfiguration(SupportDatabaseTypeFactoryMocker.createJdbcUrl("FixtureDB"), "1.0", true, true);
+        try (
+                MockedStatic<DatabaseTypeFactory> ignored = SupportDatabaseTypeFactoryMocker.mockByConnectionMetadata();
+                MockedStatic<DatabaseIdentifierContextFactory> mocked = mockStatic(DatabaseIdentifierContextFactory.class)) {
+            mocked.when(() -> DatabaseIdentifierContextFactory.create(any(), any(DataSource.class))).thenAnswer(invocation -> {
+                DataSource actual = invocation.getArgument(1, DataSource.class);
+                assertThrows(SQLFeatureNotSupportedException.class, () -> actual.setLoginTimeout(1));
+                return new DatabaseIdentifierContext(IdentifierCasePolicyFactory.newSensitivePolicySet());
+            });
+            new MCPJdbcDatabaseProfileLoader().load("logic_db", runtimeDatabaseConfig);
+        }
+    }
+    
+    @Test
+    void assertLoadDataSourceWithUnsupportedParentLogger() throws SQLException {
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = createRuntimeDatabaseConfiguration(SupportDatabaseTypeFactoryMocker.createJdbcUrl("FixtureDB"), "1.0", true, true);
+        try (
+                MockedStatic<DatabaseTypeFactory> ignored = SupportDatabaseTypeFactoryMocker.mockByConnectionMetadata();
+                MockedStatic<DatabaseIdentifierContextFactory> mocked = mockStatic(DatabaseIdentifierContextFactory.class)) {
+            mocked.when(() -> DatabaseIdentifierContextFactory.create(any(), any(DataSource.class))).thenAnswer(invocation -> {
+                DataSource actual = invocation.getArgument(1, DataSource.class);
+                assertThrows(SQLFeatureNotSupportedException.class, actual::getParentLogger);
+                return new DatabaseIdentifierContext(IdentifierCasePolicyFactory.newSensitivePolicySet());
+            });
+            new MCPJdbcDatabaseProfileLoader().load("logic_db", runtimeDatabaseConfig);
+        }
+    }
+    
+    @Test
+    void assertLoadDataSourceWrapper() throws SQLException {
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = createRuntimeDatabaseConfiguration(SupportDatabaseTypeFactoryMocker.createJdbcUrl("FixtureDB"), "1.0", true, true);
+        try (
+                MockedStatic<DatabaseTypeFactory> ignored = SupportDatabaseTypeFactoryMocker.mockByConnectionMetadata();
+                MockedStatic<DatabaseIdentifierContextFactory> mocked = mockStatic(DatabaseIdentifierContextFactory.class)) {
+            mocked.when(() -> DatabaseIdentifierContextFactory.create(any(), any(DataSource.class))).thenAnswer(invocation -> {
+                DataSource actual = invocation.getArgument(1, DataSource.class);
+                assertTrue(actual.isWrapperFor(DataSource.class));
+                assertFalse(actual.isWrapperFor(Connection.class));
+                assertThat(actual.unwrap(DataSource.class), sameInstance(actual));
+                return new DatabaseIdentifierContext(IdentifierCasePolicyFactory.newSensitivePolicySet());
+            });
+            new MCPJdbcDatabaseProfileLoader().load("logic_db", runtimeDatabaseConfig);
+        }
+    }
+    
+    @Test
+    void assertLoadDataSourceWithUnsupportedWrapper() throws SQLException {
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = createRuntimeDatabaseConfiguration(SupportDatabaseTypeFactoryMocker.createJdbcUrl("FixtureDB"), "1.0", true, true);
+        try (
+                MockedStatic<DatabaseTypeFactory> ignored = SupportDatabaseTypeFactoryMocker.mockByConnectionMetadata();
+                MockedStatic<DatabaseIdentifierContextFactory> mocked = mockStatic(DatabaseIdentifierContextFactory.class)) {
+            mocked.when(() -> DatabaseIdentifierContextFactory.create(any(), any(DataSource.class))).thenAnswer(invocation -> {
+                DataSource actual = invocation.getArgument(1, DataSource.class);
+                SQLException actualException = assertThrows(SQLException.class, () -> actual.unwrap(Connection.class));
+                assertThat(actualException.getMessage(), is("Unable to unwrap runtime database data source to `java.sql.Connection`."));
+                return new DatabaseIdentifierContext(IdentifierCasePolicyFactory.newSensitivePolicySet());
+            });
+            new MCPJdbcDatabaseProfileLoader().load("logic_db", runtimeDatabaseConfig);
+        }
+    }
+    
+    @Test
+    void assertLoadDataSourceWithNonSQLConnectionFailure() throws SQLException {
+        IllegalStateException expectedCause = new IllegalStateException("foo_invalid_config");
+        RuntimeDatabaseConnectionException expected = RuntimeDatabaseConnectionException.invalidConfiguration("logic_db", expectedCause);
+        Connection connection = mock(Connection.class);
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = createRuntimeDatabaseConfiguration(SupportDatabaseTypeFactoryMocker.createJdbcUrl("FixtureDB"), "1.0", true, true, connection);
+        when(runtimeDatabaseConfig.openConnection("logic_db")).thenReturn(connection).thenThrow(expected);
+        try (
+                MockedStatic<DatabaseTypeFactory> ignored = SupportDatabaseTypeFactoryMocker.mockByConnectionMetadata();
+                MockedStatic<DatabaseIdentifierContextFactory> mocked = mockStatic(DatabaseIdentifierContextFactory.class)) {
+            mocked.when(() -> DatabaseIdentifierContextFactory.create(any(), any(DataSource.class))).thenAnswer(invocation -> {
+                DataSource dataSource = invocation.getArgument(1, DataSource.class);
+                SQLException actual = assertThrows(SQLException.class, dataSource::getConnection);
+                assertThat(actual.getCause(), sameInstance(expected));
+                return new DatabaseIdentifierContext(IdentifierCasePolicyFactory.newSensitivePolicySet());
+            });
+            new MCPJdbcDatabaseProfileLoader().load("logic_db", runtimeDatabaseConfig);
+        }
+    }
+    
     @Test
     void assertLoad() throws SQLException {
         DatabaseIdentifierContext expectedIdentifierContext = new DatabaseIdentifierContext(IdentifierCasePolicyFactory.newSensitivePolicySet());
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 19abf413033..08a67c1a9f6 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
@@ -84,6 +84,49 @@ class RuntimeDatabaseValidationServiceTest {
         mockedProfileLoaders.close();
     }
     
+    @Test
+    void assertValidateWithVisibleMetadataCatalog() throws SQLException {
+        RuntimeDatabaseValidationService service = new RuntimeDatabaseValidationService();
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = mock(RuntimeDatabaseConfiguration.class);
+        Connection connection = mock(Connection.class);
+        DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
+        when(runtimeDatabaseConfig.openConnection("logic_db")).thenReturn(connection);
+        when(connection.getMetaData()).thenReturn(databaseMetaData);
+        ResultSet catalogs = mock(ResultSet.class);
+        when(databaseMetaData.getCatalogs()).thenReturn(catalogs);
+        when(catalogs.next()).thenReturn(true, true, false);
+        when(catalogs.getString(1)).thenReturn("foo_other", "logic_db");
+        when(getProfileLoader().load(any(), any(RuntimeDatabaseConfiguration.class))).thenReturn(new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL,
+                new DatabaseIdentifierContext(IdentifierCasePolicyFactory.newSensitivePolicySet())));
+        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));
+        assertThat(actual.getStatus(), is("ready"));
+        verify(catalogs).close();
+    }
+    
+    @Test
+    void assertValidateWithVisibleMetadataSchema() throws SQLException {
+        RuntimeDatabaseValidationService service = new RuntimeDatabaseValidationService();
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = mock(RuntimeDatabaseConfiguration.class);
+        Connection connection = mock(Connection.class);
+        DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
+        when(runtimeDatabaseConfig.openConnection("logic_db")).thenReturn(connection);
+        when(connection.getMetaData()).thenReturn(databaseMetaData);
+        ResultSet catalogs = mock(ResultSet.class);
+        when(databaseMetaData.getCatalogs()).thenReturn(catalogs);
+        ResultSet schemas = mock(ResultSet.class);
+        when(databaseMetaData.getSchemas()).thenReturn(schemas);
+        when(schemas.next()).thenReturn(true, true, false);
+        when(schemas.getString("TABLE_SCHEM")).thenReturn("foo_other", "logic_db");
+        when(getProfileLoader().load(any(), any(RuntimeDatabaseConfiguration.class))).thenReturn(new RuntimeDatabaseProfile("logic_db", "FixtureDB", "1.0", TransactionCapability.LOCAL,
+                new DatabaseIdentifierContext(IdentifierCasePolicyFactory.newSensitivePolicySet())));
+        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));
+        assertThat(actual.getStatus(), is("ready"));
+        verify(catalogs).close();
+        verify(schemas).close();
+    }
+    
     @Test
     void assertValidateWithMissingDatabase() {
         RuntimeDatabaseValidationService service = new RuntimeDatabaseValidationService();
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPCompletionTargetDescriptorValidatorTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPCompletionTargetDescriptorValidatorTest.java
index 783e362fdc5..54dafec7efd 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPCompletionTargetDescriptorValidatorTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPCompletionTargetDescriptorValidatorTest.java
@@ -20,8 +20,13 @@ package org.apache.shardingsphere.mcp.support.descriptor;
 import org.apache.shardingsphere.mcp.api.capability.completion.MCPCompletionTargetDescriptor;
 import org.apache.shardingsphere.mcp.api.capability.prompt.MCPPromptArgumentDescriptor;
 import org.apache.shardingsphere.mcp.api.capability.prompt.MCPPromptDescriptor;
-import org.junit.jupiter.api.Test;
+import org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceAnnotations;
+import org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceDescriptor;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -31,24 +36,50 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 
 class MCPCompletionTargetDescriptorValidatorTest {
     
-    @Test
-    void assertValidateWithUndeclaredContextArgument() {
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("invalidCompletionTargets")
+    void assertValidateWithInvalidCompletionTarget(final String name, final Collection<MCPCompletionTargetDescriptor> descriptors, final Collection<MCPResourceDescriptor> resources,
+                                                   final String expectedMessage) {
         MCPPromptDescriptor prompt = new MCPPromptDescriptor("test_prompt", "Test Prompt", "Guide the model through a test prompt.",
                 List.of(new MCPPromptArgumentDescriptor("database", "Database", "Logical database.", false)), Map.of());
-        MCPCompletionTargetDescriptor completion = new MCPCompletionTargetDescriptor("prompt", "test_prompt", List.of("database"), 50,
-                Map.of(MCPShardingSphereMetadataKeys.REQUIRED_CONTEXT_ARGUMENTS, Map.of("database", List.of("tenant"))));
-        IllegalStateException actual = assertThrows(IllegalStateException.class,
-                () -> MCPCompletionTargetDescriptorValidator.validate(List.of(completion), List.of(prompt), List.of()));
-        assertThat(actual.getMessage(), is("Completion target `prompt:test_prompt` context argument `tenant` for `database` is not declared by the target."));
+        IllegalStateException actual = assertThrows(IllegalStateException.class, () -> MCPCompletionTargetDescriptorValidator.validate(descriptors, List.of(prompt), resources));
+        assertThat(actual.getMessage(), is(expectedMessage));
     }
     
-    @Test
-    void assertValidateWithDuplicateArguments() {
-        MCPPromptDescriptor prompt = new MCPPromptDescriptor("test_prompt", "Test Prompt", "Guide the model through a test prompt.",
-                List.of(new MCPPromptArgumentDescriptor("database", "Database", "Logical database.", false)), Map.of());
-        MCPCompletionTargetDescriptor completion = new MCPCompletionTargetDescriptor("prompt", "test_prompt", List.of("database", "database"), 50, Map.of());
-        IllegalStateException actual = assertThrows(IllegalStateException.class,
-                () -> MCPCompletionTargetDescriptorValidator.validate(List.of(completion), List.of(prompt), List.of()));
-        assertThat(actual.getMessage(), is("Completion target `prompt:test_prompt` must not contain duplicate arguments."));
+    private static Collection<Arguments> invalidCompletionTargets() {
+        MCPCompletionTargetDescriptor completion = new MCPCompletionTargetDescriptor("prompt", "test_prompt", List.of("database"), 50, Map.of());
+        MCPResourceDescriptor fixedResource = new MCPResourceDescriptor("shardingsphere://capabilities", "foo_fixed", "Capabilities", "Read capabilities.", "application/json",
+                MCPResourceAnnotations.EMPTY, Map.of());
+        MCPResourceDescriptor template = new MCPResourceDescriptor("shardingsphere://databases/{database}", "foo_template", "Database", "Read database.", "application/json",
+                MCPResourceAnnotations.EMPTY, Map.of());
+        return List.of(
+                Arguments.of("unknown context argument",
+                        List.of(new MCPCompletionTargetDescriptor("prompt", "test_prompt", List.of("database"), 50,
+                                Map.of(MCPShardingSphereMetadataKeys.REQUIRED_CONTEXT_ARGUMENTS, Map.of("database", List.of("tenant"))))),
+                        List.of(), "Completion target `prompt:test_prompt` context argument `tenant` for `database` is not declared by the target."),
+                Arguments.of("duplicate arguments", List.of(new MCPCompletionTargetDescriptor("prompt", "test_prompt", List.of("database", "database"), 50, Map.of())), List.of(),
+                        "Completion target `prompt:test_prompt` must not contain duplicate arguments."),
+                Arguments.of("duplicate target", List.of(completion, completion), List.of(), "Duplicate MCP completion target `prompt:test_prompt`."),
+                Arguments.of("unknown prompt", List.of(new MCPCompletionTargetDescriptor("prompt", "foo_unknown", List.of("database"), 50, Map.of())), List.of(),
+                        "Completion target references unknown prompt `foo_unknown`."),
+                Arguments.of("unknown resource", List.of(new MCPCompletionTargetDescriptor("resource", "shardingsphere://foo_unknown", List.of("database"), 50, Map.of())), List.of(),
+                        "Completion target references unknown resource `shardingsphere://foo_unknown`."),
+                Arguments.of("unsupported reference", List.of(new MCPCompletionTargetDescriptor("tool", "test_prompt", List.of("database"), 50, Map.of())), List.of(),
+                        "Unsupported completion reference type `tool`."),
+                Arguments.of("fixed resource target", List.of(new MCPCompletionTargetDescriptor("resource", "shardingsphere://capabilities", List.of(), 50, Map.of())), List.of(fixedResource),
+                        "Completion target `resource:shardingsphere://capabilities` must reference a resource template."),
+                Arguments.of("unknown resource argument", List.of(new MCPCompletionTargetDescriptor("resource", "shardingsphere://databases/{database}", List.of("schema"), 50, Map.of())),
+                        List.of(template), "Completion target `resource:shardingsphere://databases/{database}` argument `schema` is not a URI template variable."),
+                Arguments.of("context metadata is not object",
+                        List.of(new MCPCompletionTargetDescriptor("prompt", "test_prompt", List.of("database"), 50, Map.of(MCPShardingSphereMetadataKeys.REQUIRED_CONTEXT_ARGUMENTS, "database"))),
+                        List.of(), "Completion target `prompt:test_prompt` metadata `org.apache.shardingsphere/required-context-arguments` must be an object."),
+                Arguments.of("unknown context metadata key",
+                        List.of(new MCPCompletionTargetDescriptor("prompt", "test_prompt", List.of("database"), 50,
+                                Map.of(MCPShardingSphereMetadataKeys.REQUIRED_CONTEXT_ARGUMENTS, Map.of("schema", List.of())))),
+                        List.of(), "Completion target `prompt:test_prompt` required context argument `schema` is not declared by the target."),
+                Arguments.of("context value is not list",
+                        List.of(new MCPCompletionTargetDescriptor("prompt", "test_prompt", List.of("database"), 50,
+                                Map.of(MCPShardingSphereMetadataKeys.REQUIRED_CONTEXT_ARGUMENTS, Map.of("database", "schema")))),
+                        List.of(), "Completion target `prompt:test_prompt` required context for `database` must be a list."));
     }
 }
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogIndexTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogIndexTest.java
index 3a48e2ff0f5..4eca72f22f1 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogIndexTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogIndexTest.java
@@ -35,6 +35,18 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class MCPDescriptorCatalogIndexTest {
     
+    @Test
+    void assertGetRequiredShardingSphereResourceMetadataWithUnknownResource() {
+        IllegalStateException actual = assertThrows(IllegalStateException.class, () -> MCPDescriptorCatalogIndex.getRequiredShardingSphereResourceMetadata("shardingsphere://foo_unknown"));
+        assertThat(actual.getMessage(), is("ShardingSphere MCP resource metadata descriptor is required for `shardingsphere://foo_unknown`."));
+    }
+    
+    @Test
+    void assertGetRequiredPromptTemplateBindingWithUnknownPrompt() {
+        IllegalStateException actual = assertThrows(IllegalStateException.class, () -> MCPDescriptorCatalogIndex.getRequiredPromptTemplateBinding("foo_unknown"));
+        assertThat(actual.getMessage(), is("MCP prompt template binding is required for `foo_unknown`."));
+    }
+    
     @Test
     void assertGetResourceDescriptors() {
         Collection<MCPResourceDescriptor> actualDescriptors = MCPDescriptorCatalogIndex.getResourceDescriptors();
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogLoaderTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogLoaderTest.java
index c39a9899e87..6ae1e29a69b 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogLoaderTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogLoaderTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.shardingsphere.mcp.support.descriptor;
 
+import org.apache.shardingsphere.infra.util.directory.ClasspathResourceDirectoryReader;
 import org.apache.shardingsphere.mcp.api.capability.completion.MCPCompletionTargetDescriptor;
 import org.apache.shardingsphere.mcp.api.capability.prompt.MCPPromptArgumentDescriptor;
 import org.apache.shardingsphere.mcp.api.capability.prompt.MCPPromptDescriptor;
@@ -24,22 +25,137 @@ import org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceAnnotati
 import org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceDescriptor;
 import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolDescriptor;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.MockedStatic;
 
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 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.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
 
 class MCPDescriptorCatalogLoaderTest {
     
+    private static final String DESCRIPTOR_DIRECTORY = "META-INF/shardingsphere-mcp/mcp-descriptors";
+    
+    @TempDir
+    private Path tempDir;
+    
+    @Test
+    void assertLoadYmlResourceFiltersNonYamlFiles() throws IOException {
+        Path descriptor = createDescriptorResource("foo-public-loader.yml", """
+                tools:
+                  - name: foo_tool
+                    title: Foo Tool
+                    description: Read the foo tool.
+                    inputSchema:
+                      type: object
+                      properties: {}
+                      required: []
+                      additionalProperties: false
+                    outputSchema:
+                      type: object
+                      properties:
+                        foo_value:
+                          type: string
+                          description: Foo value.
+                      examples:
+                        - foo_value: foo_value
+                    annotations:
+                      readOnlyHint: true
+                      destructiveHint: false
+                      idempotentHint: true
+                      openWorldHint: true
+                """);
+        Files.writeString(descriptor.resolveSibling("foo.txt"), "foo_ignored");
+        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+        try (
+                URLClassLoader classLoader = new URLClassLoader(new URL[]{tempDir.toUri().toURL()}, originalClassLoader);
+                MockedStatic<ClasspathResourceDirectoryReader> mocked = mockStatic(ClasspathResourceDirectoryReader.class)) {
+            mocked.when(() -> ClasspathResourceDirectoryReader.read(DESCRIPTOR_DIRECTORY)).thenReturn(Stream.of(DESCRIPTOR_DIRECTORY + "/foo-public-loader.yml", DESCRIPTOR_DIRECTORY + "/foo.txt"));
+            Thread.currentThread().setContextClassLoader(classLoader);
+            MCPDescriptorCatalog actual = MCPDescriptorCatalogLoader.load();
+            assertThat(actual.getProtocolDescriptors().getToolDescriptors().stream().map(MCPToolDescriptor::getName).toList(), is(List.of("foo_tool")));
+        } finally {
+            Thread.currentThread().setContextClassLoader(originalClassLoader);
+        }
+    }
+    
+    private Path createDescriptorResource(final String fileName, final String yaml) throws IOException {
+        Path result = tempDir.resolve(DESCRIPTOR_DIRECTORY).resolve(fileName);
+        Files.createDirectories(result.getParent());
+        Files.writeString(result, yaml);
+        return result;
+    }
+    
+    @Test
+    void assertLoadWithResourceReadFailure() throws IOException {
+        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+        ClassLoader classLoader = mock(ClassLoader.class);
+        URL resource = mock(URL.class);
+        IOException expectedCause = new IOException("foo_read_failure");
+        when(resource.toExternalForm()).thenReturn("file:/foo.yaml");
+        when(resource.openStream()).thenThrow(expectedCause);
+        when(classLoader.resources(DESCRIPTOR_DIRECTORY + "/foo.yaml")).thenReturn(Stream.of(resource));
+        try (MockedStatic<ClasspathResourceDirectoryReader> mocked = mockStatic(ClasspathResourceDirectoryReader.class)) {
+            mocked.when(() -> ClasspathResourceDirectoryReader.read(DESCRIPTOR_DIRECTORY)).thenReturn(Stream.of(DESCRIPTOR_DIRECTORY + "/foo.yaml"));
+            Thread.currentThread().setContextClassLoader(classLoader);
+            IllegalStateException actual = assertThrows(IllegalStateException.class, MCPDescriptorCatalogLoader::load);
+            assertThat(actual.getMessage(), is("Failed to load MCP descriptor resource `file:/foo.yaml`."));
+            assertThat(actual.getCause(), sameInstance(expectedCause));
+        } finally {
+            Thread.currentThread().setContextClassLoader(originalClassLoader);
+        }
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("invalidValueTypes")
+    void assertLoadWithInvalidValueType(final String name, final String yaml, final String expectedMessage) throws IOException {
+        Path descriptor = createDescriptorResource("foo-public-loader.yaml", yaml);
+        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+        try (
+                URLClassLoader classLoader = new URLClassLoader(new URL[]{tempDir.toUri().toURL()}, originalClassLoader);
+                MockedStatic<ClasspathResourceDirectoryReader> mocked = mockStatic(ClasspathResourceDirectoryReader.class)) {
+            mocked.when(() -> ClasspathResourceDirectoryReader.read(DESCRIPTOR_DIRECTORY)).thenReturn(Stream.of(DESCRIPTOR_DIRECTORY + "/foo-public-loader.yaml"));
+            Thread.currentThread().setContextClassLoader(classLoader);
+            IllegalArgumentException actual = assertThrows(IllegalArgumentException.class, MCPDescriptorCatalogLoader::load);
+            assertThat(actual.getMessage(), is(String.format(expectedMessage, descriptor.toUri().toURL().toExternalForm())));
+        } finally {
+            Thread.currentThread().setContextClassLoader(originalClassLoader);
+        }
+    }
+    
+    private static Stream<Arguments> invalidValueTypes() {
+        return Stream.of(
+                Arguments.of("audience type", "resources: [{annotations: {audience: assistant}}]", "MCP descriptor resource `%s` expects list at `$.resources[0].annotations.audience`."),
+                Arguments.of("audience member", "resources: [{annotations: {audience: [1]}}]", "MCP descriptor resource `%s` expects string at `$.resources[0].annotations.audience[0]`."),
+                Arguments.of("priority type", "resources: [{annotations: {priority: foo}}]", "MCP descriptor resource `%s` expects number at `$.resources[0].annotations.priority`."),
+                Arguments.of("lastModified type", "resources: [{annotations: {lastModified: 1}}]", "MCP descriptor resource `%s` expects string at `$.resources[0].annotations.lastModified`."),
+                Arguments.of("tool title type", "tools: [{annotations: {title: 1, readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true}}]",
+                        "MCP descriptor resource `%s` expects string at `$.tools[0].annotations.title`."),
+                Arguments.of("tools collection type", "tools: foo", "MCP descriptor resource `%s` expects list at `$.tools`."));
+    }
+    
     @Test
     void assertLoadValidatesDescriptorQuality() {
         MCPDescriptorCatalog actual = MCPDescriptorCatalogLoader.load();
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptDescriptorValidatorTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptDescriptorValidatorTest.java
index 1a568459bef..ce0ce959ff1 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptDescriptorValidatorTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptDescriptorValidatorTest.java
@@ -18,8 +18,11 @@
 package org.apache.shardingsphere.mcp.support.descriptor;
 
 import org.apache.shardingsphere.mcp.api.capability.prompt.MCPPromptDescriptor;
-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.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -29,11 +32,27 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 
 class MCPPromptDescriptorValidatorTest {
     
-    @Test
-    void assertValidate() {
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("invalidPrompts")
+    void assertValidateWithInvalidPrompt(final String name, final Collection<MCPPromptDescriptor> descriptors, final Collection<MCPPromptTemplateBinding> bindings,
+                                         final String expectedMessage) {
+        IllegalStateException actual = assertThrows(IllegalStateException.class, () -> MCPPromptDescriptorValidator.validate(descriptors, bindings));
+        assertThat(actual.getMessage(), is(expectedMessage));
+    }
+    
+    private static Collection<Arguments> invalidPrompts() {
         MCPPromptDescriptor prompt = new MCPPromptDescriptor("test_prompt", "Test Prompt", "Guide the model through a test prompt.", List.of(), Map.of());
-        MCPPromptTemplateBinding binding = new MCPPromptTemplateBinding("test_prompt", "META-INF/shardingsphere-mcp/prompts/fixture-single-brace-placeholder.md");
-        IllegalStateException actual = assertThrows(IllegalStateException.class, () -> MCPPromptDescriptorValidator.validate(List.of(prompt), List.of(binding)));
-        assertThat(actual.getMessage(), is("Prompt template `META-INF/shardingsphere-mcp/prompts/fixture-single-brace-placeholder.md` contains unsupported model-facing placeholder `{database}`."));
+        MCPPromptTemplateBinding binding = new MCPPromptTemplateBinding("test_prompt", "META-INF/shardingsphere-mcp/prompts/fixture-uri-template.md");
+        return List.of(
+                Arguments.of("unsupported placeholder", List.of(prompt),
+                        List.of(new MCPPromptTemplateBinding("test_prompt", "META-INF/shardingsphere-mcp/prompts/fixture-single-brace-placeholder.md")),
+                        "Prompt template `META-INF/shardingsphere-mcp/prompts/fixture-single-brace-placeholder.md` contains unsupported model-facing placeholder `{database}`."),
+                Arguments.of("missing binding", List.of(prompt), List.of(), "Prompt `test_prompt` must declare an internal template binding."),
+                Arguments.of("duplicate prompt", List.of(prompt, prompt), List.of(binding), "Duplicate MCP prompt descriptor `test_prompt`."),
+                Arguments.of("undeclared placeholder", List.of(prompt), List.of(new MCPPromptTemplateBinding("test_prompt", "META-INF/shardingsphere-mcp/prompts/fixture-markdown-resource.md")),
+                        "Prompt template `META-INF/shardingsphere-mcp/prompts/fixture-markdown-resource.md` has undeclared placeholder `database`."),
+                Arguments.of("client form metadata is not a list", List.of(new MCPPromptDescriptor("test_prompt", "Test Prompt", "Guide the model through a test prompt.", List.of(),
+                        Map.of("org.apache.shardingsphere/client-form-only-arguments", "database"))), List.of(binding),
+                        "Prompt `test_prompt` metadata `org.apache.shardingsphere/client-form-only-arguments` must be a list."));
     }
 }
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceDescriptorValidatorTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceDescriptorValidatorTest.java
index e50641f3956..09a8fb8022a 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceDescriptorValidatorTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceDescriptorValidatorTest.java
@@ -19,8 +19,11 @@ package org.apache.shardingsphere.mcp.support.descriptor;
 
 import org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceAnnotations;
 import org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceDescriptor;
-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.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -30,13 +33,43 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 
 class MCPResourceDescriptorValidatorTest {
     
-    @Test
-    void assertValidate() {
-        MCPResourceDescriptor descriptor = new MCPResourceDescriptor("shardingsphere://capabilities", "server-capability-catalog", "Server Capability Catalog",
-                "Read the model-facing capability catalog.", "application/json", MCPResourceAnnotations.EMPTY, Map.of());
-        MCPDescriptorCatalog catalog = new MCPDescriptorCatalog(new MCPProtocolDescriptorCatalog(List.of(descriptor), List.of(descriptor), List.of(), List.of()),
-                new MCPShardingSphereDescriptorCatalog(List.of(), List.of(), List.of(), List.of(), List.of()));
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("invalidResources")
+    void assertValidateWithInvalidResource(final String name, final MCPDescriptorCatalog catalog, final String expectedMessage) {
         IllegalStateException actual = assertThrows(IllegalStateException.class, () -> MCPResourceDescriptorValidator.validate(catalog));
-        assertThat(actual.getMessage(), is("Resource template `shardingsphere://capabilities` must contain template variables."));
+        assertThat(actual.getMessage(), is(expectedMessage));
+    }
+    
+    private static Collection<Arguments> invalidResources() {
+        MCPResourceDescriptor fixedResource = createResource("shardingsphere://capabilities");
+        MCPResourceDescriptor template = createResource("shardingsphere://databases/{database}");
+        MCPUriVariableDescriptor variable = new MCPUriVariableDescriptor("database", "Database", "Logical database.", true, "database");
+        return List.of(
+                Arguments.of("template without variables", createCatalog(List.of(fixedResource), List.of(fixedResource), List.of()),
+                        "Resource template `shardingsphere://capabilities` must contain template variables."),
+                Arguments.of("fixed resource with variables", createCatalog(List.of(template), List.of(), List.of()),
+                        "Fixed resource `shardingsphere://databases/{database}` must not contain template variables."),
+                Arguments.of("blank URI", createCatalog(List.of(createResource(" ")), List.of(), List.of()), "Resource URI is required."),
+                Arguments.of("duplicate URI", createCatalog(List.of(fixedResource, fixedResource), List.of(), List.of()), "Duplicate MCP resource descriptor `shardingsphere://capabilities`."),
+                Arguments.of("missing variable metadata", createCatalog(List.of(), List.of(template), List.of()),
+                        "Resource descriptor `shardingsphere://databases/{database}` must describe URI template variable `database`."),
+                Arguments.of("optional variable", createCatalog(List.of(), List.of(template), List.of(new MCPUriVariableDescriptor("database", "Database", "Logical database.", false, "database"))),
+                        "Resource parameter `shardingsphere://databases/{database}.database` must be required because URI template variables are required."),
+                Arguments.of("non-template variable", createCatalog(List.of(), List.of(template), List.of(new MCPUriVariableDescriptor("schema", "Schema", "Logical schema.", true, "schema"))),
+                        "Resource descriptor `shardingsphere://databases/{database}` declares non-template parameter `schema`."),
+                Arguments.of("duplicate variable metadata", createCatalog(List.of(), List.of(template), List.of(variable, variable)),
+                        "Duplicate MCP resource parameter `shardingsphere://databases/{database}.database`."));
+    }
+    
+    private static MCPResourceDescriptor createResource(final String uri) {
+        return new MCPResourceDescriptor(uri, "foo_resource", "Resource", "Read resource.", "application/json", MCPResourceAnnotations.EMPTY, Map.of());
+    }
+    
+    private static MCPDescriptorCatalog createCatalog(final Collection<MCPResourceDescriptor> resources, final Collection<MCPResourceDescriptor> templates,
+                                                      final Collection<MCPUriVariableDescriptor> variables) {
+        Collection<ShardingSphereMCPResourceMetadata> metadata = templates.stream().map(each -> new ShardingSphereMCPResourceMetadata(each.getUriTemplate(), variables,
+                "detail", "database", "", List.of(), List.of(), List.of())).toList();
+        return new MCPDescriptorCatalog(new MCPProtocolDescriptorCatalog(resources, templates, List.of(), List.of()),
+                new MCPShardingSphereDescriptorCatalog(metadata, List.of(), List.of(), List.of(), List.of()));
     }
 }
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationDescriptorValidatorTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationDescriptorValidatorTest.java
index 2bb573fca7c..53fbc8c4743 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationDescriptorValidatorTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationDescriptorValidatorTest.java
@@ -19,8 +19,11 @@ package org.apache.shardingsphere.mcp.support.descriptor;
 
 import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolAnnotations;
 import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolDescriptor;
-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.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -30,30 +33,27 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 
 class MCPResourceNavigationDescriptorValidatorTest {
     
-    @Test
-    void assertValidateWithUnknownSource() {
-        MCPResourceNavigationDescriptor navigation = new MCPResourceNavigationDescriptor("missing", "database_gateway_test_tool", List.of(), List.of(), "Read test tool.");
-        IllegalStateException actual = assertThrows(IllegalStateException.class,
-                () -> MCPResourceNavigationDescriptorValidator.validate(List.of(navigation), createCatalog()));
-        assertThat(actual.getMessage(), is("Resource navigation references unknown source `missing`."));
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("invalidNavigations")
+    void assertValidateWithInvalidNavigation(final String name, final Collection<MCPResourceNavigationDescriptor> descriptors, final String expectedMessage) {
+        IllegalStateException actual = assertThrows(IllegalStateException.class, () -> MCPResourceNavigationDescriptorValidator.validate(descriptors, createCatalog()));
+        assertThat(actual.getMessage(), is(expectedMessage));
     }
     
-    @Test
-    void assertValidateWithDuplicateRequiredArguments() {
-        MCPResourceNavigationDescriptor navigation = new MCPResourceNavigationDescriptor("database_gateway_test_tool", "database_gateway_test_tool",
-                List.of("database", "database"), List.of(), "Read test tool.");
-        IllegalStateException actual = assertThrows(IllegalStateException.class,
-                () -> MCPResourceNavigationDescriptorValidator.validate(List.of(navigation), createCatalog()));
-        assertThat(actual.getMessage(), is("Resource navigation `database_gateway_test_tool` to `database_gateway_test_tool` must not contain duplicate required arguments."));
-    }
-    
-    @Test
-    void assertValidateWithDuplicateCarriedArguments() {
-        MCPResourceNavigationDescriptor navigation = new MCPResourceNavigationDescriptor("database_gateway_test_tool", "database_gateway_test_tool",
-                List.of(), List.of("database", "database"), "Read test tool.");
-        IllegalStateException actual = assertThrows(IllegalStateException.class,
-                () -> MCPResourceNavigationDescriptorValidator.validate(List.of(navigation), createCatalog()));
-        assertThat(actual.getMessage(), is("Resource navigation `database_gateway_test_tool` to `database_gateway_test_tool` must not contain duplicate carried arguments."));
+    private static Collection<Arguments> invalidNavigations() {
+        MCPResourceNavigationDescriptor navigation = new MCPResourceNavigationDescriptor("database_gateway_test_tool", "database_gateway_test_tool", List.of(), List.of(), "Read test tool.");
+        return List.of(
+                Arguments.of("unknown source", List.of(new MCPResourceNavigationDescriptor("missing", "database_gateway_test_tool", List.of(), List.of(), "Read test tool.")),
+                        "Resource navigation references unknown source `missing`."),
+                Arguments.of("unknown target", List.of(new MCPResourceNavigationDescriptor("database_gateway_test_tool", "missing", List.of(), List.of(), "Read test tool.")),
+                        "Resource navigation references unknown target `missing`."),
+                Arguments.of("duplicate required arguments", List.of(new MCPResourceNavigationDescriptor("database_gateway_test_tool", "database_gateway_test_tool",
+                        List.of("database", "database"), List.of(), "Read test tool.")),
+                        "Resource navigation `database_gateway_test_tool` to `database_gateway_test_tool` must not contain duplicate required arguments."),
+                Arguments.of("duplicate carried arguments", List.of(new MCPResourceNavigationDescriptor("database_gateway_test_tool", "database_gateway_test_tool",
+                        List.of(), List.of("database", "database"), "Read test tool.")),
+                        "Resource navigation `database_gateway_test_tool` to `database_gateway_test_tool` must not contain duplicate carried arguments."),
+                Arguments.of("duplicate endpoints", List.of(navigation, navigation), "Duplicate MCP resource navigation `database_gateway_test_tool` to `database_gateway_test_tool`."));
     }
     
     private MCPDescriptorCatalog createCatalog() {
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorCatalogValidatorTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorCatalogValidatorTest.java
index 89823b62677..288822a9d64 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorCatalogValidatorTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorCatalogValidatorTest.java
@@ -17,12 +17,17 @@
 
 package org.apache.shardingsphere.mcp.support.descriptor;
 
+import org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceAnnotations;
+import org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceDescriptor;
 import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolAnnotations;
 import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolDescriptor;
 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 org.junit.jupiter.params.provider.ValueSource;
 
+import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -66,6 +71,141 @@ class MCPToolDescriptorCatalogValidatorTest {
         assertThat(actual.getMessage(), is("Tool `database_gateway_test_tool` inputSchema additionalProperties must be a boolean."));
     }
     
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("invalidCoreTools")
+    void assertValidateWithInvalidCoreTool(final String name, final String toolName, final Map<String, Object> inputSchema, final Map<String, Object> outputSchema, final String expectedMessage) {
+        MCPToolDescriptor descriptor = new MCPToolDescriptor(toolName, "Test Tool", "Run a test tool.", inputSchema, outputSchema, MCPToolAnnotations.builder().build(), Map.of());
+        MCPDescriptorCatalog catalog = new MCPDescriptorCatalog(new MCPProtocolDescriptorCatalog(List.of(), List.of(), List.of(descriptor), List.of()),
+                new MCPShardingSphereDescriptorCatalog(List.of(), List.of(), List.of(), List.of(), List.of()));
+        IllegalStateException actual = assertThrows(IllegalStateException.class, () -> MCPToolDescriptorCatalogValidator.validate(catalog));
+        assertThat(actual.getMessage(), is(expectedMessage));
+    }
+    
+    private static Collection<Arguments> invalidCoreTools() {
+        Map<String, Object> inputSchema = createInputSchema();
+        Map<String, Object> searchOutput = createOutputSchema(List.of("response_mode", "items", "count", "search_context", "total_match_count"));
+        Map<String, Object> queryOutput = createOutputSchema(List.of("response_mode", "result_kind", "statement_class", "statement_type", "status", "returned_row_count",
+                "applied_max_rows", "applied_timeout_ms", "truncated", "next_actions"));
+        Map<String, Object> updateOutput = createOutputSchema(List.of("response_mode", "result_kind", "statement_class", "statement_type", "status", "columns", "rows", "returned_row_count",
+                "applied_max_rows", "applied_timeout_ms", "suggested_arguments", "next_actions"));
+        Map<String, Object> runtimeOutput = createOutputSchema(List.of("response_mode", "status", "database", "checks", "category", "recovery"));
+        Map<String, Object> validRuntimeOutput = replaceProperty(runtimeOutput, "response_mode", Map.of("type", "string", "description", "Mode.", "enum", List.of("validation")));
+        Map<String, Object> databaseInput = Map.of("type", "object", "properties", Map.of("database", Map.of("type", "string", "description", "Database.")),
+                "required", List.of("database"), "additionalProperties", false);
+        return List.of(
+                Arguments.of("search items schema is not object", "database_gateway_search_metadata", inputSchema,
+                        replaceProperty(searchOutput, "items", Map.of("type", "array", "description", "Items.", "items", true)),
+                        "Tool `database_gateway_search_metadata` outputSchema property `items.items` must be an object."),
+                Arguments.of("search item properties missing", "database_gateway_search_metadata", inputSchema,
+                        replaceProperty(searchOutput, "items", Map.of("type", "array", "description", "Items.", "items", Map.of("type", "object"))),
+                        "Tool `database_gateway_search_metadata` outputSchema property `items.items.properties` must declare properties."),
+                Arguments.of("search item field missing", "database_gateway_search_metadata", inputSchema,
+                        replaceProperty(searchOutput, "items",
+                                Map.of("type", "array", "description", "Items.", "items", Map.of("type", "object", "properties", Map.of("foo", Map.of("description", "Field."))))),
+                        "Tool `database_gateway_search_metadata` outputSchema item must declare `database`."),
+                Arguments.of("search item field is not object", "database_gateway_search_metadata", inputSchema,
+                        replaceProperty(searchOutput, "items", Map.of("type", "array", "description", "Items.", "items", Map.of("type", "object", "properties", Map.of("database", true)))),
+                        "Tool `database_gateway_search_metadata` outputSchema item property `database` must be an object."),
+                Arguments.of("explain database missing", "database_gateway_execute_explain_query", inputSchema, queryOutput, "Tool `database_gateway_execute_explain_query` must declare `database`."),
+                Arguments.of("explain database optional", "database_gateway_execute_explain_query",
+                        Map.of("type", "object", "properties", Map.of("database", Map.of("type", "string", "description", "Database.")), "required", List.of(), "additionalProperties", false),
+                        queryOutput, "Tool `database_gateway_execute_explain_query` `database` must be required."),
+                Arguments.of("update mode missing", "database_gateway_execute_update", inputSchema, updateOutput, "Tool `database_gateway_execute_update` must declare execution_mode."),
+                Arguments.of("update mode optional", "database_gateway_execute_update", createModeInputSchema(List.of()), updateOutput,
+                        "Tool `database_gateway_execute_update` execution_mode must be required."),
+                Arguments.of("update mode enum missing", "database_gateway_execute_update",
+                        Map.of("type", "object", "properties", Map.of("execution_mode", Map.of("type", "string", "description", "Mode.")), "required", List.of("execution_mode"),
+                                "additionalProperties", false),
+                        updateOutput, "Tool `database_gateway_execute_update` execution_mode must allow execute and preview."),
+                Arguments.of("runtime response mode enum missing", "database_gateway_validate_runtime_database", databaseInput, runtimeOutput,
+                        "Tool `database_gateway_validate_runtime_database` response_mode must allow validation."),
+                Arguments.of("runtime database missing", "database_gateway_validate_runtime_database", inputSchema, validRuntimeOutput,
+                        "Tool `database_gateway_validate_runtime_database` must declare `database`."),
+                Arguments.of("runtime database optional", "database_gateway_validate_runtime_database",
+                        Map.of("type", "object", "properties", Map.of("database", Map.of("type", "string", "description", "Database.")), "required", List.of(), "additionalProperties", false),
+                        validRuntimeOutput, "Tool `database_gateway_validate_runtime_database` database must be required."),
+                Arguments.of("runtime credential exposed", "database_gateway_validate_runtime_database",
+                        Map.of("type", "object", "properties",
+                                Map.of("database", Map.of("type", "string", "description", "Database."), "password", Map.of("type", "string", "description", "Password.")), "required",
+                                List.of("database"), "additionalProperties", false),
+                        validRuntimeOutput, "Tool `database_gateway_validate_runtime_database` must not expose `password`."),
+                Arguments.of("runtime check items missing", "database_gateway_validate_runtime_database", databaseInput,
+                        replaceProperty(validRuntimeOutput, "checks", Map.of("type", "array", "description", "Checks.")),
+                        "Tool `database_gateway_validate_runtime_database` outputSchema property `checks.items` must be an object."),
+                Arguments.of("runtime check properties missing", "database_gateway_validate_runtime_database", databaseInput,
+                        replaceProperty(validRuntimeOutput, "checks", Map.of("type", "array", "description", "Checks.", "items", Map.of("type", "object"))),
+                        "Tool `database_gateway_validate_runtime_database` outputSchema property `checks.items.properties` must declare properties."),
+                Arguments.of("runtime check field missing", "database_gateway_validate_runtime_database", databaseInput,
+                        replaceProperty(validRuntimeOutput, "checks",
+                                Map.of("type", "array", "description", "Checks.", "items", Map.of("type", "object", "properties", Map.of("foo", Map.of("description", "Field."))))),
+                        "Tool `database_gateway_validate_runtime_database` outputSchema check item must declare `name`."));
+    }
+    
+    private static Map<String, Object> createOutputSchema(final Collection<String> fields) {
+        Map<String, Object> props = new LinkedHashMap<>(fields.size(), 1F);
+        for (String each : fields) {
+            props.put(each, Map.of("type", "string", "description", "Output field."));
+        }
+        if (props.containsKey("next_actions")) {
+            props.put("next_actions", Map.of("type", "array", "description", "Next steps.", "items", Map.of("type", "object", "properties",
+                    Map.of("order", Map.of("description", "Order."), "type", Map.of("description", "Type."), "title", Map.of("description", "Title.")))));
+        }
+        return Map.of("type", "object", "properties", props, "examples", List.of(Map.of("status", "ok")));
+    }
+    
+    @SuppressWarnings("unchecked")
+    private static Map<String, Object> replaceProperty(final Map<String, Object> schema, final String name, final Object value) {
+        Map<String, Object> props = new LinkedHashMap<>((Map<String, Object>) schema.get("properties"));
+        props.put(name, value);
+        return Map.of("type", "object", "properties", props, "examples", schema.get("examples"));
+    }
+    
+    private static Map<String, Object> createModeInputSchema(final Collection<String> required) {
+        return Map.of("type", "object", "properties", Map.of("execution_mode", Map.of("type", "string", "description", "Mode.", "enum", List.of("execute", "preview"))),
+                "required", required, "additionalProperties", false);
+    }
+    
+    @Test
+    void assertValidateWithRelatedResourceMetadataType() {
+        MCPToolDescriptor descriptor = new MCPToolDescriptor("database_gateway_test_tool", "Test Tool", "Run a test tool.", createInputSchema(), createOutputSchema(List.of("status")),
+                MCPToolAnnotations.builder().build(), Map.of(MCPShardingSphereMetadataKeys.RELATED_RESOURCE_URIS, "shardingsphere://foo_resource"));
+        MCPDescriptorCatalog catalog = new MCPDescriptorCatalog(new MCPProtocolDescriptorCatalog(List.of(), List.of(), List.of(descriptor), List.of()),
+                new MCPShardingSphereDescriptorCatalog(List.of(), List.of(), List.of(), List.of(), List.of()));
+        IllegalStateException actual = assertThrows(IllegalStateException.class, () -> MCPToolDescriptorCatalogValidator.validate(catalog));
+        assertThat(actual.getMessage(), is("Tool `database_gateway_test_tool` metadata `org.apache.shardingsphere/related-resource-uris` must be a list."));
+    }
+    
+    @Test
+    void assertValidateWithRelatedResourceWithoutMetadata() {
+        MCPToolDescriptor descriptor = new MCPToolDescriptor("database_gateway_test_tool", "Test Tool", "Run a test tool.", createInputSchema(), createOutputSchema(List.of("status")),
+                MCPToolAnnotations.builder().build(), Map.of(MCPShardingSphereMetadataKeys.RELATED_RESOURCE_URIS, List.of("shardingsphere://foo_resource")));
+        MCPResourceDescriptor resource =
+                new MCPResourceDescriptor("shardingsphere://foo_resource", "foo_resource", "Resource", "Read resource.", "application/json", MCPResourceAnnotations.EMPTY, Map.of());
+        MCPDescriptorCatalog catalog = new MCPDescriptorCatalog(new MCPProtocolDescriptorCatalog(List.of(resource), List.of(), List.of(descriptor), List.of()),
+                new MCPShardingSphereDescriptorCatalog(List.of(), List.of(), List.of(), List.of(), List.of()));
+        IllegalStateException actual = assertThrows(IllegalStateException.class, () -> MCPToolDescriptorCatalogValidator.validate(catalog));
+        assertThat(actual.getMessage(), is(
+                "Tool `database_gateway_test_tool` metadata `org.apache.shardingsphere/related-resource-uris` references resource `shardingsphere://foo_resource` without ShardingSphere metadata."));
+    }
+    
+    @Test
+    void assertValidateWithDuplicateTools() {
+        MCPDescriptorCatalog catalog = createCatalog(createInputSchema());
+        MCPToolDescriptor descriptor = catalog.getProtocolDescriptors().getToolDescriptors().iterator().next();
+        MCPDescriptorCatalog duplicateCatalog = new MCPDescriptorCatalog(new MCPProtocolDescriptorCatalog(List.of(), List.of(), List.of(descriptor, descriptor), List.of()),
+                catalog.getShardingSphereDescriptors());
+        IllegalStateException actual = assertThrows(IllegalStateException.class, () -> MCPToolDescriptorCatalogValidator.validate(duplicateCatalog));
+        assertThat(actual.getMessage(), is("Duplicate MCP tool descriptor `database_gateway_test_tool`."));
+    }
+    
+    @Test
+    void assertValidateWithMissingPlanningTool() {
+        MCPDescriptorCatalog catalog = new MCPDescriptorCatalog(new MCPProtocolDescriptorCatalog(List.of(), List.of(), List.of(), List.of()),
+                new MCPShardingSphereDescriptorCatalog(List.of(), List.of(), List.of(), List.of(), List.of(new MCPToolRuntimeDescriptor("foo_missing", "plan"))));
+        IllegalStateException actual = assertThrows(IllegalStateException.class, () -> MCPToolDescriptorCatalogValidator.validate(catalog));
+        assertThat(actual.getMessage(), is("Planning runtime tool `foo_missing` must reference a registered tool descriptor."));
+    }
+    
     private MCPDescriptorCatalog createCatalog(final Map<String, Object> inputSchema) {
         MCPToolDescriptor descriptor = new MCPToolDescriptor("database_gateway_test_tool", "Test Tool", "Run a test tool.", inputSchema,
                 Map.of("type", "object", "properties", Map.of("status", Map.of("type", "string", "description", "Status.")), "examples", List.of(Map.of("status", "ok"))),
@@ -74,7 +214,7 @@ class MCPToolDescriptorCatalogValidatorTest {
                 new MCPShardingSphereDescriptorCatalog(List.of(), List.of(), List.of(), List.of(), List.of()));
     }
     
-    private Map<String, Object> createInputSchema() {
+    private static Map<String, Object> createInputSchema() {
         return Map.of("type", "object", "properties", Map.of("query", Map.of("type", "string", "description", "Query.")),
                 "required", List.of("query"), "additionalProperties", false);
     }
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolInputSchemaValidatorTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolInputSchemaValidatorTest.java
index 3208dfdd6e9..266d17d810b 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolInputSchemaValidatorTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolInputSchemaValidatorTest.java
@@ -19,11 +19,11 @@ package org.apache.shardingsphere.mcp.support.descriptor;
 
 import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolAnnotations;
 import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolDescriptor;
-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.math.BigInteger;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Stream;
@@ -44,6 +44,37 @@ class MCPToolInputSchemaValidatorTest {
     
     private static Stream<Arguments> invalidInputSchemaCases() {
         return Stream.of(
+                Arguments.of("conditional composition",
+                        Map.of("type", "object", "properties", Map.of("query", Map.of("type", "string", "description", "Query.")), "required", List.of("query"), "additionalProperties", false,
+                                "if", Map.of("query", Map.of("type", "string"))),
+                        "Tool `database_gateway_test_tool` inputSchema contains unsupported top-level field `if`."),
+                Arguments.of("top-level type", Map.of("type", "array", "properties", Map.of(), "required", List.of(), "additionalProperties", false),
+                        "Tool `database_gateway_test_tool` inputSchema must be an object."),
+                Arguments.of("top-level properties", Map.of("type", "object", "properties", "foo", "required", List.of(), "additionalProperties", false),
+                        "Tool `database_gateway_test_tool` inputSchema must declare properties."),
+                Arguments.of("top-level required", Map.of("type", "object", "properties", Map.of(), "required", "foo", "additionalProperties", false),
+                        "Tool `database_gateway_test_tool` inputSchema required must be an array."),
+                Arguments.of("top-level additionalProperties", Map.of("type", "object", "properties", Map.of(), "required", List.of(), "additionalProperties", "foo"),
+                        "Tool `database_gateway_test_tool` inputSchema additionalProperties must be a boolean."),
+                Arguments.of("properties on string", createInputSchemaWithQuery(Map.of("type", "string", "properties", Map.of(), "description", "Query."), List.of("query")),
+                        "Tool `database_gateway_test_tool` inputSchema at `inputSchema.properties.query` must use type `object` with properties."),
+                Arguments.of("nested properties is not object", createInputSchemaWithQuery(Map.of("type", "object", "properties", "foo", "description", "Query."), List.of("query")),
+                        "Tool `database_gateway_test_tool` inputSchema at `inputSchema.properties.query.properties` must be an object."),
+                Arguments.of("property name is not string",
+                        Map.of("type", "object", "properties", Map.of(1, Map.of("type", "string", "description", "Query.")), "required", List.of(), "additionalProperties", false),
+                        "Tool `database_gateway_test_tool` inputSchema property name at `inputSchema.properties` must be a string."),
+                Arguments.of("required on string", createInputSchemaWithQuery(Map.of("type", "string", "required", List.of(), "description", "Query."), List.of("query")),
+                        "Tool `database_gateway_test_tool` inputSchema at `inputSchema.properties.query` must use type `object` with required properties."),
+                Arguments.of("nested required is not array", createInputSchemaWithQuery(Map.of("type", "object", "required", "foo", "description", "Query."), List.of("query")),
+                        "Tool `database_gateway_test_tool` inputSchema at `inputSchema.properties.query.required` must be an array."),
+                Arguments.of("items on string", createInputSchemaWithQuery(Map.of("type", "string", "items", Map.of(), "description", "Query."), List.of("query")),
+                        "Tool `database_gateway_test_tool` inputSchema at `inputSchema.properties.query` must use type `array` with items."),
+                Arguments.of("additionalProperties on string", createInputSchemaWithQuery(Map.of("type", "string", "additionalProperties", false, "description", "Query."), List.of("query")),
+                        "Tool `database_gateway_test_tool` inputSchema at `inputSchema.properties.query` must use type `object` with additionalProperties."),
+                Arguments.of("range on string", createInputSchemaWithQuery(Map.of("type", "string", "minimum", 0, "maximum", 10, "description", "Query."), List.of("query")),
+                        "Tool `database_gateway_test_tool` inputSchema at `inputSchema.properties.query` only supports minimum, maximum and default for type `integer`."),
+                Arguments.of("integer default wrong type", createInputSchemaWithQuery(Map.of("type", "integer", "default", "foo", "description", "Query."), List.of("query")),
+                        "Tool `database_gateway_test_tool` inputSchema at `inputSchema.properties.query.default` must be an integer in the Java int range."),
                 Arguments.of("unsupported type", createInputSchemaWithQuery(Map.of("type", "unsupported", "description", "Query."), List.of("query")),
                         "Tool `database_gateway_test_tool` inputSchema at `inputSchema.properties.query` uses unsupported type `unsupported`."),
                 Arguments.of("property is not an object", createInputSchemaWithQuery("string", List.of("query")),
@@ -88,12 +119,21 @@ class MCPToolInputSchemaValidatorTest {
         return Map.of("type", "object", "properties", Map.of("query", querySchema), "required", required, "additionalProperties", false);
     }
     
-    @Test
-    void assertValidateWithStringDefault() {
-        Map<String, Object> inputSchema = createInputSchemaWithQuery(Map.of("type", "string", "default", "query", "description", "Query."), List.of("query"));
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("validInputSchemaCases")
+    void assertValidateWithValidInputSchema(final String name, final Map<String, Object> inputSchema) {
         assertDoesNotThrow(() -> MCPToolInputSchemaValidator.validate(createToolDescriptor(inputSchema)));
     }
     
+    private static Stream<Arguments> validInputSchemaCases() {
+        return Stream.of(
+                Arguments.of("string default", createInputSchemaWithQuery(Map.of("type", "string", "default", "query", "description", "Query."), List.of("query"))),
+                Arguments.of("integer default without range", createInputSchemaWithQuery(Map.of("type", "integer", "default", 3, "description", "Query."), List.of("query"))),
+                Arguments.of("BigInteger bounds", createInputSchemaWithQuery(Map.of("type", "integer", "minimum", BigInteger.ZERO, "maximum", BigInteger.TEN, "default", BigInteger.TEN,
+                        "description", "Query."), List.of("query"))),
+                Arguments.of("schema without type", createInputSchemaWithQuery(Map.of("description", "Query."), List.of("query"))));
+    }
+    
     private MCPToolDescriptor createToolDescriptor(final Map<String, Object> inputSchema) {
         Map<String, Object> outputSchema = Map.of("type", "object", "properties", Map.of("status", Map.of("type", "string", "description", "Status.")),
                 "examples", List.of(Map.of("status", "ok")));
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolOutputSchemaValidatorTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolOutputSchemaValidatorTest.java
index a0fc2c7d243..2297f20eb3d 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolOutputSchemaValidatorTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolOutputSchemaValidatorTest.java
@@ -20,7 +20,11 @@ package org.apache.shardingsphere.mcp.support.descriptor;
 import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolAnnotations;
 import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolDescriptor;
 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.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -30,6 +34,67 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 
 class MCPToolOutputSchemaValidatorTest {
     
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("invalidOutputSchemas")
+    void assertValidateWithInvalidOutputSchema(final String name, final Map<String, Object> outputSchema, final String expectedMessage) {
+        IllegalStateException actual = assertThrows(IllegalStateException.class, () -> MCPToolOutputSchemaValidator.validate(createDescriptor(outputSchema)));
+        assertThat(actual.getMessage(), is(expectedMessage));
+    }
+    
+    private MCPToolDescriptor createDescriptor(final Map<String, Object> outputSchema) {
+        return new MCPToolDescriptor("database_gateway_test_tool", "Test Tool", "Run a test tool.", Map.of(), outputSchema, MCPToolAnnotations.builder().build(), Map.of());
+    }
+    
+    private static Collection<Arguments> invalidOutputSchemas() {
+        return List.of(
+                Arguments.of("wrong schema type", Map.of("type", "array"), "Tool `database_gateway_test_tool` outputSchema must be an object."),
+                Arguments.of("empty properties", createOutputSchema(Map.of(), List.of(Map.of("status", "ok"))), "Tool `database_gateway_test_tool` outputSchema must declare properties."),
+                Arguments.of("missing examples", Map.of("type", "object", "properties", Map.of("status", Map.of("description", "Status."))),
+                        "Tool `database_gateway_test_tool` outputSchema must declare examples."),
+                Arguments.of("unknown response mode", createOutputSchema(Map.of("status", Map.of("description", "Status.")), List.of(Map.of("response_mode", "foo_unknown"))),
+                        "Tool `database_gateway_test_tool` output example uses unknown response_mode `foo_unknown`."),
+                Arguments.of("unknown continuation mode", createOutputSchema(Map.of("status", Map.of("description", "Status.")), List.of(Map.of("continuation_mode", "foo_unknown"))),
+                        "Tool `database_gateway_test_tool` output example uses unknown continuation_mode `foo_unknown`."),
+                Arguments.of("unknown recovery category", createOutputSchema(Map.of("status", Map.of("description", "Status.")), List.of(Map.of("recovery_category", "foo_unknown"))),
+                        "Tool `database_gateway_test_tool` output example uses unknown recovery_category `foo_unknown`."),
+                Arguments.of("next actions is not array", createOutputSchema(Map.of("status", Map.of("description", "Status.")), List.of(Map.of("next_actions", "foo_actions"))),
+                        "Tool `database_gateway_test_tool` next_actions example must be an array."),
+                Arguments.of("action is not object", createOutputSchema(Map.of("status", Map.of("description", "Status.")), List.of(Map.of("next_actions", List.of("foo_action")))),
+                        "Tool `database_gateway_test_tool` next_actions example item must be an object."),
+                Arguments.of("unknown action type", createOutputSchema(Map.of("status", Map.of("description", "Status.")), List.of(Map.of("next_actions", List.of(Map.of("type", "foo_action"))))),
+                        "Tool `database_gateway_test_tool` next_actions example uses unknown type `foo_action`."),
+                Arguments.of("critical field is not object", createOutputSchema(Map.of("summary", "foo_summary"), List.of(Map.of("status", "ok"))),
+                        "Tool `database_gateway_test_tool` model-critical output field `summary` must be an object."),
+                Arguments.of("missing description", createOutputSchema(Map.of("summary", Map.of("type", "string")), List.of(Map.of("status", "ok"))),
+                        "Tool model-critical output field `database_gateway_test_tool.summary` description is required."),
+                Arguments.of("resource placeholder", createOutputSchema(Map.of("summary", Map.of("description", "ShardingSphere MCP resource: foo")), List.of(Map.of("status", "ok"))),
+                        "Tool model-critical output field `database_gateway_test_tool.summary` description must not be a placeholder description."),
+                Arguments.of("template placeholder", createOutputSchema(Map.of("summary", Map.of("description", "ShardingSphere MCP resource template: foo")), List.of(Map.of("status", "ok"))),
+                        "Tool model-critical output field `database_gateway_test_tool.summary` description must not be a placeholder description."),
+                Arguments.of("action schema type", createOutputSchema(Map.of("next_actions", Map.of("type", "object", "description", "Next steps.")), List.of(Map.of("status", "ok"))),
+                        "Tool `database_gateway_test_tool` next_actions must be an array."),
+                Arguments.of("action schema items", createOutputSchema(Map.of("next_actions", Map.of("type", "array", "description", "Next steps.", "items", "foo")), List.of(Map.of("status", "ok"))),
+                        "Tool `database_gateway_test_tool` next_actions items must be an object."),
+                Arguments.of("action schema properties",
+                        createOutputSchema(Map.of("next_actions", Map.of("type", "array", "description", "Next steps.", "items", Map.of())), List.of(Map.of("status", "ok"))),
+                        "Tool `database_gateway_test_tool` next_actions items must declare properties."),
+                Arguments.of("action schema required field", createOutputSchema(Map.of("next_actions", createNextActionsSchema(Map.of())), List.of(Map.of("status", "ok"))),
+                        "Tool `database_gateway_test_tool` next_actions item must declare `order`."),
+                Arguments.of("action schema field is not object", createOutputSchema(Map.of("next_actions", createNextActionsSchema(Map.of("order", 1))), List.of(Map.of("status", "ok"))),
+                        "Tool `database_gateway_test_tool` next_actions item field `order` must be an object."),
+                Arguments.of("action schema missing field description",
+                        createOutputSchema(Map.of("next_actions", createNextActionsSchema(Map.of("order", Map.of("type", "integer")))), List.of(Map.of("status", "ok"))),
+                        "Tool next_actions item field `database_gateway_test_tool.order` description is required."));
+    }
+    
+    private static Map<String, Object> createOutputSchema(final Map<String, Object> props, final Object examples) {
+        return Map.of("type", "object", "properties", props, "examples", examples);
+    }
+    
+    private static Map<String, Object> createNextActionsSchema(final Map<String, Object> props) {
+        return Map.of("type", "array", "description", "Next steps.", "items", Map.of("type", "object", "properties", props));
+    }
+    
     @Test
     void assertValidate() {
         Map<String, Object> inputSchema = Map.of("type", "object", "properties", Map.of("query", Map.of("type", "string", "description", "Query.")),
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/ISOOffsetDateTimeValidatorTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/ISOOffsetDateTimeValidatorTest.java
new file mode 100644
index 00000000000..4b52bc5f3d3
--- /dev/null
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/ISOOffsetDateTimeValidatorTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.descriptor.yaml.validator;
+
+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 javax.validation.ConstraintValidatorContext;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class ISOOffsetDateTimeValidatorTest {
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("assertIsValidCases")
+    void assertIsValid(final String name, final String value, final boolean expected) {
+        boolean actual = new ISOOffsetDateTimeValidator().isValid(value, mock(ConstraintValidatorContext.class));
+        assertThat(actual, is(expected));
+    }
+    
+    @Test
+    void assertIsValidWithBlankValue() {
+        ConstraintValidatorContext context = mock(ConstraintValidatorContext.class);
+        ConstraintValidatorContext.ConstraintViolationBuilder builder = mock(ConstraintValidatorContext.ConstraintViolationBuilder.class);
+        when(context.buildConstraintViolationWithTemplate("is required")).thenReturn(builder);
+        assertFalse(new ISOOffsetDateTimeValidator().isValid(" ", context));
+        verify(context).disableDefaultConstraintViolation();
+        verify(builder).addConstraintViolation();
+    }
+    
+    private static Stream<Arguments> assertIsValidCases() {
+        return Stream.of(
+                Arguments.of("without value", null, true),
+                Arguments.of("with offset", "2026-10-12T10:00:00+08:00", true),
+                Arguments.of("invalid date time", "foo_time", false));
+    }
+}
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/MCPDescriptionValidatorTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/MCPDescriptionValidatorTest.java
new file mode 100644
index 00000000000..8990d6ceb0b
--- /dev/null
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/MCPDescriptionValidatorTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.descriptor.yaml.validator;
+
+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 javax.validation.ConstraintValidatorContext;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class MCPDescriptionValidatorTest {
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("assertIsValidCases")
+    void assertIsValid(final String name, final String value, final boolean expected) {
+        boolean actual = new MCPDescriptionValidator().isValid(value, mock(ConstraintValidatorContext.class));
+        assertThat(actual, is(expected));
+    }
+    
+    @Test
+    void assertIsValidWithResourcePlaceholder() {
+        ConstraintValidatorContext context = mock(ConstraintValidatorContext.class);
+        ConstraintValidatorContext.ConstraintViolationBuilder builder = mock(ConstraintValidatorContext.ConstraintViolationBuilder.class);
+        when(context.buildConstraintViolationWithTemplate("must not be a placeholder description")).thenReturn(builder);
+        assertFalse(new MCPDescriptionValidator().isValid("ShardingSphere MCP resource: foo_resource", context));
+        verify(context).disableDefaultConstraintViolation();
+        verify(builder).addConstraintViolation();
+    }
+    
+    @Test
+    void assertIsValidWithResourceTemplatePlaceholder() {
+        ConstraintValidatorContext context = mock(ConstraintValidatorContext.class);
+        ConstraintValidatorContext.ConstraintViolationBuilder builder = mock(ConstraintValidatorContext.ConstraintViolationBuilder.class);
+        when(context.buildConstraintViolationWithTemplate("must not be a placeholder description")).thenReturn(builder);
+        assertFalse(new MCPDescriptionValidator().isValid("ShardingSphere MCP resource template: foo_resource", context));
+        verify(context).disableDefaultConstraintViolation();
+        verify(builder).addConstraintViolation();
+    }
+    
+    private static Stream<Arguments> assertIsValidCases() {
+        return Stream.of(
+                Arguments.of("without description", null, false),
+                Arguments.of("with description", "Read the current runtime capabilities.", true),
+                Arguments.of("blank description", " ", false));
+    }
+}
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/MCPPromptGuidanceMetaValidatorTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/MCPPromptGuidanceMetaValidatorTest.java
new file mode 100644
index 00000000000..8e3e59c44b1
--- /dev/null
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/MCPPromptGuidanceMetaValidatorTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.descriptor.yaml.validator;
+
+import org.apache.shardingsphere.mcp.support.descriptor.MCPShardingSphereMetadataKeys;
+import org.junit.jupiter.api.Test;
+
+import javax.validation.ConstraintValidatorContext;
+import java.util.List;
+import java.util.Map;
+
+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.verify;
+import static org.mockito.Mockito.when;
+
+class MCPPromptGuidanceMetaValidatorTest {
+    
+    @Test
+    void assertIsValidWithoutMetadata() {
+        assertTrue(new MCPPromptGuidanceMetaValidator().isValid(null, mock(ConstraintValidatorContext.class)));
+    }
+    
+    @Test
+    void assertIsValidWithGuidance() {
+        assertTrue(new MCPPromptGuidanceMetaValidator().isValid(Map.of(MCPShardingSphereMetadataKeys.STOP_CONDITIONS, List.of("stop"),
+                MCPShardingSphereMetadataKeys.ASK_USER_CONDITIONS, List.of("ask")), mock(ConstraintValidatorContext.class)));
+    }
+    
+    @Test
+    void assertIsValidWithoutStopConditions() {
+        ConstraintValidatorContext context = mock(ConstraintValidatorContext.class);
+        ConstraintValidatorContext.ConstraintViolationBuilder builder = mock(ConstraintValidatorContext.ConstraintViolationBuilder.class);
+        when(context.buildConstraintViolationWithTemplate("must declare " + MCPShardingSphereMetadataKeys.STOP_CONDITIONS)).thenReturn(builder);
+        assertFalse(new MCPPromptGuidanceMetaValidator().isValid(Map.of(MCPShardingSphereMetadataKeys.ASK_USER_CONDITIONS, List.of("ask")), context));
+        verify(builder).addConstraintViolation();
+    }
+    
+    @Test
+    void assertIsValidWithEmptyAskUserConditions() {
+        ConstraintValidatorContext context = mock(ConstraintValidatorContext.class);
+        ConstraintValidatorContext.ConstraintViolationBuilder builder = mock(ConstraintValidatorContext.ConstraintViolationBuilder.class);
+        when(context.buildConstraintViolationWithTemplate("must declare " + MCPShardingSphereMetadataKeys.ASK_USER_CONDITIONS)).thenReturn(builder);
+        assertFalse(new MCPPromptGuidanceMetaValidator().isValid(Map.of(MCPShardingSphereMetadataKeys.STOP_CONDITIONS, List.of("stop"),
+                MCPShardingSphereMetadataKeys.ASK_USER_CONDITIONS, List.of()), context));
+        verify(context).disableDefaultConstraintViolation();
+        verify(builder).addConstraintViolation();
+    }
+}
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/resource/MCPUriPathSegmentUtilsTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/resource/MCPUriPathSegmentUtilsTest.java
index 34aa2e96529..2e4f1478c82 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/resource/MCPUriPathSegmentUtilsTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/resource/MCPUriPathSegmentUtilsTest.java
@@ -18,7 +18,12 @@
 package org.apache.shardingsphere.mcp.support.resource;
 
 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.Collection;
+import java.util.List;
 import java.util.Optional;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -26,6 +31,16 @@ import static org.hamcrest.Matchers.is;
 
 class MCPUriPathSegmentUtilsTest {
     
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("invalidEncodings")
+    void assertDecodePathSegmentWithMalformedEncoding(final String name, final String pathSegment) {
+        assertThat(MCPUriPathSegmentUtils.decodePathSegment(pathSegment), is(Optional.empty()));
+    }
+    
+    private static Collection<Arguments> invalidEncodings() {
+        return List.of(Arguments.of("truncated escape", "foo%2"), Arguments.of("invalid hexadecimal", "logic%ZZdb"), Arguments.of("invalid UTF-8", "%FF"));
+    }
+    
     @Test
     void assertEncodePathSegment() {
         assertThat(MCPUriPathSegmentUtils.encodePathSegment("订单 / archive?"), is("%E8%AE%A2%E5%8D%95%20%2F%20archive%3F"));
@@ -43,9 +58,4 @@ class MCPUriPathSegmentUtilsTest {
         assertThat(actualPathSegment, is(Optional.of("logic+db")));
     }
     
-    @Test
-    void assertDecodePathSegmentWithMalformedPercentEncoding() {
-        Optional<String> actualPathSegment = MCPUriPathSegmentUtils.decodePathSegment("logic%ZZdb");
-        assertThat(actualPathSegment, is(Optional.empty()));
-    }
 }
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/resource/MCPUriTemplateTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/resource/MCPUriTemplateTest.java
index 17e91f58123..d3af4b8b44c 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/resource/MCPUriTemplateTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/resource/MCPUriTemplateTest.java
@@ -19,7 +19,11 @@ package org.apache.shardingsphere.mcp.support.resource;
 
 import org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceURIVariables;
 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.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -32,9 +36,24 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class MCPUriTemplateTest {
     
-    @Test
-    void assertCreateWithUnsupportedScheme() {
-        assertThrows(IllegalArgumentException.class, () -> new MCPUriTemplate("unsupported://capabilities"));
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("invalidTemplates")
+    void assertCreateWithInvalidPath(final String name, final String template, final String expectedMessage) {
+        IllegalArgumentException actual = assertThrows(IllegalArgumentException.class, () -> new MCPUriTemplate(template));
+        assertThat(actual.getMessage(), is(expectedMessage));
+    }
+    
+    private static Collection<Arguments> invalidTemplates() {
+        return List.of(
+                Arguments.of("unsupported scheme", "unsupported://capabilities", "Invalid URI `unsupported://capabilities`."),
+                Arguments.of("missing scheme separator", "invalid-template", "Invalid URI `invalid-template`."),
+                Arguments.of("embedded variable", "shardingsphere://databases/prefix-{database}",
+                        "URI template variables must occupy a complete path segment in `shardingsphere://databases/prefix-{database}`."),
+                Arguments.of("empty path", "shardingsphere://", "URI path is required for `shardingsphere://`."),
+                Arguments.of("empty segment", "shardingsphere://databases//schemas", "URI segment is required for `shardingsphere://databases//schemas`."),
+                Arguments.of("nested variable marker", "shardingsphere://databases/{{database}}", "Invalid URI template variable `{database}` in `shardingsphere://databases/{{database}}`."),
+                Arguments.of("duplicate variable", "shardingsphere://databases/{database}/{database}",
+                        "Duplicate URI template variable `database` in `shardingsphere://databases/{database}/{database}`."));
     }
     
     @Test
@@ -84,13 +103,4 @@ class MCPUriTemplateTest {
         assertTrue(new MCPUriTemplate("shardingsphere://databases/{database}").overlaps(new MCPUriTemplate("shardingsphere://databases/logic_db")));
     }
     
-    @Test
-    void assertCreateWithEmbeddedVariable() {
-        assertThrows(IllegalArgumentException.class, () -> new MCPUriTemplate("shardingsphere://databases/prefix-{database}"));
-    }
-    
-    @Test
-    void assertCreateWithInvalidTemplate() {
-        assertThrows(IllegalArgumentException.class, () -> new MCPUriTemplate("invalid-template"));
-    }
 }
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowLifecycleUtilsTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowLifecycleUtilsTest.java
index 654b769ee30..f783f597faa 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowLifecycleUtilsTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowLifecycleUtilsTest.java
@@ -21,14 +21,39 @@ import org.apache.shardingsphere.mcp.support.workflow.model.ClarifiedIntent;
 import org.apache.shardingsphere.mcp.support.workflow.model.InteractionPlan;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
 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.stream.Stream;
 
 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;
 
 class WorkflowLifecycleUtilsTest {
     
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("dropWorkflowCases")
+    void assertIsDropWorkflow(final String name, final WorkflowContextSnapshot snapshot, final boolean expected) {
+        assertThat(WorkflowLifecycleUtils.isDropWorkflow(snapshot), is(expected));
+    }
+    
+    private static Stream<Arguments> dropWorkflowCases() {
+        return Stream.of(
+                Arguments.of("before clarification", new WorkflowContextSnapshot(), false),
+                Arguments.of("drop operation", createSnapshot("drop"), true),
+                Arguments.of("create operation", createSnapshot("create"), false));
+    }
+    
+    private static WorkflowContextSnapshot createSnapshot(final String operationType) {
+        WorkflowContextSnapshot result = new WorkflowContextSnapshot();
+        ClarifiedIntent clarifiedIntent = new ClarifiedIntent();
+        clarifiedIntent.setOperationType(operationType);
+        result.setClarifiedIntent(clarifiedIntent);
+        return result;
+    }
+    
     @Test
     void assertIsNotOwnedBySessionWithBlankSnapshotSession() {
         WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
@@ -55,22 +80,4 @@ class WorkflowLifecycleUtilsTest {
     void assertResolveCurrentStepWithoutInteractionPlan() {
         assertThat(WorkflowLifecycleUtils.resolveCurrentStep(new WorkflowContextSnapshot()), is(""));
     }
-    
-    @Test
-    void assertIsDropWorkflow() {
-        WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
-        ClarifiedIntent clarifiedIntent = new ClarifiedIntent();
-        clarifiedIntent.setOperationType("drop");
-        snapshot.setClarifiedIntent(clarifiedIntent);
-        assertTrue(WorkflowLifecycleUtils.isDropWorkflow(snapshot));
-    }
-    
-    @Test
-    void assertIsDropWorkflowWithNonDropOperation() {
-        WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
-        ClarifiedIntent clarifiedIntent = new ClarifiedIntent();
-        clarifiedIntent.setOperationType("create");
-        snapshot.setClarifiedIntent(clarifiedIntent);
-        assertFalse(WorkflowLifecycleUtils.isDropWorkflow(snapshot));
-    }
 }
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 b59c9721827..f89d27b73ee 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
@@ -43,6 +43,21 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class WorkflowPlanPayloadBuilderTest {
     
+    @Test
+    void assertBuildWithPreviewedPlan() {
+        WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
+        snapshot.setPlanId("plan-1");
+        snapshot.setWorkflowKind(WorkflowKind.valueOf("mask.rule"));
+        snapshot.setStatus(WorkflowLifecycle.STATUS_PREVIEWED);
+        snapshot.setClarifiedIntent(new ClarifiedIntent());
+        WorkflowRequest request = new WorkflowRequest();
+        snapshot.setRequest(request);
+        snapshot.setInteractionPlan(InteractionPlan.create("plan-1", request, "Mask workflow plan.", List.of("Review"), List.of("rules")));
+        Map<String, Object> actual = WorkflowPlanPayloadBuilder.build(snapshot);
+        assertThat(actual.get("summary"), is("Workflow plan `plan-1` for mask.rule is `previewed`."));
+        assertThat(actual.get("status"), is(WorkflowLifecycle.STATUS_PREVIEWED));
+    }
+    
     @Test
     void assertBuildIncludesIntentInference() {
         WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();