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 5868bfb0cb9 Add proxy preflight validation tool (#38771)
5868bfb0cb9 is described below

commit 5868bfb0cb9fa0ac556fc31f69d9335bf5e6f238
Author: Liang Zhang <[email protected]>
AuthorDate: Tue Jun 2 16:11:10 2026 +0800

    Add proxy preflight validation tool (#38771)
    
    * Add proxy preflight validation tool
    
    Add a new database_gateway_validate_proxy_connectivity tool for
    ShardingSphere-Proxy preflight validation. Introduce the minimal request
    and response models, wire the core handler and descriptor contract, and
    reuse runtime connection recovery metadata for validation failures.
    
    Also document the new protocol surface and add focused support/core tests
    for request validation, execution flow, descriptor checks, capabilities,
    and recovery payload behavior.
    
    * Add proxy preflight validation tool
    
    Add a new database_gateway_validate_proxy_connectivity tool for
    ShardingSphere-Proxy preflight validation. Introduce the minimal request
    and response models, wire the core handler and descriptor contract, and
    reuse runtime connection recovery metadata for validation failures.
    
    Also document the new protocol surface and add focused support/core tests
    for request validation, execution flow, descriptor checks, capabilities,
    and recovery payload behavior.
---
 .../content/reference/mcp/protocol-surface.cn.md   |   8 +
 .../content/reference/mcp/protocol-surface.en.md   |   8 +
 .../shardingsphere-mcp/capabilities.cn.md          |  16 ++
 .../shardingsphere-mcp/capabilities.en.md          |  16 ++
 .../shardingsphere-mcp/configuration.cn.md         |   5 +
 .../shardingsphere-mcp/configuration.en.md         |   5 +
 .../mcp/core/context/MCPRequestScope.java          |  13 +-
 .../descriptor/CoreToolDescriptorValidator.java    |  52 +++-
 .../mcp/core/handler/core/CoreToolHandlers.java    |   2 +
 .../error/MCPBasicRecoveryPayloadFactory.java      |   3 +
 .../protocol/error/MCPRecoveryPayloadSupport.java  |   3 +-
 .../ValidateProxyConnectivityToolHandler.java      |  67 +++++
 .../mcp-descriptors/mcp-descriptor-core.yaml       |  97 +++++++
 .../mcp/core/context/MCPRequestScopeTest.java      |  12 +
 .../CoreToolDescriptorValidatorTest.java           |  29 +++
 .../core/handler/core/CoreHandlerProviderTest.java |   2 +-
 .../mcp/core/protocol/MCPErrorConverterTest.java   |  10 +
 .../capability/ServerCapabilitiesHandlerTest.java  |  18 +-
 .../tool/handler/ToolDefinitionRegistryTest.java   |  28 ++-
 .../ValidateProxyConnectivityToolHandlerTest.java  |  69 +++++
 .../database/MCPDatabaseHandlerContext.java        |  11 +
 .../jdbc/RuntimeDatabaseConnectionException.java   |  13 +
 .../request/ProxyPreflightValidationRequest.java   |  50 ++++
 .../tool/response/ProxyPreflightCheckResult.java   |  95 +++++++
 .../response/ProxyPreflightValidationResult.java   |  93 +++++++
 .../service/ProxyPreflightValidationService.java   | 206 +++++++++++++++
 .../MCPModelFirstContractPayloadBuilder.java       |  22 +-
 .../RuntimeDatabaseConnectionExceptionTest.java    |   7 +
 .../ProxyPreflightValidationRequestTest.java       |  41 ++-
 .../response/ProxyPreflightCheckResultTest.java    |  65 +++++
 .../ProxyPreflightValidationResultTest.java        |  66 +++++
 .../ProxyPreflightValidationServiceTest.java       | 280 +++++++++++++++++++++
 .../AbstractProductionMySQLRuntimeE2ETest.java     |   2 +
 .../production/PackagedDistributionE2ETest.java    |  12 +-
 .../production/ProductionMySQLRuntimeE2ETest.java  |   1 +
 .../programmatic/HttpTransportContractE2ETest.java |   2 +-
 .../test/e2e/mcp/support/OfficialMCPToolNames.java |   1 +
 .../model-contract/capabilities.yaml               |  19 +-
 38 files changed, 1395 insertions(+), 54 deletions(-)

diff --git a/docs/document/content/reference/mcp/protocol-surface.cn.md 
b/docs/document/content/reference/mcp/protocol-surface.cn.md
index 4b85016806f..ae8952199d8 100644
--- a/docs/document/content/reference/mcp/protocol-surface.cn.md
+++ b/docs/document/content/reference/mcp/protocol-surface.cn.md
@@ -42,6 +42,14 @@ ShardingSphere-MCP 不要求 roots,也不会发送 `sampling/createMessage` 
 - 可按 `database`、`schema`、`query`、`object_types` 收窄范围。
 - `object_types` 支持 
`database`、`schema`、`table`、`view`、`column`、`index`、`sequence`。
 
+`database_gateway_validate_proxy_connectivity`
+
+- 在正式接入前校验已配置的运行时数据库。
+- 必填输入为 `database`。
+- 使用管理员已配置的运行时数据库连接信息;JDBC URL、用户名、密码和驱动类名不是工具输入。
+- 返回 `status`、有序 `checks`、整体 `category` 和结构化 `recovery` 对象。
+- 常见失败分类包括 
`missing_jdbc_driver`、`authentication_failed`、`authorization_failed`、`connection_timeout`、`invalid_configuration`、`database_unavailable`、`connection_failed`
 和 `database_not_visible`。
+
 `database_gateway_execute_query`
 
 - 执行一个 classifier 允许的 `SELECT` 或 `EXPLAIN ANALYZE`。
diff --git a/docs/document/content/reference/mcp/protocol-surface.en.md 
b/docs/document/content/reference/mcp/protocol-surface.en.md
index 732af9b40d8..54a1e0d8371 100644
--- a/docs/document/content/reference/mcp/protocol-surface.en.md
+++ b/docs/document/content/reference/mcp/protocol-surface.en.md
@@ -42,6 +42,14 @@ ShardingSphere-MCP does not require roots and does not send 
`sampling/createMess
 - Narrows scope by `database`, `schema`, `query`, and `object_types`.
 - `object_types` supports `database`, `schema`, `table`, `view`, `column`, 
`index`, and `sequence`.
 
+`database_gateway_validate_proxy_connectivity`
+
+- Validates a configured runtime database before formal onboarding.
+- Required input is `database`.
+- Uses the administrator-configured runtime database connection details; JDBC 
URL, username, password, and driver class are not tool inputs.
+- Returns `status`, ordered `checks`, overall `category`, and a structured 
`recovery` object.
+- Common failure categories include `missing_jdbc_driver`, 
`authentication_failed`, `authorization_failed`, `connection_timeout`, 
`invalid_configuration`, `database_unavailable`, `connection_failed`, and 
`database_not_visible`.
+
 `database_gateway_execute_query`
 
 - Executes one classifier-approved `SELECT` or `EXPLAIN ANALYZE`.
diff --git 
a/docs/document/content/user-manual/shardingsphere-mcp/capabilities.cn.md 
b/docs/document/content/user-manual/shardingsphere-mcp/capabilities.cn.md
index 4a844247e38..98b18daf76e 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/capabilities.cn.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/capabilities.cn.md
@@ -79,6 +79,7 @@ weight = 2
 | 工具 | 用途 | 自然语言示例 | 副作用 |
 | --- | --- | --- | --- |
 | `database_gateway_search_metadata` | 按名称片段和对象类型搜索运行时数据库元数据,并返回后续资源读取提示。 | 
“查找名字包含 `order` 的表。” | 无。 |
+| `database_gateway_validate_proxy_connectivity` | 
在正式接入前校验已配置的运行时数据库,包括驱动加载、JDBC 连通性、metadata 可读性和数据库可见性。 | “先检查已配置的 `logic_db` 
能不能接入,再注册。” | 无。 |
 | `database_gateway_execute_query` | 执行一个已判定为查询类的 `SELECT` 或 `EXPLAIN 
ANALYZE`。 | “查询 `orders` 表前 10 行。” | 无;拒绝 DML、DDL、DCL、事务控制、savepoint 和其他有副作用 
SQL。 |
 | `database_gateway_execute_update` | 预览或执行一个可能修改数据、元数据、规则或事务状态的 SQL。 | 
“预览这条变更 SQL,先不要执行。” | 有;应先预览并确认。 |
 | `database_gateway_apply_workflow` | 预览、执行或导出功能插件生成的治理变更计划。 | “先预览刚才的加密规则计划。” 
| 取决于执行方式;预览和人工执行包不修改运行时状态。 |
@@ -86,6 +87,21 @@ weight = 2
 
 插件工具在对应插件页面说明。
 
+### Proxy 预检结果
+
+`database_gateway_validate_proxy_connectivity` 返回固定结构的校验结果,顶层字段包括:
+
+- `response_mode`
+- `status`
+- `database`
+- `checks`
+- `category`
+- `recovery`
+
+常见失败分类包括 
`missing_jdbc_driver`、`authentication_failed`、`authorization_failed`、`connection_timeout`、`invalid_configuration`、`database_unavailable`、`connection_failed`
 和 `database_not_visible`。
+`recovery` 字段沿用运行时数据库连接失败的 secret-safe 恢复风格。
+该工具只接受已配置的 `database` 名称。JDBC URL、用户名、密码和驱动类名等连接细节保留在运行时配置中。
+
 ## 提示
 
 提示用于任务引导,例如先读取哪些信息、如何处理 SQL 执行边界、如何从失败中恢复。
diff --git 
a/docs/document/content/user-manual/shardingsphere-mcp/capabilities.en.md 
b/docs/document/content/user-manual/shardingsphere-mcp/capabilities.en.md
index c99d027dfbf..0ee5ae7f94d 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/capabilities.en.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/capabilities.en.md
@@ -79,6 +79,7 @@ Actions with side effects should be previewed or reviewed 
first.
 | Tool | Purpose | Natural language example | Side effects |
 | --- | --- | --- | --- |
 | `database_gateway_search_metadata` | Search runtime database metadata by 
name fragment and object type, and return resource hints for follow-up reads. | 
"Find tables whose names contain `order`." | None. |
+| `database_gateway_validate_proxy_connectivity` | Validate a configured 
runtime database, including driver loading, JDBC connectivity, metadata 
readability, and database visibility before formal onboarding. | "Check whether 
configured database `logic_db` is ready before we register it." | None. |
 | `database_gateway_execute_query` | Execute exactly one classifier-approved 
`SELECT` or `EXPLAIN ANALYZE` statement. | "Query the first 10 rows from 
`orders`." | None; rejects DML, DDL, DCL, transaction control, savepoints, and 
other side-effecting SQL. |
 | `database_gateway_execute_update` | Preview or execute one SQL statement 
that may mutate data, metadata, rules, or transaction state. | "Preview this 
change SQL without executing it." | Yes; preview and confirmation are 
recommended first. |
 | `database_gateway_apply_workflow` | Preview, execute, or export a governance 
change plan created by a feature plugin. | "Preview the previous encryption 
rule plan first." | Depends on the execution choice; preview and manual 
packages do not change runtime state. |
@@ -86,6 +87,21 @@ Actions with side effects should be previewed or reviewed 
first.
 
 Plugin tools are documented on the corresponding plugin pages.
 
+### Proxy preflight validation output
+
+`database_gateway_validate_proxy_connectivity` returns a structured validation 
payload with these top-level fields:
+
+- `response_mode`
+- `status`
+- `database`
+- `checks`
+- `category`
+- `recovery`
+
+Common failure categories include `missing_jdbc_driver`, 
`authentication_failed`, `authorization_failed`, `connection_timeout`, 
`invalid_configuration`, `database_unavailable`, `connection_failed`, and 
`database_not_visible`.
+The `recovery` field follows the same secret-safe runtime recovery style used 
by runtime database connection failures.
+The tool only accepts the configured `database` name. Connection details such 
as JDBC URL, username, password, and driver class stay in the runtime 
configuration.
+
 ## Prompts
 
 Prompts provide task guidance, such as which information to read first, how to 
handle SQL execution boundaries, or how to recover from failure.
diff --git 
a/docs/document/content/user-manual/shardingsphere-mcp/configuration.cn.md 
b/docs/document/content/user-manual/shardingsphere-mcp/configuration.cn.md
index d15d0da3326..00c4b495964 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/configuration.cn.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/configuration.cn.md
@@ -59,6 +59,11 @@ runtimeDatabases:
 | `password (?)` | 连接运行时数据库的密码。 |
 | `driverClassName (+)` | JDBC 驱动类名,例如 MySQL 驱动使用 `com.mysql.cj.jdbc.Driver`。 |
 
+说明:
+
+- `(+)` 表示必填项。
+- `(?)` 表示可选项。
+
 注意事项:
 
 - 连接 ShardingSphere-Proxy 时,MCP 资源暴露的是 ShardingSphere 逻辑库,不是底层物理存储单元。
diff --git 
a/docs/document/content/user-manual/shardingsphere-mcp/configuration.en.md 
b/docs/document/content/user-manual/shardingsphere-mcp/configuration.en.md
index 11c17836613..e6b337e9e91 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/configuration.en.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/configuration.en.md
@@ -59,6 +59,11 @@ runtimeDatabases:
 | `password (?)` | Password for the runtime database. |
 | `driverClassName (+)` | JDBC driver class name, such as 
`com.mysql.cj.jdbc.Driver` for the MySQL driver. |
 
+Legend:
+
+- `(+)` means required.
+- `(?)` means optional.
+
 Notes:
 
 - When the target is ShardingSphere-Proxy, MCP resources expose ShardingSphere 
logical databases, not physical storage units.
diff --git 
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/context/MCPRequestScope.java
 
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/context/MCPRequestScope.java
index 647277fcf16..60c8cc3ed9f 100644
--- 
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/context/MCPRequestScope.java
+++ 
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/context/MCPRequestScope.java
@@ -25,6 +25,7 @@ import 
org.apache.shardingsphere.mcp.core.tool.handler.execute.MCPSQLExecutionFa
 import org.apache.shardingsphere.mcp.core.workflow.WorkflowProxyQueryService;
 import 
org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
 import 
org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapabilityProvider;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
 import 
org.apache.shardingsphere.mcp.support.database.metadata.context.RequestScopedMetadataContext;
 import 
org.apache.shardingsphere.mcp.support.database.metadata.query.MetadataQueryService;
 import 
org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureCapabilityFacade;
@@ -34,6 +35,7 @@ import 
org.apache.shardingsphere.mcp.support.database.spi.MCPMetadataQueryFacade
 import 
org.apache.shardingsphere.mcp.support.workflow.MCPWorkflowHandlerContext;
 import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
 
+import java.util.Map;
 import java.util.Optional;
 
 /**
@@ -47,6 +49,9 @@ public final class MCPRequestScope implements 
MCPServiceHandlerContext, MCPDatab
     @Getter(AccessLevel.NONE)
     private final MCPDatabaseCapabilityProvider databaseCapabilityProvider;
     
+    @Getter(AccessLevel.NONE)
+    private final Map<String, RuntimeDatabaseConfiguration> runtimeDatabases;
+    
     @Getter(AccessLevel.NONE)
     private final RequestScopedMetadataContext metadataContext;
     
@@ -79,7 +84,8 @@ public final class MCPRequestScope implements 
MCPServiceHandlerContext, MCPDatab
         MCPSessionManager sessionManager = runtimeContext.getSessionManager();
         activeTransport = runtimeContext.getActiveTransport();
         databaseCapabilityProvider = 
runtimeContext.getDatabaseCapabilityProvider();
-        metadataContext = new 
RequestScopedMetadataContext(sessionManager.getTransactionResourceManager().getRuntimeDatabases(),
 databaseCapabilityProvider);
+        runtimeDatabases = 
sessionManager.getTransactionResourceManager().getRuntimeDatabases();
+        metadataContext = new RequestScopedMetadataContext(runtimeDatabases, 
databaseCapabilityProvider);
         sessionAttribution = sessionManager.findSessionAttribution(sessionId);
         workflowSessionContext = runtimeContext.getWorkflowSessionContext();
         metadataQueryFacade = new 
MetadataQueryService(databaseCapabilityProvider, metadataContext);
@@ -97,6 +103,11 @@ public final class MCPRequestScope implements 
MCPServiceHandlerContext, MCPDatab
         return databaseCapabilityProvider;
     }
     
+    @Override
+    public Optional<RuntimeDatabaseConfiguration> 
findRuntimeDatabaseConfiguration(final String databaseName) {
+        return Optional.ofNullable(runtimeDatabases.get(databaseName));
+    }
+    
     @Override
     public Optional<MCPSessionAttribution> findSessionAttribution() {
         return sessionAttribution;
diff --git 
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidator.java
 
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidator.java
index 5f74312d0c7..fac0c532ac3 100644
--- 
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidator.java
+++ 
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidator.java
@@ -40,7 +40,9 @@ public final class CoreToolDescriptorValidator implements 
MCPToolDescriptorValid
     
     private static final String EXECUTE_UPDATE = 
"database_gateway_execute_update";
     
-    private static final Set<String> SUPPORTED_TOOLS = Set.of(SEARCH_METADATA, 
EXECUTE_QUERY, EXECUTE_UPDATE);
+    private static final String VALIDATE_PROXY_CONNECTIVITY = 
"database_gateway_validate_proxy_connectivity";
+    
+    private static final Set<String> SUPPORTED_TOOLS = Set.of(SEARCH_METADATA, 
VALIDATE_PROXY_CONNECTIVITY, EXECUTE_QUERY, EXECUTE_UPDATE);
     
     @Override
     public boolean supports(final MCPToolDescriptor toolDescriptor) {
@@ -60,6 +62,12 @@ public final class CoreToolDescriptorValidator implements 
MCPToolDescriptorValid
                     "applied_max_rows", "applied_timeout_ms", "truncated", 
MCPPayloadFieldNames.NEXT_ACTIONS));
             return;
         }
+        if (VALIDATE_PROXY_CONNECTIVITY.equals(toolDescriptor.getName())) {
+            
MCPToolDescriptorValidationUtils.validateRequiredOutputFields(toolDescriptor, 
List.of("response_mode", "status", "database", "checks", "category", 
MCPPayloadFieldNames.RECOVERY));
+            validateProxyConnectivityContract(toolDescriptor);
+            validateProxyConnectivityChecks(toolDescriptor);
+            return;
+        }
         
MCPToolDescriptorValidationUtils.validateRequiredOutputFields(toolDescriptor, 
List.of("response_mode", "result_kind", "statement_class", "statement_type", 
"status", "returned_row_count",
                 "applied_max_rows", "applied_timeout_ms", 
"suggested_arguments", MCPPayloadFieldNames.NEXT_ACTIONS));
         validateExecuteUpdateDescriptor(toolDescriptor);
@@ -103,6 +111,39 @@ public final class CoreToolDescriptorValidator implements 
MCPToolDescriptorValid
                 () -> new IllegalStateException("Tool 
`database_gateway_execute_update` execution_mode must allow execute and 
preview."));
     }
     
+    private void validateProxyConnectivityContract(final MCPToolDescriptor 
descriptor) {
+        Map<?, ?> responseMode = findToolOutputProperty(descriptor, 
"response_mode").orElseThrow(
+                () -> new IllegalStateException("Tool 
`database_gateway_validate_proxy_connectivity` must declare response_mode."));
+        Object responseModes = responseMode.get("enum");
+        ShardingSpherePreconditions.checkState(responseModes instanceof 
Collection && ((Collection<?>) responseModes).contains("validation"),
+                () -> new IllegalStateException("Tool 
`database_gateway_validate_proxy_connectivity` response_mode must allow 
validation."));
+        
ShardingSpherePreconditions.checkState(findToolInputProperty(descriptor, 
"database").isPresent(),
+                () -> new IllegalStateException("Tool 
`database_gateway_validate_proxy_connectivity` must declare `database`."));
+        ShardingSpherePreconditions.checkState(isRequiredToolInput(descriptor, 
"database"),
+                () -> new IllegalStateException("Tool 
`database_gateway_validate_proxy_connectivity` database must be required."));
+        for (String each : List.of("databaseType", "jdbcUrl", "username", 
"password", "driverClassName")) {
+            
ShardingSpherePreconditions.checkState(findToolInputProperty(descriptor, 
each).isEmpty(),
+                    () -> new IllegalStateException(String.format("Tool 
`database_gateway_validate_proxy_connectivity` must not expose `%s`.", each)));
+        }
+    }
+    
+    private void validateProxyConnectivityChecks(final MCPToolDescriptor 
descriptor) {
+        Map<?, ?> properties = (Map<?, ?>) 
descriptor.getOutputSchema().get("properties");
+        Object checks = properties.get("checks");
+        ShardingSpherePreconditions.checkState(checks instanceof Map,
+                () -> new IllegalStateException("Tool 
`database_gateway_validate_proxy_connectivity` outputSchema property `checks` 
must be an object."));
+        Object checkItemSchema = ((Map<?, ?>) checks).get("items");
+        ShardingSpherePreconditions.checkState(checkItemSchema instanceof Map,
+                () -> new IllegalStateException("Tool 
`database_gateway_validate_proxy_connectivity` outputSchema property 
`checks.items` must be an object."));
+        Object checkItemProperties = ((Map<?, ?>) 
checkItemSchema).get("properties");
+        ShardingSpherePreconditions.checkState(checkItemProperties instanceof 
Map && !((Map<?, ?>) checkItemProperties).isEmpty(),
+                () -> new IllegalStateException("Tool 
`database_gateway_validate_proxy_connectivity` outputSchema property 
`checks.items.properties` must declare properties."));
+        for (String each : List.of("name", "status", "category", "message")) {
+            ShardingSpherePreconditions.checkState(((Map<?, ?>) 
checkItemProperties).containsKey(each),
+                    () -> new IllegalStateException(String.format("Tool 
`database_gateway_validate_proxy_connectivity` outputSchema check item must 
declare `%s`.", each)));
+        }
+    }
+    
     private Optional<Map<?, ?>> findToolInputProperty(final MCPToolDescriptor 
descriptor, final String fieldName) {
         Object properties = descriptor.getInputSchema().get("properties");
         if (!(properties instanceof Map)) {
@@ -112,6 +153,15 @@ public final class CoreToolDescriptorValidator implements 
MCPToolDescriptorValid
         return property instanceof Map ? Optional.of((Map<?, ?>) property) : 
Optional.empty();
     }
     
+    private Optional<Map<?, ?>> findToolOutputProperty(final MCPToolDescriptor 
descriptor, final String fieldName) {
+        Object properties = descriptor.getOutputSchema().get("properties");
+        if (!(properties instanceof Map)) {
+            return Optional.empty();
+        }
+        Object property = ((Map<?, ?>) properties).get(fieldName);
+        return property instanceof Map ? Optional.of((Map<?, ?>) property) : 
Optional.empty();
+    }
+    
     private boolean isRequiredToolInput(final MCPToolDescriptor descriptor, 
final String fieldName) {
         Object required = descriptor.getInputSchema().get("required");
         return required instanceof Collection && ((Collection<?>) 
required).contains(fieldName);
diff --git 
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/handler/core/CoreToolHandlers.java
 
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/handler/core/CoreToolHandlers.java
index e476812cbd6..eaf79b953a5 100644
--- 
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/handler/core/CoreToolHandlers.java
+++ 
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/handler/core/CoreToolHandlers.java
@@ -24,6 +24,7 @@ import 
org.apache.shardingsphere.mcp.core.workflow.WorkflowRuntimeDefinitionRegi
 import 
org.apache.shardingsphere.mcp.core.tool.handler.execute.ExecuteQueryToolHandler;
 import 
org.apache.shardingsphere.mcp.core.tool.handler.execute.ExecuteUpdateToolHandler;
 import 
org.apache.shardingsphere.mcp.core.tool.handler.metadata.SearchMetadataToolHandler;
+import 
org.apache.shardingsphere.mcp.core.tool.handler.metadata.ValidateProxyConnectivityToolHandler;
 import 
org.apache.shardingsphere.mcp.core.tool.handler.workflow.WorkflowExecutionToolHandler;
 import 
org.apache.shardingsphere.mcp.core.tool.handler.workflow.WorkflowValidationToolHandler;
 
@@ -40,6 +41,7 @@ final class CoreToolHandlers {
         WorkflowRuntimeDefinitionRegistry workflowRuntimeDefinitionRegistry = 
WorkflowRuntimeDefinitionRegistry.load();
         return List.of(
                 new SearchMetadataToolHandler(),
+                new ValidateProxyConnectivityToolHandler(),
                 new ExecuteQueryToolHandler(),
                 new ExecuteUpdateToolHandler(),
                 new 
WorkflowExecutionToolHandler(workflowRuntimeDefinitionRegistry),
diff --git 
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPBasicRecoveryPayloadFactory.java
 
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPBasicRecoveryPayloadFactory.java
index 7a5ed3eb673..e83882bbf77 100644
--- 
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPBasicRecoveryPayloadFactory.java
+++ 
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPBasicRecoveryPayloadFactory.java
@@ -225,6 +225,9 @@ final class MCPBasicRecoveryPayloadFactory {
         if 
(RuntimeDatabaseConnectionException.CATEGORY_INVALID_CONFIGURATION.equals(cause.getCategory()))
 {
             return "Fix the MCP runtime database configuration outside MCP, 
then retry.";
         }
+        if 
(RuntimeDatabaseConnectionException.CATEGORY_DATABASE_NOT_VISIBLE.equals(cause.getCategory()))
 {
+            return "Connect to the intended logical database or update the 
expected database name before retrying.";
+        }
         return "Check the runtime database availability and configuration, 
then retry.";
     }
 }
diff --git 
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPRecoveryPayloadSupport.java
 
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPRecoveryPayloadSupport.java
index 67760f1324c..daa138c5186 100644
--- 
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPRecoveryPayloadSupport.java
+++ 
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPRecoveryPayloadSupport.java
@@ -87,6 +87,7 @@ final class MCPRecoveryPayloadSupport {
         return 
RuntimeDatabaseConnectionException.CATEGORY_MISSING_JDBC_DRIVER.equals(category)
 || 
RuntimeDatabaseConnectionException.CATEGORY_AUTHENTICATION_FAILED.equals(category)
                 || 
RuntimeDatabaseConnectionException.CATEGORY_AUTHORIZATION_FAILED.equals(category)
                 || 
RuntimeDatabaseConnectionException.CATEGORY_CONNECTION_TIMEOUT.equals(category) 
|| 
RuntimeDatabaseConnectionException.CATEGORY_INVALID_CONFIGURATION.equals(category)
-                || 
RuntimeDatabaseConnectionException.CATEGORY_DATABASE_UNAVAILABLE.equals(category)
 || 
RuntimeDatabaseConnectionException.CATEGORY_CONNECTION_FAILED.equals(category);
+                || 
RuntimeDatabaseConnectionException.CATEGORY_DATABASE_UNAVAILABLE.equals(category)
+                || 
RuntimeDatabaseConnectionException.CATEGORY_CONNECTION_FAILED.equals(category);
     }
 }
diff --git 
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/ValidateProxyConnectivityToolHandler.java
 
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/ValidateProxyConnectivityToolHandler.java
new file mode 100644
index 00000000000..4c07259e434
--- /dev/null
+++ 
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/ValidateProxyConnectivityToolHandler.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.core.tool.handler.metadata;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.shardingsphere.mcp.api.protocol.response.MCPResponse;
+import org.apache.shardingsphere.mcp.api.tool.MCPToolCall;
+import org.apache.shardingsphere.mcp.api.tool.MCPToolHandler;
+import org.apache.shardingsphere.mcp.core.protocol.error.MCPErrorConverter;
+import 
org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConnectionException;
+import 
org.apache.shardingsphere.mcp.support.database.tool.request.ProxyPreflightValidationRequest;
+import 
org.apache.shardingsphere.mcp.support.database.tool.service.ProxyPreflightValidationService;
+import org.apache.shardingsphere.mcp.support.protocol.MCPPayloadFieldNames;
+
+import java.util.Map;
+
+/**
+ * Handler for proxy connectivity validation tool.
+ */
+@RequiredArgsConstructor
+public final class ValidateProxyConnectivityToolHandler implements 
MCPToolHandler<MCPDatabaseHandlerContext> {
+    
+    public static final String TOOL_NAME = 
"database_gateway_validate_proxy_connectivity";
+    
+    private final ProxyPreflightValidationService validationService;
+    
+    public ValidateProxyConnectivityToolHandler() {
+        this(new ProxyPreflightValidationService());
+    }
+    
+    @Override
+    public Class<MCPDatabaseHandlerContext> getContextType() {
+        return MCPDatabaseHandlerContext.class;
+    }
+    
+    @Override
+    public String getToolName() {
+        return TOOL_NAME;
+    }
+    
+    @Override
+    public MCPResponse handle(final MCPDatabaseHandlerContext databaseContext, 
final MCPToolCall toolCall) {
+        return 
validationService.validate(ProxyPreflightValidationRequest.from(toolCall.getArguments()),
 databaseContext::findRuntimeDatabaseConfiguration, 
this::createRecoveryPayload);
+    }
+    
+    @SuppressWarnings("unchecked")
+    private Map<String, Object> createRecoveryPayload(final 
RuntimeDatabaseConnectionException cause) {
+        Object result = 
MCPErrorConverter.convert(cause).toPayload().get(MCPPayloadFieldNames.RECOVERY);
+        return result instanceof Map ? (Map<String, Object>) result : Map.of();
+    }
+}
diff --git 
a/mcp/core/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-core.yaml
 
b/mcp/core/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-core.yaml
index 8283e9bb597..e724b9f3cbb 100644
--- 
a/mcp/core/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-core.yaml
+++ 
b/mcp/core/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-core.yaml
@@ -795,6 +795,103 @@ tools:
       org.apache.shardingsphere/related-resource-uris:
         - shardingsphere://databases
         - "shardingsphere://databases/{database}/schemas"
+  - name: database_gateway_validate_proxy_connectivity
+    title: Validate Proxy Connectivity
+    description: >-
+      Validate one configured ShardingSphere-MCP runtime database before 
formal onboarding. Use this to preflight the configured database type,
+      JDBC driver, JDBC connectivity, metadata readability, and database 
visibility without executing SQL tools.
+    inputSchema:
+      type: object
+      properties:
+        database:
+          type: string
+          description: "Configured runtime database name to validate."
+      required:
+        - database
+      additionalProperties: false
+    outputSchema:
+      type: object
+      properties:
+        response_mode:
+          type: string
+          description: "Stable response mode marker for proxy preflight 
validation payloads."
+          enum:
+            - validation
+        status:
+          type: string
+          description: "Overall preflight status: ready when all required 
checks passed, failed otherwise."
+          enum:
+            - ready
+            - failed
+        database:
+          type: string
+          description: "Validated runtime database name."
+        checks:
+          type: array
+          description: "Ordered per-step validation results for configuration 
lookup, driver loading, JDBC connectivity, metadata readability, and database 
visibility."
+          items:
+            type: object
+            description: "One preflight validation check result."
+            properties:
+              name:
+                type: string
+                description: "Stable validation step name."
+              status:
+                type: string
+                description: "Per-check result status."
+                enum:
+                  - passed
+                  - failed
+                  - skipped
+              category:
+                type: string
+                description: "Ready, skipped, or a runtime connection failure 
category reused from the runtime recovery contract."
+              message:
+                type: string
+                description: "Human-readable summary of the check result."
+        category:
+          type: string
+          description: "Overall result category. ready indicates success; 
other values reuse runtime database connection failure categories."
+        recovery:
+          type: object
+          description: "Structured runtime recovery payload. Empty when status 
is ready."
+      examples:
+        - response_mode: validation
+          status: ready
+          database: logic_db
+          checks:
+            - name: configuration
+              status: passed
+              category: ready
+              message: Resolved the configured runtime database.
+            - name: jdbc_driver
+              status: passed
+              category: ready
+              message: Loaded the configured JDBC driver.
+            - name: jdbc_connectivity
+              status: passed
+              category: ready
+              message: Opened a JDBC connection and validated the configured 
database type.
+            - name: metadata_read
+              status: passed
+              category: ready
+              message: Read metadata through the configured JDBC connection.
+            - name: database_visibility
+              status: passed
+              category: ready
+              message: Validated the requested database name against visible 
JDBC metadata and connection context.
+          category: ready
+          recovery: {}
+    annotations:
+      title: Validate Proxy Connectivity
+      readOnlyHint: true
+      destructiveHint: false
+      idempotentHint: true
+      openWorldHint: true
+    meta:
+      org.apache.shardingsphere/related-resource-uris:
+        - shardingsphere://runtime
+        - shardingsphere://capabilities
   - name: database_gateway_execute_query
     title: Execute Query SQL
     description: >-
diff --git 
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/context/MCPRequestScopeTest.java
 
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/context/MCPRequestScopeTest.java
index 572c7c19149..01ffcdd768c 100644
--- 
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/context/MCPRequestScopeTest.java
+++ 
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/context/MCPRequestScopeTest.java
@@ -20,6 +20,7 @@ package org.apache.shardingsphere.mcp.core.context;
 import org.apache.shardingsphere.mcp.api.session.MCPSessionAttribution;
 import org.apache.shardingsphere.mcp.core.session.MCPSessionManager;
 import 
org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapabilityProvider;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
 import org.junit.jupiter.api.Test;
 
 import java.util.Map;
@@ -49,4 +50,15 @@ class MCPRequestScopeTest {
             assertThat(requestScope.findSessionAttribution(), 
is(Optional.empty()));
         }
     }
+    
+    @Test
+    void assertFindRuntimeDatabaseConfiguration() {
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = new 
RuntimeDatabaseConfiguration("MySQL", "jdbc:test:profile", "demo", "", 
"com.mysql.cj.jdbc.Driver");
+        MCPRuntimeContext runtimeContext = new MCPRuntimeContext(new 
MCPSessionManager(Map.of("logic_db", runtimeDatabaseConfig)),
+                new MCPDatabaseCapabilityProvider(Map.of()), "http");
+        try (MCPRequestScope requestScope = new 
MCPRequestScope(runtimeContext)) {
+            
assertThat(requestScope.findRuntimeDatabaseConfiguration("logic_db"), 
is(Optional.of(runtimeDatabaseConfig)));
+            
assertThat(requestScope.findRuntimeDatabaseConfiguration("missing_db"), 
is(Optional.empty()));
+        }
+    }
 }
diff --git 
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidatorTest.java
 
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidatorTest.java
index cd4075c0707..130677517d3 100644
--- 
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidatorTest.java
+++ 
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidatorTest.java
@@ -22,6 +22,7 @@ import 
org.apache.shardingsphere.mcp.support.descriptor.MCPDescriptorCatalogInde
 import org.junit.jupiter.api.Test;
 
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -36,6 +37,11 @@ class CoreToolDescriptorValidatorTest {
         assertTrue(new 
CoreToolDescriptorValidator().supports(MCPDescriptorCatalogIndex.getRequiredToolDescriptor("database_gateway_search_metadata")));
     }
     
+    @Test
+    void assertSupportsValidateProxyConnectivity() {
+        assertTrue(new 
CoreToolDescriptorValidator().supports(MCPDescriptorCatalogIndex.getRequiredToolDescriptor("database_gateway_validate_proxy_connectivity")));
+    }
+    
     @Test
     void assertSearchMetadataDocumentsCompleteSearchResult() {
         MCPToolDescriptor descriptor = 
MCPDescriptorCatalogIndex.getRequiredToolDescriptor("database_gateway_search_metadata");
@@ -76,6 +82,29 @@ class CoreToolDescriptorValidatorTest {
         assertThat(actual.getMessage(), is("Tool 
`database_gateway_execute_update` must declare execution_mode."));
     }
     
+    @Test
+    @SuppressWarnings("unchecked")
+    void assertValidateRejectsExposedProxyConnectivityJdbcUrl() {
+        MCPToolDescriptor descriptor = 
MCPDescriptorCatalogIndex.getRequiredToolDescriptor("database_gateway_validate_proxy_connectivity");
+        Map<String, Object> inputSchema = new 
LinkedHashMap<>(descriptor.getInputSchema());
+        Map<String, Object> properties = new LinkedHashMap<>((Map<String, 
Object>) inputSchema.get("properties"));
+        properties.put("jdbcUrl", Map.of("type", "string", "description", 
"JDBC URL."));
+        inputSchema.put("properties", properties);
+        IllegalStateException actual = 
assertThrows(IllegalStateException.class, () -> new 
CoreToolDescriptorValidator().validate(new MCPToolDescriptor(
+                descriptor.getName(), descriptor.getTitle(), 
descriptor.getDescription(), inputSchema, descriptor.getOutputSchema(), 
descriptor.getAnnotations(), descriptor.getMeta())));
+        assertThat(actual.getMessage(), is("Tool 
`database_gateway_validate_proxy_connectivity` must not expose `jdbcUrl`."));
+    }
+    
+    @Test
+    void assertValidateRejectsOptionalProxyConnectivityDatabase() {
+        MCPToolDescriptor descriptor = 
MCPDescriptorCatalogIndex.getRequiredToolDescriptor("database_gateway_validate_proxy_connectivity");
+        Map<String, Object> inputSchema = new 
LinkedHashMap<>(descriptor.getInputSchema());
+        inputSchema.put("required", List.of());
+        IllegalStateException actual = 
assertThrows(IllegalStateException.class, () -> new 
CoreToolDescriptorValidator().validate(new MCPToolDescriptor(
+                descriptor.getName(), descriptor.getTitle(), 
descriptor.getDescription(), inputSchema, descriptor.getOutputSchema(), 
descriptor.getAnnotations(), descriptor.getMeta())));
+        assertThat(actual.getMessage(), is("Tool 
`database_gateway_validate_proxy_connectivity` database must be required."));
+    }
+    
     private Map<?, ?> getInputProperties(final MCPToolDescriptor 
toolDescriptor) {
         return (Map<?, ?>) toolDescriptor.getInputSchema().get("properties");
     }
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 a4673c71ec8..23c3e580f99 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
@@ -34,7 +34,7 @@ class CoreHandlerProviderTest {
     void assertGetToolHandlers() {
         Collection<MCPToolHandler<?>> actual = new 
CoreHandlerProvider().getToolHandlers();
         assertThat(actual.stream().map(MCPToolHandler::getToolName).toList(),
-                is(List.of("database_gateway_search_metadata", 
"database_gateway_execute_query", "database_gateway_execute_update",
+                is(List.of("database_gateway_search_metadata", 
"database_gateway_validate_proxy_connectivity", 
"database_gateway_execute_query", "database_gateway_execute_update",
                         "database_gateway_apply_workflow", 
"database_gateway_validate_workflow")));
     }
     
diff --git 
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/protocol/MCPErrorConverterTest.java
 
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/protocol/MCPErrorConverterTest.java
index 4bf465ac7f6..715a2609ddd 100644
--- 
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/protocol/MCPErrorConverterTest.java
+++ 
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/protocol/MCPErrorConverterTest.java
@@ -374,6 +374,16 @@ class MCPErrorConverterTest {
         assertThat(actualRecovery.get("model_action"), is("Check runtime 
database account privileges outside MCP, then retry."));
     }
     
+    @Test
+    void assertConvertRuntimeDatabaseNotVisibleWithRecovery() {
+        Map<String, Object> actual = 
MCPErrorConverter.convert(RuntimeDatabaseConnectionException.databaseNotVisible("logic_db",
 new IllegalStateException("not visible"))).toPayload();
+        Map<?, ?> actualRecovery = (Map<?, ?>) actual.get("recovery");
+        assertThat(actualRecovery.get("category"), is("database_not_visible"));
+        assertThat(actualRecovery.get("recovery_category"), is("validation"));
+        assertThat(actualRecovery.get("database"), is("logic_db"));
+        assertThat(actualRecovery.get("model_action"), is("Connect to the 
intended logical database or update the expected database name before 
retrying."));
+    }
+    
     @Test
     void assertConvertToolCallLimitExceededWithRecovery() {
         Map<String, Object> actual = MCPErrorConverter.convert(new 
MCPToolCallLimitExceededException("session-1", 
"database_gateway_search_metadata", 1)).toPayload();
diff --git 
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/ServerCapabilitiesHandlerTest.java
 
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/ServerCapabilitiesHandlerTest.java
index be4601db195..171fa3702aa 100644
--- 
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/ServerCapabilitiesHandlerTest.java
+++ 
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/ServerCapabilitiesHandlerTest.java
@@ -41,8 +41,9 @@ class ServerCapabilitiesHandlerTest {
         Map<String, Object> actual = createCapabilitiesPayload();
         assertBaselineTopLevelKeys(actual);
         assertTrue(((Collection<?>) 
actual.get("supportedResources")).contains("shardingsphere://capabilities"));
-        assertTrue(((Collection<?>) 
actual.get("supportedTools")).containsAll(List.of("database_gateway_search_metadata",
 "database_gateway_execute_query",
-                "database_gateway_execute_update", 
"database_gateway_apply_workflow", "database_gateway_validate_workflow")));
+        assertTrue(
+                ((Collection<?>) 
actual.get("supportedTools")).containsAll(List.of("database_gateway_search_metadata",
 "database_gateway_validate_proxy_connectivity", 
"database_gateway_execute_query",
+                        "database_gateway_execute_update", 
"database_gateway_apply_workflow", "database_gateway_validate_workflow")));
         assertFalse(((List<?>) actual.get("resources")).isEmpty());
         assertFalse(((List<?>) actual.get("resourceTemplates")).isEmpty());
         assertFalse(((List<?>) actual.get("tools")).isEmpty());
@@ -106,6 +107,7 @@ class ServerCapabilitiesHandlerTest {
         Map<?, ?> metadataRule = (Map<?, ?>) actual.get("metadata_rule");
         assertThat(metadataRule.get("first_resource"), 
is("shardingsphere://databases"));
         assertThat(metadataRule.get("search_tool"), 
is("database_gateway_search_metadata"));
+        assertThat(((Map<?, ?>) actual.get("preflight_rule")).get("tool"), 
is("database_gateway_validate_proxy_connectivity"));
         Map<?, ?> sqlToolSelection = (Map<?, ?>) 
actual.get("sql_tool_selection");
         assertThat(((Map<?, ?>) 
sqlToolSelection.get("read_only")).get("tool"), 
is("database_gateway_execute_query"));
         assertThat(((Map<?, ?>) 
sqlToolSelection.get("side_effecting")).get("first_mode"), is("preview"));
@@ -126,6 +128,7 @@ class ServerCapabilitiesHandlerTest {
         assertThat(actual.get("optional_catalog_resource"), 
is("shardingsphere://capabilities"));
         assertFalse(actual.containsKey("safe_first_resource"));
         assertThat(actual.get("metadata_first_resource"), 
is("shardingsphere://databases"));
+        
assertTrue(String.valueOf(actual.get("preflight_rule")).contains("database_gateway_validate_proxy_connectivity"));
         assertTrue(((Map<?, ?>) 
actual.get("sql_tool_selection")).containsKey("side_effecting"));
         assertTrue(actual.containsKey("workflow_session_rule"));
         assertTrue(actual.containsKey("next_action_rule"));
@@ -138,6 +141,7 @@ class ServerCapabilitiesHandlerTest {
         assertThat(actual.get("argument_completion_method"), 
is("completion/complete"));
         assertThat(actual.get("optional_catalog_resource"), 
is("shardingsphere://capabilities"));
         assertThat(actual.get("metadata_search_tool"), 
is("database_gateway_search_metadata"));
+        assertThat(actual.get("preflight_validation_tool"), 
is("database_gateway_validate_proxy_connectivity"));
         assertThat(actual.get("side_effect_sql_tool"), 
is("database_gateway_execute_update"));
     }
     
@@ -169,6 +173,9 @@ class ServerCapabilitiesHandlerTest {
         Map<?, ?> inspectMetadata = findByKey((List<?>) 
capabilities.get("common_flows"), "flow_id", "inspect_metadata");
         assertTrue(((List<?>) 
inspectMetadata.get("steps")).containsAll(List.of("resources/list", 
"resources/templates/list", "call_tool database_gateway_search_metadata")));
         assertReferencedFlowEntries(inspectMetadata, supportedTools, 
supportedResources);
+        Map<?, ?> validateRuntimeDatabase = findByKey((List<?>) 
capabilities.get("common_flows"), "flow_id", "validate_runtime_database");
+        assertTrue(((List<?>) 
validateRuntimeDatabase.get("steps")).contains("call_tool 
database_gateway_validate_proxy_connectivity"));
+        assertReferencedFlowEntries(validateRuntimeDatabase, supportedTools, 
supportedResources);
         Map<?, ?> sideEffectingSql = findByKey((List<?>) 
capabilities.get("common_flows"), "flow_id", "side_effecting_sql");
         assertTrue(((List<?>) 
sideEffectingSql.get("steps")).contains("call_tool 
database_gateway_execute_update execution_mode=preview"));
         assertTrue(((List<?>) 
sideEffectingSql.get("steps")).contains("call_tool 
database_gateway_execute_update execution_mode=execute"));
@@ -242,6 +249,13 @@ class ServerCapabilitiesHandlerTest {
         
assertTrue(searchMetadataOutputProperties.containsKey("total_match_count"));
         Map<?, ?> objectTypesSchema = findInputSchema(searchMetadataTool, 
"object_types");
         assertTrue(((List<?>) ((Map<?, ?>) 
objectTypesSchema.get("items")).get("enum")).containsAll(List.of("database", 
"schema", "table", "view", "column", "index", "sequence")));
+        Map<?, ?> validateProxyConnectivityTool = findTool(capabilities, 
"database_gateway_validate_proxy_connectivity");
+        Map<?, ?> validateProxyConnectivityOutputProperties = (Map<?, ?>) 
((Map<?, ?>) 
validateProxyConnectivityTool.get("outputSchema")).get("properties");
+        assertThat(getInputFieldNames(validateProxyConnectivityTool), 
is(List.of("database")));
+        
assertTrue(validateProxyConnectivityOutputProperties.containsKey("status"));
+        
assertTrue(validateProxyConnectivityOutputProperties.containsKey("checks"));
+        
assertTrue(validateProxyConnectivityOutputProperties.containsKey("category"));
+        
assertTrue(validateProxyConnectivityOutputProperties.containsKey("recovery"));
         Map<?, ?> executeUpdateTool = findTool(capabilities, 
"database_gateway_execute_update");
         Map<?, ?> executeUpdateOutputProperties = (Map<?, ?>) ((Map<?, ?>) 
executeUpdateTool.get("outputSchema")).get("properties");
         assertTrue(executeUpdateOutputProperties.containsKey("response_mode"));
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 b68526ec8a4..18d6e8c45ed 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
@@ -55,7 +55,7 @@ class ToolDefinitionRegistryTest {
     
     @Test
     void assertGetSupportedTools() {
-        assertThat(ToolDefinitionRegistry.getSupportedTools(), 
contains("database_gateway_search_metadata", "database_gateway_execute_query",
+        assertThat(ToolDefinitionRegistry.getSupportedTools(), 
contains("database_gateway_search_metadata", 
"database_gateway_validate_proxy_connectivity", 
"database_gateway_execute_query",
                 "database_gateway_execute_update", 
"database_gateway_apply_workflow", "database_gateway_validate_workflow"));
     }
     
@@ -63,20 +63,22 @@ class ToolDefinitionRegistryTest {
     void assertGetSupportedToolDescriptors() {
         List<MCPToolDescriptor> actual = 
ToolDefinitionRegistry.getSupportedToolDescriptors();
         assertThat(actual.stream().map(MCPToolDescriptor::getName).toList(),
-                is(List.of("database_gateway_search_metadata", 
"database_gateway_execute_query", "database_gateway_execute_update",
+                is(List.of("database_gateway_search_metadata", 
"database_gateway_validate_proxy_connectivity", 
"database_gateway_execute_query", "database_gateway_execute_update",
                         "database_gateway_apply_workflow", 
"database_gateway_validate_workflow")));
         assertToolFields(actual.get(0), List.of("database", "schema", "query", 
"object_types"));
-        assertToolFields(actual.get(1), List.of("database", "schema", "sql", 
"max_rows", "timeout_ms"));
-        assertRequiredFields(actual.get(1), List.of("database", "sql"));
-        assertToolFields(actual.get(2), List.of("database", "schema", "sql", 
"execution_mode", "max_rows", "timeout_ms"));
-        assertRequiredFields(actual.get(2), List.of("database", "sql", 
"execution_mode"));
-        assertField(actual.get(2), "execution_mode", "string", 
List.of("execute", "preview"), true);
-        assertField(actual.get(2), "max_rows", "integer", List.of(), false);
-        assertField(actual.get(2), "timeout_ms", "integer", List.of(), false);
-        assertToolFields(actual.get(3), List.of("plan_id", "execution_mode", 
"approved_steps"));
-        assertRequiredFields(actual.get(3), List.of("plan_id", 
"execution_mode"));
-        assertField(actual.get(3), "execution_mode", "string", 
List.of("preview", "review-then-execute", "manual-only"), true);
-        assertField(actual.get(3), "approved_steps", "array", List.of(), 
false);
+        assertToolFields(actual.get(1), List.of("database"));
+        assertRequiredFields(actual.get(1), List.of("database"));
+        assertToolFields(actual.get(2), List.of("database", "schema", "sql", 
"max_rows", "timeout_ms"));
+        assertRequiredFields(actual.get(2), List.of("database", "sql"));
+        assertToolFields(actual.get(3), List.of("database", "schema", "sql", 
"execution_mode", "max_rows", "timeout_ms"));
+        assertRequiredFields(actual.get(3), List.of("database", "sql", 
"execution_mode"));
+        assertField(actual.get(3), "execution_mode", "string", 
List.of("execute", "preview"), true);
+        assertField(actual.get(3), "max_rows", "integer", List.of(), false);
+        assertField(actual.get(3), "timeout_ms", "integer", List.of(), false);
+        assertToolFields(actual.get(4), List.of("plan_id", "execution_mode", 
"approved_steps"));
+        assertRequiredFields(actual.get(4), List.of("plan_id", 
"execution_mode"));
+        assertField(actual.get(4), "execution_mode", "string", 
List.of("preview", "review-then-execute", "manual-only"), true);
+        assertField(actual.get(4), "approved_steps", "array", List.of(), 
false);
     }
     
     @Test
diff --git 
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/ValidateProxyConnectivityToolHandlerTest.java
 
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/ValidateProxyConnectivityToolHandlerTest.java
new file mode 100644
index 00000000000..13fe5faba2d
--- /dev/null
+++ 
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/ValidateProxyConnectivityToolHandlerTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.core.tool.handler.metadata;
+
+import org.apache.shardingsphere.mcp.api.protocol.response.MCPResponse;
+import org.apache.shardingsphere.mcp.api.tool.MCPToolCall;
+import 
org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
+import 
org.apache.shardingsphere.mcp.support.database.tool.request.ProxyPreflightValidationRequest;
+import 
org.apache.shardingsphere.mcp.support.database.tool.response.ProxyPreflightCheckResult;
+import 
org.apache.shardingsphere.mcp.support.database.tool.response.ProxyPreflightValidationResult;
+import 
org.apache.shardingsphere.mcp.support.database.tool.service.ProxyPreflightValidationService;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+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 ValidateProxyConnectivityToolHandlerTest {
+    
+    @Test
+    void assertGetToolName() {
+        assertThat(new ValidateProxyConnectivityToolHandler().getToolName(), 
is("database_gateway_validate_proxy_connectivity"));
+    }
+    
+    @Test
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    void assertHandle() {
+        ProxyPreflightValidationService validationService = 
mock(ProxyPreflightValidationService.class);
+        when(validationService.validate(any(), any(), any()))
+                .thenReturn(ProxyPreflightValidationResult.ready("logic_db", 
List.of(ProxyPreflightCheckResult.passed("configuration", "Validated the 
request."))));
+        MCPDatabaseHandlerContext databaseContext = 
mock(MCPDatabaseHandlerContext.class);
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = 
mock(RuntimeDatabaseConfiguration.class);
+        
when(databaseContext.findRuntimeDatabaseConfiguration("logic_db")).thenReturn(Optional.of(runtimeDatabaseConfig));
+        MCPResponse actual = new 
ValidateProxyConnectivityToolHandler(validationService).handle(databaseContext, 
new MCPToolCall("session-1", Map.of(
+                "jdbcUrl", "jdbc:mysql://127.0.0.1:3307/logic_db",
+                "database", "logic_db")));
+        assertThat(actual.toPayload().get("response_mode"), is("validation"));
+        ArgumentCaptor<ProxyPreflightValidationRequest> requestCaptor = 
ArgumentCaptor.forClass(ProxyPreflightValidationRequest.class);
+        ArgumentCaptor<Function<String, 
Optional<RuntimeDatabaseConfiguration>>> resolverCaptor = 
ArgumentCaptor.forClass(Function.class);
+        verify(validationService).validate(requestCaptor.capture(), 
resolverCaptor.capture(), any());
+        assertThat(requestCaptor.getValue().getDatabase(), is("logic_db"));
+        assertThat(resolverCaptor.getValue().apply("logic_db"), 
is(Optional.of(runtimeDatabaseConfig)));
+    }
+}
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/MCPDatabaseHandlerContext.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/MCPDatabaseHandlerContext.java
index beb2f8993dc..cfd9d7c8496 100644
--- 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/MCPDatabaseHandlerContext.java
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/MCPDatabaseHandlerContext.java
@@ -18,11 +18,14 @@
 package org.apache.shardingsphere.mcp.support.database;
 
 import org.apache.shardingsphere.mcp.api.MCPHandlerContext;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
 import 
org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureCapabilityFacade;
 import 
org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureExecutionFacade;
 import 
org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
 import 
org.apache.shardingsphere.mcp.support.database.spi.MCPMetadataQueryFacade;
 
+import java.util.Optional;
+
 /**
  * Database-aware MCP handler context.
  */
@@ -62,4 +65,12 @@ public interface MCPDatabaseHandlerContext extends 
MCPHandlerContext {
      * @return capability facade
      */
     MCPFeatureCapabilityFacade getCapabilityFacade();
+    
+    /**
+     * Find runtime database configuration.
+     *
+     * @param databaseName database name
+     * @return runtime database configuration
+     */
+    Optional<RuntimeDatabaseConfiguration> 
findRuntimeDatabaseConfiguration(String databaseName);
 }
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/RuntimeDatabaseConnectionException.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/RuntimeDatabaseConnectionException.java
index 7d1d209cb60..ee892dc7a77 100644
--- 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/RuntimeDatabaseConnectionException.java
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/RuntimeDatabaseConnectionException.java
@@ -44,6 +44,8 @@ public final class RuntimeDatabaseConnectionException extends 
RuntimeException {
     
     public static final String CATEGORY_CONNECTION_FAILED = 
"connection_failed";
     
+    public static final String CATEGORY_DATABASE_NOT_VISIBLE = 
"database_not_visible";
+    
     private static final long serialVersionUID = -757957427736251437L;
     
     private final String databaseName;
@@ -89,6 +91,17 @@ public final class RuntimeDatabaseConnectionException 
extends RuntimeException {
         return new RuntimeDatabaseConnectionException(databaseName, 
resolveCategory(cause), cause);
     }
     
+    /**
+     * Create database not visible exception.
+     *
+     * @param databaseName database name
+     * @param cause cause
+     * @return runtime database connection exception
+     */
+    public static RuntimeDatabaseConnectionException databaseNotVisible(final 
String databaseName, final Throwable cause) {
+        return new RuntimeDatabaseConnectionException(databaseName, 
CATEGORY_DATABASE_NOT_VISIBLE, cause);
+    }
+    
     private static String resolveCategory(final SQLException cause) {
         String sqlState = Objects.toString(cause.getSQLState(), "");
         String message = Objects.toString(cause.getMessage(), 
"").toLowerCase(Locale.ENGLISH);
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/request/ProxyPreflightValidationRequest.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/request/ProxyPreflightValidationRequest.java
new file mode 100644
index 00000000000..ef77760014c
--- /dev/null
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/request/ProxyPreflightValidationRequest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.database.tool.request;
+
+import lombok.Getter;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Proxy preflight validation request.
+ */
+@Getter
+public final class ProxyPreflightValidationRequest {
+    
+    private final String database;
+    
+    public ProxyPreflightValidationRequest(final String database) {
+        this.database = Objects.toString(database, "");
+    }
+    
+    /**
+     * Create request from MCP tool arguments.
+     *
+     * @param arguments tool arguments
+     * @return request
+     */
+    public static ProxyPreflightValidationRequest from(final Map<String, 
Object> arguments) {
+        return new ProxyPreflightValidationRequest(getRawString(arguments, 
"database"));
+    }
+    
+    private static String getRawString(final Map<String, Object> arguments, 
final String fieldName) {
+        return Objects.toString(arguments.get(fieldName), "");
+    }
+}
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/response/ProxyPreflightCheckResult.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/response/ProxyPreflightCheckResult.java
new file mode 100644
index 00000000000..23711ae5d14
--- /dev/null
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/response/ProxyPreflightCheckResult.java
@@ -0,0 +1,95 @@
+/*
+ * 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.database.tool.response;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Proxy preflight check result.
+ */
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+@Getter
+public final class ProxyPreflightCheckResult {
+    
+    private static final String STATUS_PASSED = "passed";
+    
+    private static final String STATUS_FAILED = "failed";
+    
+    private static final String STATUS_SKIPPED = "skipped";
+    
+    private final String name;
+    
+    private final String status;
+    
+    private final String category;
+    
+    private final String message;
+    
+    /**
+     * Create a passed check result.
+     *
+     * @param name check name
+     * @param message check message
+     * @return check result
+     */
+    public static ProxyPreflightCheckResult passed(final String name, final 
String message) {
+        return new ProxyPreflightCheckResult(name, STATUS_PASSED, "ready", 
message);
+    }
+    
+    /**
+     * Create a failed check result.
+     *
+     * @param name check name
+     * @param category failure category
+     * @param message check message
+     * @return check result
+     */
+    public static ProxyPreflightCheckResult failed(final String name, final 
String category, final String message) {
+        return new ProxyPreflightCheckResult(name, STATUS_FAILED, category, 
message);
+    }
+    
+    /**
+     * Create a skipped check result.
+     *
+     * @param name check name
+     * @param message check message
+     * @return check result
+     */
+    public static ProxyPreflightCheckResult skipped(final String name, final 
String message) {
+        return new ProxyPreflightCheckResult(name, STATUS_SKIPPED, "skipped", 
message);
+    }
+    
+    /**
+     * Convert to payload.
+     *
+     * @return payload
+     */
+    public Map<String, Object> toPayload() {
+        Map<String, Object> result = new LinkedHashMap<>(4, 1F);
+        result.put("name", name);
+        result.put("status", status);
+        result.put("category", category);
+        result.put("message", message);
+        return result;
+    }
+}
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/response/ProxyPreflightValidationResult.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/response/ProxyPreflightValidationResult.java
new file mode 100644
index 00000000000..4af5fcfc809
--- /dev/null
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/response/ProxyPreflightValidationResult.java
@@ -0,0 +1,93 @@
+/*
+ * 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.database.tool.response;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.apache.shardingsphere.mcp.api.protocol.response.MCPResponse;
+import org.apache.shardingsphere.mcp.support.protocol.MCPPayloadFieldNames;
+import org.apache.shardingsphere.mcp.support.protocol.MCPResponseMode;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Proxy preflight validation result.
+ */
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+@Getter
+public final class ProxyPreflightValidationResult implements MCPResponse {
+    
+    private final String status;
+    
+    private final String database;
+    
+    private final List<ProxyPreflightCheckResult> checks;
+    
+    private final String category;
+    
+    private final Map<String, Object> recovery;
+    
+    /**
+     * Create a success result.
+     *
+     * @param database database name
+     * @param checks check results
+     * @return validation result
+     */
+    public static ProxyPreflightValidationResult ready(final String database, 
final List<ProxyPreflightCheckResult> checks) {
+        return new ProxyPreflightValidationResult("ready", 
Objects.toString(database, ""), checks, "ready", Map.of());
+    }
+    
+    /**
+     * Create a failure result.
+     *
+     * @param database database name
+     * @param checks check results
+     * @param category failure category
+     * @param recovery recovery payload
+     * @return validation result
+     */
+    public static ProxyPreflightValidationResult failed(final String database, 
final List<ProxyPreflightCheckResult> checks, final String category, final 
Map<String, Object> recovery) {
+        return new ProxyPreflightValidationResult("failed", 
Objects.toString(database, ""), checks, category, null == recovery ? Map.of() : 
recovery);
+    }
+    
+    @Override
+    public Map<String, Object> toPayload() {
+        Map<String, Object> result = new LinkedHashMap<>(6, 1F);
+        result.put("response_mode", MCPResponseMode.VALIDATION);
+        result.put("status", status);
+        result.put("database", database);
+        result.put("checks", createChecksPayload());
+        result.put("category", category);
+        result.put(MCPPayloadFieldNames.RECOVERY, recovery);
+        return result;
+    }
+    
+    private List<Map<String, Object>> createChecksPayload() {
+        List<Map<String, Object>> result = new LinkedList<>();
+        for (ProxyPreflightCheckResult each : checks) {
+            result.add(each.toPayload());
+        }
+        return result;
+    }
+}
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/service/ProxyPreflightValidationService.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/service/ProxyPreflightValidationService.java
new file mode 100644
index 00000000000..9d25609c988
--- /dev/null
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/tool/service/ProxyPreflightValidationService.java
@@ -0,0 +1,206 @@
+/*
+ * 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.database.tool.service;
+
+import lombok.RequiredArgsConstructor;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.MCPJdbcDatabaseProfileLoader;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.MCPJdbcMetadataLoader;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConnectionException;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseProfile;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.model.MCPDatabaseMetadata;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.model.MCPSchemaMetadata;
+import 
org.apache.shardingsphere.mcp.support.database.tool.request.ProxyPreflightValidationRequest;
+import 
org.apache.shardingsphere.mcp.support.database.tool.response.ProxyPreflightCheckResult;
+import 
org.apache.shardingsphere.mcp.support.database.tool.response.ProxyPreflightValidationResult;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Proxy preflight validation service.
+ */
+@RequiredArgsConstructor
+public final class ProxyPreflightValidationService {
+    
+    private static final String PRE_FLIGHT_BINDING_DATABASE = 
"__preflight_validation__";
+    
+    private final MCPJdbcDatabaseProfileLoader profileLoader;
+    
+    private final MCPJdbcMetadataLoader metadataLoader;
+    
+    public ProxyPreflightValidationService() {
+        this(new MCPJdbcDatabaseProfileLoader(), new MCPJdbcMetadataLoader());
+    }
+    
+    /**
+     * Validate proxy preflight request.
+     *
+     * @param request validation request
+     * @param runtimeDatabaseResolver runtime database resolver
+     * @param recoveryFactory runtime recovery factory
+     * @return validation result
+     */
+    public ProxyPreflightValidationResult validate(final 
ProxyPreflightValidationRequest request,
+                                                   final Function<String, 
Optional<RuntimeDatabaseConfiguration>> runtimeDatabaseResolver,
+                                                   final 
Function<RuntimeDatabaseConnectionException, Map<String, Object>> 
recoveryFactory) {
+        List<ProxyPreflightCheckResult> checks = new LinkedList<>();
+        String database = normalize(request.getDatabase());
+        Optional<RuntimeDatabaseConfiguration> runtimeDatabaseConfig = 
findRuntimeDatabaseConfiguration(database, runtimeDatabaseResolver);
+        if (runtimeDatabaseConfig.isEmpty()) {
+            RuntimeDatabaseConnectionException ex = 
createMissingRuntimeDatabaseException(database);
+            checks.add(ProxyPreflightCheckResult.failed("configuration", 
ex.getCategory(), "The requested database is not configured for this MCP 
runtime."));
+            appendSkippedChecks(checks, "jdbc_driver", "configuration 
validation did not finish");
+            appendSkippedChecks(checks, "jdbc_connectivity", "configuration 
validation did not finish");
+            appendSkippedChecks(checks, "metadata_read", "configuration 
validation did not finish");
+            appendSkippedChecks(checks, "database_visibility", "configuration 
validation did not finish");
+            return createFailureResult(database, checks, ex, recoveryFactory);
+        }
+        checks.add(ProxyPreflightCheckResult.passed("configuration", "Resolved 
the configured runtime database."));
+        RuntimeDatabaseProfile databaseProfile;
+        try {
+            databaseProfile = profileLoader.load(database, 
runtimeDatabaseConfig.get());
+            checks.add(ProxyPreflightCheckResult.passed("jdbc_driver", "Loaded 
the configured JDBC driver."));
+            checks.add(ProxyPreflightCheckResult.passed("jdbc_connectivity", 
"Opened a JDBC connection and validated the configured database type."));
+        } catch (final RuntimeDatabaseConnectionException ex) {
+            appendProfileFailureChecks(checks, ex);
+            appendSkippedChecks(checks, "metadata_read", "driver or 
connectivity validation failed");
+            appendSkippedChecks(checks, "database_visibility", "driver or 
connectivity validation failed");
+            return createFailureResult(database, checks, ex, recoveryFactory);
+        }
+        MCPDatabaseMetadata databaseMetadata;
+        try {
+            databaseMetadata = metadataLoader.load(database, 
runtimeDatabaseConfig.get(), databaseProfile);
+            checks.add(ProxyPreflightCheckResult.passed("metadata_read", "Read 
metadata through the configured JDBC connection."));
+        } catch (final RuntimeDatabaseConnectionException ex) {
+            checks.add(ProxyPreflightCheckResult.failed("metadata_read", 
ex.getCategory(), "Failed to read metadata through the configured JDBC 
connection."));
+            appendSkippedChecks(checks, "database_visibility", "metadata 
validation failed");
+            return createFailureResult(database, checks, ex, recoveryFactory);
+        }
+        try {
+            validateDatabaseVisibility(database, runtimeDatabaseConfig.get(), 
databaseMetadata);
+            checks.add(ProxyPreflightCheckResult.passed("database_visibility", 
"Validated the requested database name against visible JDBC metadata and 
connection context."));
+        } catch (final RuntimeDatabaseConnectionException ex) {
+            checks.add(ProxyPreflightCheckResult.failed("database_visibility", 
ex.getCategory(), "The requested database name is not visible to the configured 
JDBC connection."));
+            return createFailureResult(database, checks, ex, recoveryFactory);
+        }
+        return ProxyPreflightValidationResult.ready(database, checks);
+    }
+    
+    private Optional<RuntimeDatabaseConfiguration> 
findRuntimeDatabaseConfiguration(final String database,
+                                                                               
     final Function<String, Optional<RuntimeDatabaseConfiguration>> 
runtimeDatabaseResolver) {
+        return database.isEmpty() ? Optional.empty() : 
runtimeDatabaseResolver.apply(database);
+    }
+    
+    private RuntimeDatabaseConnectionException 
createMissingRuntimeDatabaseException(final String database) {
+        return 
RuntimeDatabaseConnectionException.invalidConfiguration(resolveExceptionDatabaseName(database),
+                new IllegalStateException("Proxy preflight validation requires 
one configured runtime database."));
+    }
+    
+    private void appendProfileFailureChecks(final 
List<ProxyPreflightCheckResult> checks, final 
RuntimeDatabaseConnectionException ex) {
+        if 
(RuntimeDatabaseConnectionException.CATEGORY_MISSING_JDBC_DRIVER.equals(ex.getCategory()))
 {
+            checks.add(ProxyPreflightCheckResult.failed("jdbc_driver", 
ex.getCategory(), "Failed to load the configured JDBC driver."));
+            appendSkippedChecks(checks, "jdbc_connectivity", "driver loading 
failed");
+            return;
+        }
+        checks.add(ProxyPreflightCheckResult.passed("jdbc_driver", "Loaded the 
configured JDBC driver."));
+        checks.add(ProxyPreflightCheckResult.failed("jdbc_connectivity", 
ex.getCategory(), "Failed to open a JDBC connection or validate the configured 
database type."));
+    }
+    
+    private void appendSkippedChecks(final List<ProxyPreflightCheckResult> 
checks, final String name, final String reason) {
+        checks.add(ProxyPreflightCheckResult.skipped(name, 
String.format("Skipped because %s.", reason)));
+    }
+    
+    private ProxyPreflightValidationResult createFailureResult(final String 
database, final List<ProxyPreflightCheckResult> checks, final 
RuntimeDatabaseConnectionException cause,
+                                                               final 
Function<RuntimeDatabaseConnectionException, Map<String, Object>> 
recoveryFactory) {
+        return ProxyPreflightValidationResult.failed(database, checks, 
cause.getCategory(), recoveryFactory.apply(cause));
+    }
+    
+    private void validateDatabaseVisibility(final String database, final 
RuntimeDatabaseConfiguration runtimeDatabaseConfig, final MCPDatabaseMetadata 
databaseMetadata) {
+        if (containsVisibleSchema(databaseMetadata, database)) {
+            return;
+        }
+        try (Connection connection = 
runtimeDatabaseConfig.openConnection(resolveExceptionDatabaseName(database))) {
+            if (isVisibleDatabase(connection, database)) {
+                return;
+            }
+        } catch (final SQLException ex) {
+            throw 
RuntimeDatabaseConnectionException.connectionFailed(resolveExceptionDatabaseName(database),
 ex);
+        }
+        throw 
RuntimeDatabaseConnectionException.databaseNotVisible(resolveExceptionDatabaseName(database),
+                new IllegalStateException(String.format("Requested database 
`%s` is not visible to the configured JDBC connection.", database)));
+    }
+    
+    private boolean containsVisibleSchema(final MCPDatabaseMetadata 
databaseMetadata, final String database) {
+        for (MCPSchemaMetadata each : databaseMetadata.getSchemas()) {
+            if (database.equalsIgnoreCase(each.getSchema())) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    private boolean isVisibleDatabase(final Connection connection, final 
String database) throws SQLException {
+        return matches(connection.getCatalog(), database)
+                || matches(connection.getSchema(), database)
+                || containsCatalog(connection.getMetaData(), database)
+                || containsSchema(connection.getMetaData(), database);
+    }
+    
+    private boolean containsCatalog(final DatabaseMetaData databaseMetaData, 
final String database) throws SQLException {
+        try (ResultSet resultSet = databaseMetaData.getCatalogs()) {
+            while (resultSet.next()) {
+                if (matches(resultSet.getString(1), database)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+    
+    private boolean containsSchema(final DatabaseMetaData databaseMetaData, 
final String database) throws SQLException {
+        try (ResultSet resultSet = databaseMetaData.getSchemas()) {
+            while (resultSet.next()) {
+                if (matches(resultSet.getString("TABLE_SCHEM"), database)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+    
+    private boolean matches(final String actualValue, final String 
expectedValue) {
+        return !Objects.toString(actualValue, "").trim().isEmpty() && 
actualValue.trim().equalsIgnoreCase(expectedValue);
+    }
+    
+    private String resolveExceptionDatabaseName(final String database) {
+        return database.isEmpty() ? PRE_FLIGHT_BINDING_DATABASE : database;
+    }
+    
+    private String normalize(final String value) {
+        return Objects.toString(value, "").trim();
+    }
+}
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPModelFirstContractPayloadBuilder.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPModelFirstContractPayloadBuilder.java
index 1434d25b520..c6ac58476d5 100644
--- 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPModelFirstContractPayloadBuilder.java
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPModelFirstContractPayloadBuilder.java
@@ -35,6 +35,8 @@ final class MCPModelFirstContractPayloadBuilder {
     
     private static final String PLANNING_TOOL_NAME_PREFIX = 
"database_gateway_plan_";
     
+    private static final String PREFLIGHT_TOOL_NAME = 
"database_gateway_validate_proxy_connectivity";
+    
     private static final String CATALOG_RESOURCE_URI = 
"shardingsphere://capabilities";
     
     private static final String ARGUMENT_COMPLETION_METHOD = 
"completion/complete";
@@ -44,13 +46,14 @@ final class MCPModelFirstContractPayloadBuilder {
     private final MCPDescriptorCatalog catalog;
     
     Map<String, Object> createModelFirstSummary() {
-        Map<String, Object> result = new LinkedHashMap<>(10, 1F);
+        Map<String, Object> result = new LinkedHashMap<>(11, 1F);
         result.put("official_discovery_methods", 
createOfficialDiscoveryMethods());
         result.put("argument_completion_method", ARGUMENT_COMPLETION_METHOD);
         result.put("catalog_resource_role", CATALOG_RESOURCE_URI
                 + " complements MCP list methods with ShardingSphere domain 
capability guidance, workflow guidance, and side-effect notes.");
         result.put("optional_catalog_resource", CATALOG_RESOURCE_URI);
         result.put("metadata_rule", createMetadataRule());
+        result.put("preflight_rule", createPreflightRule());
         result.put("sql_tool_selection", createSqlToolSelection());
         result.put("side_effect_rule", "Preview side effects first; execute 
only when the requested side effect is still intended.");
         result.put("workflow_rule", createWorkflowRule());
@@ -60,12 +63,13 @@ final class MCPModelFirstContractPayloadBuilder {
     }
     
     Map<String, Object> createModelContract() {
-        Map<String, Object> result = new LinkedHashMap<>(11, 1F);
+        Map<String, Object> result = new LinkedHashMap<>(12, 1F);
         result.put("public_surface_source", MCP_LIST_METHODS_SOURCE);
         result.put("official_discovery_methods", 
createOfficialDiscoveryMethods());
         result.put("argument_completion_method", ARGUMENT_COMPLETION_METHOD);
         result.put("optional_catalog_resource", CATALOG_RESOURCE_URI);
         result.put("metadata_first_resource", "shardingsphere://databases");
+        result.put("preflight_rule", "Use 
database_gateway_validate_proxy_connectivity with a configured database name 
before onboarding or troubleshooting runtime connectivity.");
         result.put("sql_tool_selection", Map.of(
                 "read_only", "Use database_gateway_execute_query for one 
classifier-approved SELECT or EXPLAIN ANALYZE statement.",
                 "side_effecting", "Use database_gateway_execute_update with 
execution_mode=preview before execution."));
@@ -78,12 +82,13 @@ final class MCPModelFirstContractPayloadBuilder {
     }
     
     Map<String, Object> createSurfaceSummary() {
-        Map<String, Object> result = new LinkedHashMap<>(10, 1F);
+        Map<String, Object> result = new LinkedHashMap<>(11, 1F);
         result.put("official_discovery_methods", 
createOfficialDiscoveryMethods());
         result.put("argument_completion_method", ARGUMENT_COMPLETION_METHOD);
         result.put("optional_catalog_resource", CATALOG_RESOURCE_URI);
         result.put("metadata_resource", "shardingsphere://databases");
         result.put("metadata_search_tool", "database_gateway_search_metadata");
+        result.put("preflight_validation_tool", PREFLIGHT_TOOL_NAME);
         result.put("read_only_sql_tool", "database_gateway_execute_query");
         result.put("side_effect_sql_tool", "database_gateway_execute_update");
         result.put("workflow_tools", 
List.of("database_gateway_apply_workflow", 
"database_gateway_validate_workflow"));
@@ -126,6 +131,9 @@ final class MCPModelFirstContractPayloadBuilder {
                         "call_tool database_gateway_search_metadata", 
"read_resource returned resource.uri"),
                         "Stop when the requested metadata detail resource is 
read.",
                         List.of("database_gateway_search_metadata"), 
List.of("shardingsphere://databases")),
+                createCommonFlow("validate_runtime_database", 
List.of("read_resource shardingsphere://databases", "call_tool 
database_gateway_validate_proxy_connectivity"),
+                        "Stop after the configured runtime database reports 
ready or returns structured recovery guidance.",
+                        List.of(PREFLIGHT_TOOL_NAME), 
List.of("shardingsphere://databases")),
                 createCommonFlow("read_only_sql", List.of("read_resource 
shardingsphere://databases/{database}/capabilities", "call_tool 
database_gateway_execute_query"),
                         "Use one SELECT or EXPLAIN ANALYZE statement and stop 
after the result is reported.",
                         List.of("database_gateway_execute_query"), 
List.of("shardingsphere://databases/{database}/capabilities")),
@@ -176,6 +184,14 @@ final class MCPModelFirstContractPayloadBuilder {
         return result;
     }
     
+    private Map<String, Object> createPreflightRule() {
+        Map<String, Object> result = new LinkedHashMap<>(3, 1F);
+        result.put("tool", PREFLIGHT_TOOL_NAME);
+        result.put("input_rule", "Pass only a configured runtime database 
name.");
+        result.put("secret_rule", "Connection details stay in administrator 
runtime configuration and are not tool arguments.");
+        return result;
+    }
+    
     private Map<String, Object> createSqlToolSelection() {
         Map<String, Object> result = new LinkedHashMap<>(2, 1F);
         Map<String, Object> readOnly = new LinkedHashMap<>(2, 1F);
diff --git 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/RuntimeDatabaseConnectionExceptionTest.java
 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/RuntimeDatabaseConnectionExceptionTest.java
index 2f6a7a1aaca..3dbc4d787e1 100644
--- 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/RuntimeDatabaseConnectionExceptionTest.java
+++ 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/RuntimeDatabaseConnectionExceptionTest.java
@@ -72,4 +72,11 @@ class RuntimeDatabaseConnectionExceptionTest {
         RuntimeDatabaseConnectionException actual = 
RuntimeDatabaseConnectionException.connectionFailed("logic_db", new 
SQLException("Broken connection"));
         assertThat(actual.getCategory(), 
is(RuntimeDatabaseConnectionException.CATEGORY_CONNECTION_FAILED));
     }
+    
+    @Test
+    void assertDatabaseNotVisible() {
+        RuntimeDatabaseConnectionException actual = 
RuntimeDatabaseConnectionException.databaseNotVisible("logic_db", new 
IllegalStateException("not visible"));
+        assertThat(actual.getMessage(), is("Runtime database `logic_db` 
connection failed: database_not_visible."));
+        assertThat(actual.getCategory(), 
is(RuntimeDatabaseConnectionException.CATEGORY_DATABASE_NOT_VISIBLE));
+    }
 }
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/OfficialMCPToolNames.java
 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/request/ProxyPreflightValidationRequestTest.java
similarity index 51%
copy from 
test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/OfficialMCPToolNames.java
copy to 
mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/request/ProxyPreflightValidationRequestTest.java
index 5bef92123fc..7b995801339 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/OfficialMCPToolNames.java
+++ 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/request/ProxyPreflightValidationRequestTest.java
@@ -15,33 +15,28 @@
  * limitations under the License.
  */
 
-package org.apache.shardingsphere.test.e2e.mcp.support;
+package org.apache.shardingsphere.mcp.support.database.tool.request;
 
-import java.util.List;
+import org.junit.jupiter.api.Test;
 
-/**
- * Official MCP tool names packaged by default.
- */
-public final class OfficialMCPToolNames {
-    
-    private static final List<String> ALL = List.of(
-            "database_gateway_search_metadata",
-            "database_gateway_execute_query",
-            "database_gateway_execute_update",
-            "database_gateway_apply_workflow",
-            "database_gateway_validate_workflow",
-            "database_gateway_plan_encrypt_rule",
-            "database_gateway_plan_mask_rule");
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class ProxyPreflightValidationRequestTest {
     
-    private OfficialMCPToolNames() {
+    @Test
+    void assertFromPreservesDatabaseArgument() {
+        ProxyPreflightValidationRequest actual = 
ProxyPreflightValidationRequest.from(Map.of(
+                "jdbcUrl", " jdbc:mysql://127.0.0.1:3307/logic_db ",
+                "database", " logic_db "));
+        assertThat(actual.getDatabase(), is(" logic_db "));
     }
     
-    /**
-     * Get official MCP tool names.
-     *
-     * @return official MCP tool names
-     */
-    public static List<String> getAll() {
-        return ALL;
+    @Test
+    void assertFromDefaultsMissingDatabase() {
+        ProxyPreflightValidationRequest actual = 
ProxyPreflightValidationRequest.from(Map.of());
+        assertThat(actual.getDatabase(), is(""));
     }
 }
diff --git 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/response/ProxyPreflightCheckResultTest.java
 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/response/ProxyPreflightCheckResultTest.java
new file mode 100644
index 00000000000..10f219ffd92
--- /dev/null
+++ 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/response/ProxyPreflightCheckResultTest.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.database.tool.response;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class ProxyPreflightCheckResultTest {
+    
+    @Test
+    void assertPassed() {
+        ProxyPreflightCheckResult actual = 
ProxyPreflightCheckResult.passed("jdbc_connectivity", "Opened a JDBC 
connection.");
+        assertThat(actual.getName(), is("jdbc_connectivity"));
+        assertThat(actual.getStatus(), is("passed"));
+        assertThat(actual.getCategory(), is("ready"));
+        assertThat(actual.getMessage(), is("Opened a JDBC connection."));
+    }
+    
+    @Test
+    void assertFailed() {
+        ProxyPreflightCheckResult actual = 
ProxyPreflightCheckResult.failed("jdbc_driver", "missing_jdbc_driver", "Failed 
to load the configured JDBC driver.");
+        assertThat(actual.getName(), is("jdbc_driver"));
+        assertThat(actual.getStatus(), is("failed"));
+        assertThat(actual.getCategory(), is("missing_jdbc_driver"));
+        assertThat(actual.getMessage(), is("Failed to load the configured JDBC 
driver."));
+    }
+    
+    @Test
+    void assertSkipped() {
+        ProxyPreflightCheckResult actual = 
ProxyPreflightCheckResult.skipped("database_visibility", "Skipped because no 
database was provided.");
+        assertThat(actual.getName(), is("database_visibility"));
+        assertThat(actual.getStatus(), is("skipped"));
+        assertThat(actual.getCategory(), is("skipped"));
+        assertThat(actual.getMessage(), is("Skipped because no database was 
provided."));
+    }
+    
+    @Test
+    void assertToPayload() {
+        Map<String, Object> actual = 
ProxyPreflightCheckResult.failed("metadata_read", "connection_failed", "Failed 
to read metadata.").toPayload();
+        assertThat(actual, is(Map.of(
+                "name", "metadata_read",
+                "status", "failed",
+                "category", "connection_failed",
+                "message", "Failed to read metadata.")));
+    }
+}
diff --git 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/response/ProxyPreflightValidationResultTest.java
 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/response/ProxyPreflightValidationResultTest.java
new file mode 100644
index 00000000000..5c273be299e
--- /dev/null
+++ 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/response/ProxyPreflightValidationResultTest.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.support.database.tool.response;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class ProxyPreflightValidationResultTest {
+    
+    @Test
+    void assertReady() {
+        ProxyPreflightValidationResult actual = 
ProxyPreflightValidationResult.ready("logic_db", 
List.of(ProxyPreflightCheckResult.passed("configuration", "Validated the 
request.")));
+        assertThat(actual.getStatus(), is("ready"));
+        assertThat(actual.getDatabase(), is("logic_db"));
+        assertThat(actual.getCategory(), is("ready"));
+        assertThat(actual.getRecovery(), is(Map.of()));
+    }
+    
+    @Test
+    void assertFailed() {
+        Map<String, Object> recovery = Map.of("category", "connection_failed");
+        ProxyPreflightValidationResult actual = 
ProxyPreflightValidationResult.failed("logic_db",
+                List.of(ProxyPreflightCheckResult.failed("jdbc_connectivity", 
"connection_failed", "Failed to open a JDBC connection.")), 
"connection_failed", recovery);
+        assertThat(actual.getStatus(), is("failed"));
+        assertThat(actual.getDatabase(), is("logic_db"));
+        assertThat(actual.getCategory(), is("connection_failed"));
+        assertThat(actual.getRecovery(), is(recovery));
+    }
+    
+    @Test
+    void assertToPayload() {
+        Map<String, Object> actual = 
ProxyPreflightValidationResult.failed("logic_db",
+                List.of(ProxyPreflightCheckResult.failed("metadata_read", 
"connection_failed", "Failed to read metadata.")),
+                "connection_failed", Map.of("category", 
"connection_failed")).toPayload();
+        assertThat(actual.get("response_mode"), is("validation"));
+        assertThat(actual.get("status"), is("failed"));
+        assertThat(actual.get("database"), is("logic_db"));
+        assertThat(actual.get("category"), is("connection_failed"));
+        assertThat(actual.get("recovery"), is(Map.of("category", 
"connection_failed")));
+        assertThat(actual.get("checks"), is(List.of(Map.of(
+                "name", "metadata_read",
+                "status", "failed",
+                "category", "connection_failed",
+                "message", "Failed to read metadata."))));
+    }
+}
diff --git 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/service/ProxyPreflightValidationServiceTest.java
 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/service/ProxyPreflightValidationServiceTest.java
new file mode 100644
index 00000000000..7538b24650d
--- /dev/null
+++ 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/tool/service/ProxyPreflightValidationServiceTest.java
@@ -0,0 +1,280 @@
+/*
+ * 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.database.tool.service;
+
+import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.MCPJdbcDatabaseProfileLoader;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.MCPJdbcMetadataLoader;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConnectionException;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseProfile;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.model.MCPDatabaseMetadata;
+import 
org.apache.shardingsphere.mcp.support.database.metadata.model.MCPSchemaMetadata;
+import 
org.apache.shardingsphere.mcp.support.database.tool.request.ProxyPreflightValidationRequest;
+import 
org.apache.shardingsphere.mcp.support.database.tool.response.ProxyPreflightValidationResult;
+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 java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.DriverPropertyInfo;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.SQLTimeoutException;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.logging.Logger;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+class ProxyPreflightValidationServiceTest {
+    
+    @Test
+    void assertValidateWithMissingDatabase() {
+        MCPJdbcDatabaseProfileLoader profileLoader = 
mock(MCPJdbcDatabaseProfileLoader.class);
+        MCPJdbcMetadataLoader metadataLoader = 
mock(MCPJdbcMetadataLoader.class);
+        ProxyPreflightValidationResult actual = new 
ProxyPreflightValidationService(profileLoader, metadataLoader)
+                .validate(new ProxyPreflightValidationRequest(""),
+                        ignored -> Optional.empty(),
+                        
ProxyPreflightValidationServiceTest::createRecoveryPayload);
+        Map<String, Object> actualPayload = actual.toPayload();
+        assertThat(actualPayload.get("status"), is("failed"));
+        assertThat(actualPayload.get("category"), is("invalid_configuration"));
+        assertThat(actualPayload.get("recovery"), is(Map.of("category", 
"invalid_configuration")));
+        assertThat(((Map<?, ?>) ((List<?>) 
actualPayload.get("checks")).get(0)).get("name"), is("configuration"));
+        assertThat(((Map<?, ?>) ((List<?>) 
actualPayload.get("checks")).get(1)).get("status"), is("skipped"));
+        verifyNoInteractions(profileLoader, metadataLoader);
+    }
+    
+    @Test
+    void assertValidateWithUnknownDatabase() {
+        MCPJdbcDatabaseProfileLoader profileLoader = 
mock(MCPJdbcDatabaseProfileLoader.class);
+        MCPJdbcMetadataLoader metadataLoader = 
mock(MCPJdbcMetadataLoader.class);
+        ProxyPreflightValidationResult actual = new 
ProxyPreflightValidationService(profileLoader, metadataLoader)
+                .validate(new ProxyPreflightValidationRequest("logic_db"),
+                        ignored -> Optional.empty(),
+                        
ProxyPreflightValidationServiceTest::createRecoveryPayload);
+        Map<String, Object> actualPayload = actual.toPayload();
+        assertThat(actualPayload.get("status"), is("failed"));
+        assertThat(actualPayload.get("category"), is("invalid_configuration"));
+        assertThat(((Map<?, ?>) ((List<?>) 
actualPayload.get("checks")).get(0)).get("status"), is("failed"));
+        verifyNoInteractions(profileLoader, metadataLoader);
+    }
+    
+    @Test
+    void assertValidateWithConfiguredDatabase() {
+        MCPJdbcDatabaseProfileLoader profileLoader = 
mock(MCPJdbcDatabaseProfileLoader.class);
+        MCPJdbcMetadataLoader metadataLoader = 
mock(MCPJdbcMetadataLoader.class);
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = 
createRuntimeDatabaseConfiguration();
+        when(profileLoader.load(any(), 
any(RuntimeDatabaseConfiguration.class))).thenReturn(createProfile());
+        when(metadataLoader.load(any(), 
any(RuntimeDatabaseConfiguration.class), 
any(RuntimeDatabaseProfile.class))).thenReturn(createMetadata("logic_db"));
+        ProxyPreflightValidationService service = new 
ProxyPreflightValidationService(profileLoader, metadataLoader);
+        ProxyPreflightValidationResult actual = service.validate(new 
ProxyPreflightValidationRequest("logic_db"),
+                ignored -> Optional.of(runtimeDatabaseConfig),
+                ProxyPreflightValidationServiceTest::createRecoveryPayload);
+        Map<String, Object> actualPayload = actual.toPayload();
+        assertThat(actualPayload.get("status"), is("ready"));
+        ArgumentCaptor<RuntimeDatabaseConfiguration> configurationCaptor = 
ArgumentCaptor.forClass(RuntimeDatabaseConfiguration.class);
+        verify(profileLoader).load(any(), configurationCaptor.capture());
+        assertThat(configurationCaptor.getValue(), is(runtimeDatabaseConfig));
+    }
+    
+    @Test
+    void assertValidateWithVisibleDatabase() {
+        MCPJdbcDatabaseProfileLoader profileLoader = 
mock(MCPJdbcDatabaseProfileLoader.class);
+        MCPJdbcMetadataLoader metadataLoader = 
mock(MCPJdbcMetadataLoader.class);
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = 
createRuntimeDatabaseConfiguration();
+        when(profileLoader.load(any(), 
any(RuntimeDatabaseConfiguration.class))).thenReturn(createProfile());
+        when(metadataLoader.load(any(), 
any(RuntimeDatabaseConfiguration.class), 
any(RuntimeDatabaseProfile.class))).thenReturn(createMetadata("logic_db"));
+        ProxyPreflightValidationResult actual = new 
ProxyPreflightValidationService(profileLoader, metadataLoader)
+                .validate(new ProxyPreflightValidationRequest("logic_db"),
+                        ignored -> Optional.of(runtimeDatabaseConfig),
+                        
ProxyPreflightValidationServiceTest::createRecoveryPayload);
+        Map<String, Object> actualPayload = actual.toPayload();
+        assertThat(actualPayload.get("status"), is("ready"));
+        assertThat(((Map<?, ?>) ((List<?>) 
actualPayload.get("checks")).get(4)).get("status"), is("passed"));
+    }
+    
+    @Test
+    void assertValidateWithInvisibleDatabase() {
+        MCPJdbcDatabaseProfileLoader profileLoader = 
mock(MCPJdbcDatabaseProfileLoader.class);
+        MCPJdbcMetadataLoader metadataLoader = 
mock(MCPJdbcMetadataLoader.class);
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = new 
RuntimeDatabaseConfiguration("MySQL", InvisibleDatabaseDriver.JDBC_URL, "demo", 
"", InvisibleDatabaseDriver.class.getName());
+        when(profileLoader.load(any(), 
any(RuntimeDatabaseConfiguration.class))).thenReturn(createProfile());
+        when(metadataLoader.load(any(), 
any(RuntimeDatabaseConfiguration.class), 
any(RuntimeDatabaseProfile.class))).thenReturn(createMetadata("public"));
+        ProxyPreflightValidationResult actual = new 
ProxyPreflightValidationService(profileLoader, metadataLoader)
+                .validate(new ProxyPreflightValidationRequest("logic_db"),
+                        ignored -> Optional.of(runtimeDatabaseConfig),
+                        
ProxyPreflightValidationServiceTest::createRecoveryPayload);
+        Map<String, Object> actualPayload = actual.toPayload();
+        assertThat(actualPayload.get("status"), is("failed"));
+        assertThat(actualPayload.get("category"), 
is(RuntimeDatabaseConnectionException.CATEGORY_DATABASE_NOT_VISIBLE));
+        assertThat(actualPayload.get("recovery"), is(Map.of("category", 
RuntimeDatabaseConnectionException.CATEGORY_DATABASE_NOT_VISIBLE)));
+        assertThat(((Map<?, ?>) ((List<?>) 
actualPayload.get("checks")).get(4)).get("status"), is("failed"));
+    }
+    
+    @Test
+    void assertValidateWithMetadataReadFailure() {
+        MCPJdbcDatabaseProfileLoader profileLoader = 
mock(MCPJdbcDatabaseProfileLoader.class);
+        MCPJdbcMetadataLoader metadataLoader = 
mock(MCPJdbcMetadataLoader.class);
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = 
createRuntimeDatabaseConfiguration();
+        when(profileLoader.load(any(), 
any(RuntimeDatabaseConfiguration.class))).thenReturn(createProfile());
+        when(metadataLoader.load(any(), 
any(RuntimeDatabaseConfiguration.class), any(RuntimeDatabaseProfile.class)))
+                
.thenThrow(RuntimeDatabaseConnectionException.connectionFailed("logic_db", new 
SQLException("Broken connection")));
+        ProxyPreflightValidationResult actual = new 
ProxyPreflightValidationService(profileLoader, metadataLoader)
+                .validate(new ProxyPreflightValidationRequest("logic_db"),
+                        ignored -> Optional.of(runtimeDatabaseConfig),
+                        
ProxyPreflightValidationServiceTest::createRecoveryPayload);
+        Map<String, Object> actualPayload = actual.toPayload();
+        assertThat(actualPayload.get("status"), is("failed"));
+        assertThat(actualPayload.get("category"), 
is(RuntimeDatabaseConnectionException.CATEGORY_CONNECTION_FAILED));
+        assertThat(((Map<?, ?>) ((List<?>) 
actualPayload.get("checks")).get(3)).get("status"), is("failed"));
+        assertThat(((Map<?, ?>) ((List<?>) 
actualPayload.get("checks")).get(4)).get("status"), is("skipped"));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("assertValidateFailsDuringProfileLoadCases")
+    void assertValidateFailsDuringProfileLoad(final String name, final 
RuntimeDatabaseConnectionException cause, final String expectedDriverStatus,
+                                              final String 
expectedConnectivityStatus, final String expectedCategory) {
+        MCPJdbcDatabaseProfileLoader profileLoader = 
mock(MCPJdbcDatabaseProfileLoader.class);
+        MCPJdbcMetadataLoader metadataLoader = 
mock(MCPJdbcMetadataLoader.class);
+        RuntimeDatabaseConfiguration runtimeDatabaseConfig = 
createRuntimeDatabaseConfiguration();
+        when(profileLoader.load(any(), 
any(RuntimeDatabaseConfiguration.class))).thenThrow(cause);
+        ProxyPreflightValidationResult actual = new 
ProxyPreflightValidationService(profileLoader, metadataLoader)
+                .validate(new ProxyPreflightValidationRequest("logic_db"),
+                        ignored -> Optional.of(runtimeDatabaseConfig),
+                        
ProxyPreflightValidationServiceTest::createRecoveryPayload);
+        Map<String, Object> actualPayload = actual.toPayload();
+        List<?> checks = (List<?>) actualPayload.get("checks");
+        assertThat(actualPayload.get("status"), is("failed"));
+        assertThat(actualPayload.get("category"), is(expectedCategory));
+        assertThat(((Map<?, ?>) checks.get(1)).get("status"), 
is(expectedDriverStatus));
+        assertThat(((Map<?, ?>) checks.get(2)).get("status"), 
is(expectedConnectivityStatus));
+        assertThat(((Map<?, ?>) checks.get(3)).get("status"), is("skipped"));
+        assertThat(((Map<?, ?>) checks.get(4)).get("status"), is("skipped"));
+        verifyNoInteractions(metadataLoader);
+    }
+    
+    private static Stream<Arguments> 
assertValidateFailsDuringProfileLoadCases() {
+        return Stream.of(
+                Arguments.of("missing driver", 
RuntimeDatabaseConnectionException.missingJdbcDriver("logic_db", new 
ClassNotFoundException("missing")), "failed", "skipped",
+                        
RuntimeDatabaseConnectionException.CATEGORY_MISSING_JDBC_DRIVER),
+                Arguments.of("authentication failed", 
RuntimeDatabaseConnectionException.connectionFailed("logic_db", new 
SQLException("Access denied", "28000")), "passed", "failed",
+                        
RuntimeDatabaseConnectionException.CATEGORY_AUTHENTICATION_FAILED),
+                Arguments.of("authorization failed", 
RuntimeDatabaseConnectionException.connectionFailed("logic_db", new 
SQLException("permission denied", "42501")), "passed", "failed",
+                        
RuntimeDatabaseConnectionException.CATEGORY_AUTHORIZATION_FAILED),
+                Arguments.of("connection timeout", 
RuntimeDatabaseConnectionException.connectionFailed("logic_db", new 
SQLTimeoutException("timed out")), "passed", "failed",
+                        
RuntimeDatabaseConnectionException.CATEGORY_CONNECTION_TIMEOUT),
+                Arguments.of("database type mismatch", 
RuntimeDatabaseConnectionException.invalidConfiguration("logic_db", new 
IllegalStateException("mismatch")), "passed", "failed",
+                        
RuntimeDatabaseConnectionException.CATEGORY_INVALID_CONFIGURATION));
+    }
+    
+    private static RuntimeDatabaseProfile createProfile() {
+        return new RuntimeDatabaseProfile("logic_db", "MySQL", "8.0.36");
+    }
+    
+    private static RuntimeDatabaseConfiguration 
createRuntimeDatabaseConfiguration() {
+        return new RuntimeDatabaseConfiguration("MySQL", "jdbc:test:profile", 
"demo", "", "com.mysql.cj.jdbc.Driver");
+    }
+    
+    private static MCPDatabaseMetadata createMetadata(final String schemaName) 
{
+        return new MCPDatabaseMetadata("logic_db", "MySQL", "8.0.36", 
List.of(new MCPSchemaMetadata("logic_db", schemaName, List.of(), List.of(), 
List.of())));
+    }
+    
+    private static Map<String, Object> createRecoveryPayload(final 
RuntimeDatabaseConnectionException cause) {
+        return Map.of("category", cause.getCategory());
+    }
+    
+    private static final class InvisibleDatabaseDriver implements Driver {
+        
+        private static final String JDBC_URL = "jdbc:preflight:invisible";
+        
+        private static final InvisibleDatabaseDriver INSTANCE = new 
InvisibleDatabaseDriver();
+        
+        static {
+            try {
+                DriverManager.registerDriver(INSTANCE);
+            } catch (final SQLException ex) {
+                throw new ExceptionInInitializerError(ex);
+            }
+        }
+        
+        @Override
+        public Connection connect(final String url, final Properties info) 
throws SQLException {
+            if (!acceptsURL(url)) {
+                return null;
+            }
+            Connection result = mock(Connection.class);
+            DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
+            ResultSet catalogs = mock(ResultSet.class);
+            ResultSet schemas = mock(ResultSet.class);
+            when(result.getCatalog()).thenReturn("");
+            when(result.getSchema()).thenReturn("");
+            when(result.getMetaData()).thenReturn(databaseMetaData);
+            when(databaseMetaData.getCatalogs()).thenReturn(catalogs);
+            when(databaseMetaData.getSchemas()).thenReturn(schemas);
+            when(catalogs.next()).thenReturn(false);
+            when(schemas.next()).thenReturn(false);
+            return result;
+        }
+        
+        @Override
+        public boolean acceptsURL(final String url) {
+            return JDBC_URL.equals(url);
+        }
+        
+        @Override
+        public DriverPropertyInfo[] getPropertyInfo(final String url, final 
Properties info) {
+            return new DriverPropertyInfo[0];
+        }
+        
+        @Override
+        public int getMajorVersion() {
+            return 1;
+        }
+        
+        @Override
+        public int getMinorVersion() {
+            return 0;
+        }
+        
+        @Override
+        public boolean jdbcCompliant() {
+            return false;
+        }
+        
+        @Override
+        public Logger getParentLogger() throws SQLFeatureNotSupportedException 
{
+            throw new SQLFeatureNotSupportedException("Invisible database 
driver does not expose a parent logger.");
+        }
+    }
+}
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionMySQLRuntimeE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionMySQLRuntimeE2ETest.java
index 18269c66f46..498e927094d 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionMySQLRuntimeE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionMySQLRuntimeE2ETest.java
@@ -246,10 +246,12 @@ abstract class AbstractProductionMySQLRuntimeE2ETest 
extends AbstractTransportPa
         Map<String, Object> modelFirstSummary = 
getMap(capabilities.get("model_first_summary"));
         
assertThat(getMap(modelFirstSummary.get("official_discovery_methods")).get("tools"),
 is("tools/list"));
         assertThat(modelFirstSummary.get("optional_catalog_resource"), 
is("shardingsphere://capabilities"));
+        
assertThat(getMap(modelFirstSummary.get("preflight_rule")).get("tool"), 
is("database_gateway_validate_proxy_connectivity"));
         
assertThat(getMap(getMap(modelFirstSummary.get("sql_tool_selection")).get("read_only")).get("tool"),
 is("database_gateway_execute_query"));
         
assertThat(getMap(getMap(modelFirstSummary.get("workflow_rule")).get("preview_tool")).get("tool"),
 is("database_gateway_apply_workflow"));
         Map<String, Object> surfaceSummary = 
getMap(capabilities.get("surface_summary"));
         
assertThat(getMap(surfaceSummary.get("official_discovery_methods")).get("resources"),
 is("resources/list"));
+        assertThat(surfaceSummary.get("preflight_validation_tool"), 
is("database_gateway_validate_proxy_connectivity"));
         assertThat(surfaceSummary.get("read_only_sql_tool"), 
is("database_gateway_execute_query"));
         assertThat(surfaceSummary.get("side_effect_sql_tool"), 
is("database_gateway_execute_update"));
     }
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/PackagedDistributionE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/PackagedDistributionE2ETest.java
index 1464ccba44e..a0dae6017a6 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/PackagedDistributionE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/PackagedDistributionE2ETest.java
@@ -68,8 +68,8 @@ class PackagedDistributionE2ETest {
     private static final List<String> EXPECTED_RUNTIME_ARTIFACT_IDS = List.of(
             "shardingsphere-mcp-bootstrap", 
"shardingsphere-mcp-feature-encrypt", "shardingsphere-mcp-feature-mask");
     
-    private static final List<String> CORE_TOOL_NAMES = 
List.of("database_gateway_search_metadata", "database_gateway_execute_query", 
"database_gateway_execute_update",
-            "database_gateway_apply_workflow", 
"database_gateway_validate_workflow");
+    private static final List<String> CORE_TOOL_NAMES = 
List.of("database_gateway_search_metadata", 
"database_gateway_validate_proxy_connectivity",
+            "database_gateway_execute_query", 
"database_gateway_execute_update", "database_gateway_apply_workflow", 
"database_gateway_validate_workflow");
     
     private static final List<String> REMOVED_FEATURE_TOOL_NAMES = 
OfficialMCPToolNames.getAll().stream().filter(each -> 
!CORE_TOOL_NAMES.contains(each)).toList();
     
@@ -274,8 +274,8 @@ class PackagedDistributionE2ETest {
     
     private void assertDiscoveredTools(final List<Map<String, Object>> tools) {
         List<String> actualToolNames = tools.stream().map(each -> 
String.valueOf(each.get("name"))).toList();
-        assertThat(actualToolNames, 
hasItems("database_gateway_search_metadata", "database_gateway_execute_query", 
"database_gateway_execute_update",
-                "database_gateway_apply_workflow", 
"database_gateway_validate_workflow", "fixture_ping"));
+        assertThat(actualToolNames, 
hasItems("database_gateway_search_metadata", 
"database_gateway_validate_proxy_connectivity", 
"database_gateway_execute_query",
+                "database_gateway_execute_update", 
"database_gateway_apply_workflow", "database_gateway_validate_workflow", 
"fixture_ping"));
         for (String each : REMOVED_FEATURE_TOOL_NAMES) {
             assertFalse(actualToolNames.contains(each));
         }
@@ -300,8 +300,8 @@ class PackagedDistributionE2ETest {
     
     private void assertCapabilities(final Map<String, Object> payload) {
         List<String> actualSupportedTools = ((List<?>) 
payload.get("supportedTools")).stream().map(String::valueOf).toList();
-        assertThat(actualSupportedTools, 
hasItems("database_gateway_search_metadata", "database_gateway_execute_query", 
"database_gateway_execute_update",
-                "database_gateway_apply_workflow", 
"database_gateway_validate_workflow", "fixture_ping"));
+        assertThat(actualSupportedTools, 
hasItems("database_gateway_search_metadata", 
"database_gateway_validate_proxy_connectivity", 
"database_gateway_execute_query",
+                "database_gateway_execute_update", 
"database_gateway_apply_workflow", "database_gateway_validate_workflow", 
"fixture_ping"));
         for (String each : REMOVED_FEATURE_TOOL_NAMES) {
             assertFalse(actualSupportedTools.contains(each));
         }
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMySQLRuntimeE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMySQLRuntimeE2ETest.java
index 9b5d75911af..233b57f8a2d 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMySQLRuntimeE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMySQLRuntimeE2ETest.java
@@ -78,6 +78,7 @@ class ProductionMySQLRuntimeE2ETest extends 
AbstractProductionMySQLRuntimeE2ETes
             List<Map<String, Object>> actual = interactionClient.listTools();
             assertOfficialToolNames(actual.stream().map(each -> 
String.valueOf(each.get("name"))).toList());
             assertToolDefinition(actual, "database_gateway_search_metadata", 
"Search Metadata", "", "object_types", "array");
+            assertToolDefinition(actual, 
"database_gateway_validate_proxy_connectivity", "Validate Proxy Connectivity", 
"database", "database", "string");
             assertToolDefinition(actual, "database_gateway_execute_query", 
"Execute Query SQL", "sql", "timeout_ms", "integer");
             assertToolDefinition(actual, "database_gateway_execute_update", 
"Execute Update SQL", "sql", "timeout_ms", "integer");
         }
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportContractE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportContractE2ETest.java
index 0f1e93de2c1..341197afbb2 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportContractE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportContractE2ETest.java
@@ -44,7 +44,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 class HttpTransportContractE2ETest extends 
AbstractHttpProgrammaticRuntimeE2ETest {
     
     private static final List<String> OFFICIAL_TOOL_NAMES = List.of(
-            "database_gateway_search_metadata", 
"database_gateway_execute_query", "database_gateway_execute_update", 
"database_gateway_apply_workflow",
+            "database_gateway_search_metadata", 
"database_gateway_validate_proxy_connectivity", 
"database_gateway_execute_query", "database_gateway_execute_update", 
"database_gateway_apply_workflow",
             "database_gateway_validate_workflow", 
"database_gateway_plan_encrypt_rule", "database_gateway_plan_mask_rule");
     
     private static final String PLAN_MASK_TOOL_NAME = 
"database_gateway_plan_mask_rule";
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/OfficialMCPToolNames.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/OfficialMCPToolNames.java
index 5bef92123fc..66c91ffb1da 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/OfficialMCPToolNames.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/OfficialMCPToolNames.java
@@ -26,6 +26,7 @@ public final class OfficialMCPToolNames {
     
     private static final List<String> ALL = List.of(
             "database_gateway_search_metadata",
+            "database_gateway_validate_proxy_connectivity",
             "database_gateway_execute_query",
             "database_gateway_execute_update",
             "database_gateway_apply_workflow",
diff --git 
a/test/e2e/mcp/src/test/resources/baseline-contract/model-contract/capabilities.yaml
 
b/test/e2e/mcp/src/test/resources/baseline-contract/model-contract/capabilities.yaml
index cfac7e92c06..86c42f20d43 100644
--- 
a/test/e2e/mcp/src/test/resources/baseline-contract/model-contract/capabilities.yaml
+++ 
b/test/e2e/mcp/src/test/resources/baseline-contract/model-contract/capabilities.yaml
@@ -27,6 +27,9 @@ model_first_summary:
   metadata_rule: {first_resource: 'shardingsphere://databases', search_tool: 
database_gateway_search_metadata,
     detail_rule: Read the returned resource.uri when the list or search 
response points
       to a detail resource.}
+  preflight_rule: {tool: database_gateway_validate_proxy_connectivity, 
input_rule: Pass
+      only a configured runtime database name., secret_rule: Connection 
details stay
+      in administrator runtime configuration and are not tool arguments.}
   sql_tool_selection:
     read_only: {tool: database_gateway_execute_query, statement_rule: Use for 
one
         SELECT or EXPLAIN ANALYZE statement.}
@@ -51,6 +54,8 @@ model_contract:
   argument_completion_method: completion/complete
   optional_catalog_resource: shardingsphere://capabilities
   metadata_first_resource: shardingsphere://databases
+  preflight_rule: Use database_gateway_validate_proxy_connectivity with a 
configured
+    database name before onboarding or troubleshooting runtime connectivity.
   sql_tool_selection: {side_effecting: Use database_gateway_execute_update 
with execution_mode=preview
       before execution., read_only: Use database_gateway_execute_query for one 
classifier-approved
       SELECT or EXPLAIN ANALYZE statement.}
@@ -71,6 +76,7 @@ surface_summary:
   optional_catalog_resource: shardingsphere://capabilities
   metadata_resource: shardingsphere://databases
   metadata_search_tool: database_gateway_search_metadata
+  preflight_validation_tool: database_gateway_validate_proxy_connectivity
   read_only_sql_tool: database_gateway_execute_query
   side_effect_sql_tool: database_gateway_execute_update
   workflow_tools: [database_gateway_apply_workflow, 
database_gateway_validate_workflow]
@@ -121,6 +127,12 @@ common_flows:
   stop_condition: Stop when the requested metadata detail resource is read.
   referenced_tools: [database_gateway_search_metadata]
   referenced_resources: ['shardingsphere://databases']
+- flow_id: validate_runtime_database
+  steps: [read_resource shardingsphere://databases, call_tool 
database_gateway_validate_proxy_connectivity]
+  stop_condition: Stop after the configured runtime database reports ready or 
returns
+    structured recovery guidance.
+  referenced_tools: [database_gateway_validate_proxy_connectivity]
+  referenced_resources: ['shardingsphere://databases']
 - flow_id: read_only_sql
   steps: ['read_resource shardingsphere://databases/{database}/capabilities', 
call_tool
       database_gateway_execute_query]
@@ -152,9 +164,9 @@ common_flows:
   referenced_resources: ['shardingsphere://capabilities']
 protocolAvailability: {resources: true, resourceTemplates: true, tools: true, 
toolAnnotations: true,
   toolOutputSchemas: true, prompts: true, completions: true, 
resourceNavigation: true}
-fingerprints: {algorithm: sha256, descriptorCatalog: 
f4ad81145c1af6bb9223e71ee783865c29edbf9b25d9ecc6999a1934fbc929e8,
+fingerprints: {algorithm: sha256, descriptorCatalog: 
48d9d27f8c40295dcd4f44fbaeaca59d963e90848bbc1f20f66fc150a358a096,
   promptSet: 3ce46d5c41e261470f1846680fab5274b38b35457b7d0a92d0dd0659ec8b5c17, 
resourceNavigation: 
64abf531e9c72549a99ab5f23ee2e0f6d769e45e19b56c73f86144e8f2e4799f,
-  modelFacingSchemas: 
9acdd7ed62c0ad8f3f184debf0b4f33868a8af3b677ea4212cf785a74c1ded8b}
+  modelFacingSchemas: 
a782fa53deb816c51ce3e2a4a3b27c1a04b0f651454953594767d606ec34060f}
 resources:
 - uri: shardingsphere://capabilities
   name: server-capability-catalog
@@ -353,6 +365,9 @@ tools:
 - name: database_gateway_search_metadata
   annotations: {title: Search Metadata, readOnlyHint: true, destructiveHint: 
false,
     idempotentHint: true, openWorldHint: true}
+- name: database_gateway_validate_proxy_connectivity
+  annotations: {title: Validate Proxy Connectivity, readOnlyHint: true, 
destructiveHint: false,
+    idempotentHint: true, openWorldHint: true}
 - name: database_gateway_execute_query
   annotations: {title: Execute Query SQL, readOnlyHint: true, destructiveHint: 
false,
     idempotentHint: true, openWorldHint: true}

Reply via email to