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 c39dc442faa Refine MCP distribution defaults and registry metadata 
(#39024)
c39dc442faa is described below

commit c39dc442faa38aeae92f38886055859d6fbb6b26
Author: Liang Zhang <[email protected]>
AuthorDate: Tue Jul 7 11:46:27 2026 +0800

    Refine MCP distribution defaults and registry metadata (#39024)
    
    - Remove placeholder runtime database entries from MCP default configs
    - Add Docker-specific HTTP config for container startup
    - Allow empty runtimeDatabases while rejecting unresolved YAML placeholders
    - Add streamable-http Docker runtime metadata validation
    - Trigger MCP E2E workflow for distribution/mcp changes
    - Update MCP docs for empty startup config and Docker defaults
---
 .github/workflows/e2e-mcp.yml                      |  1 +
 distribution/mcp/src/main/bin/docker-entrypoint.sh |  2 +-
 .../conf/{mcp-stdio.yaml => mcp-http-docker.yaml}  | 18 ++++---
 .../mcp/src/main/resources/conf/mcp-http.yaml      | 10 +---
 .../mcp/src/main/resources/conf/mcp-stdio.yaml     | 10 +---
 .../user-manual/shardingsphere-mcp/_index.cn.md    |  2 +-
 .../user-manual/shardingsphere-mcp/_index.en.md    |  2 +-
 .../shardingsphere-mcp/configuration.cn.md         |  5 +-
 .../shardingsphere-mcp/configuration.en.md         |  5 +-
 .../shardingsphere-mcp/deployment.cn.md            |  3 +-
 .../shardingsphere-mcp/deployment.en.md            |  3 +-
 .../shardingsphere-mcp/troubleshooting.cn.md       |  4 +-
 .../shardingsphere-mcp/troubleshooting.en.md       |  4 +-
 .../yaml/config/YamlMCPLaunchConfiguration.java    |  3 +-
 ...YamlRuntimeDatabaseConfigurationsValidator.java | 42 +++++++++++++++-
 .../config/MCPLaunchConfigurationTest.java         |  3 +-
 .../YamlMCPLaunchConfigurationSwapperTest.java     | 56 ++++++++++++++++++++--
 .../mcp/registry/MCPRegistryMetadataCommand.java   | 16 +++++++
 .../registry/MCPRegistryMetadataCommandTest.java   | 35 +++++++++++++-
 mcp/server.json                                    |  8 ++++
 20 files changed, 182 insertions(+), 50 deletions(-)

diff --git a/.github/workflows/e2e-mcp.yml b/.github/workflows/e2e-mcp.yml
index 77b8bd1b594..363a3b9eebd 100644
--- a/.github/workflows/e2e-mcp.yml
+++ b/.github/workflows/e2e-mcp.yml
@@ -28,6 +28,7 @@ on:
     paths:
       - '.github/workflows/e2e-mcp.yml'
       - 'pom.xml'
+      - 'distribution/mcp/**'
       - 'mcp/**'
       - 'test/e2e/pom.xml'
       - 'test/e2e/mcp/**'
diff --git a/distribution/mcp/src/main/bin/docker-entrypoint.sh 
b/distribution/mcp/src/main/bin/docker-entrypoint.sh
index ccb625e41d3..af9c66df2f0 100644
--- a/distribution/mcp/src/main/bin/docker-entrypoint.sh
+++ b/distribution/mcp/src/main/bin/docker-entrypoint.sh
@@ -31,7 +31,7 @@ fi
 
 case "${SHARDINGSPHERE_MCP_TRANSPORT:-http}" in
   http)
-    exec "${START_SCRIPT}" "${APP_HOME}/conf/mcp-http.yaml"
+    exec "${START_SCRIPT}" "${APP_HOME}/conf/mcp-http-docker.yaml"
     ;;
   stdio)
     exec "${START_SCRIPT}" "${APP_HOME}/conf/mcp-stdio.yaml"
diff --git a/distribution/mcp/src/main/resources/conf/mcp-stdio.yaml 
b/distribution/mcp/src/main/resources/conf/mcp-http-docker.yaml
similarity index 64%
copy from distribution/mcp/src/main/resources/conf/mcp-stdio.yaml
copy to distribution/mcp/src/main/resources/conf/mcp-http-docker.yaml
index d727b7c242b..122918a5d6d 100644
--- a/distribution/mcp/src/main/resources/conf/mcp-stdio.yaml
+++ b/distribution/mcp/src/main/resources/conf/mcp-http-docker.yaml
@@ -1,3 +1,4 @@
+#
 # 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.
@@ -12,16 +13,13 @@
 # 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.
+#
 
 transport:
-  type: STDIO
+  type: STREAMABLE_HTTP
+  http:
+    bindHost: 0.0.0.0
+    port: 18088
+    endpointPath: /mcp
 
-runtimeDatabases:
-  # Configure this entry before startup.
-  # Replace the placeholders with your ShardingSphere-Proxy logical database.
-  # Omit password or set it to an empty string when the Proxy account has no 
password.
-  "<logic-database>":
-    jdbcUrl: "jdbc:mysql://<proxy-host>:<proxy-port>/<logic-database>"
-    username: "<proxy-username>"
-    password: "<proxy-password>"
-    driverClassName: "com.mysql.cj.jdbc.Driver"
+runtimeDatabases: {}
diff --git a/distribution/mcp/src/main/resources/conf/mcp-http.yaml 
b/distribution/mcp/src/main/resources/conf/mcp-http.yaml
index 68c16471e92..35dcb45485c 100644
--- a/distribution/mcp/src/main/resources/conf/mcp-http.yaml
+++ b/distribution/mcp/src/main/resources/conf/mcp-http.yaml
@@ -21,12 +21,4 @@ transport:
     port: 18088
     endpointPath: /mcp
 
-runtimeDatabases:
-  # Configure this entry before startup.
-  # Replace the placeholders with your ShardingSphere-Proxy logical database.
-  # Omit password or set it to an empty string when the Proxy account has no 
password.
-  "<logic-database>":
-    jdbcUrl: "jdbc:mysql://<proxy-host>:<proxy-port>/<logic-database>"
-    username: "<proxy-username>"
-    password: "<proxy-password>"
-    driverClassName: "com.mysql.cj.jdbc.Driver"
+runtimeDatabases: {}
diff --git a/distribution/mcp/src/main/resources/conf/mcp-stdio.yaml 
b/distribution/mcp/src/main/resources/conf/mcp-stdio.yaml
index d727b7c242b..ee31ea30f44 100644
--- a/distribution/mcp/src/main/resources/conf/mcp-stdio.yaml
+++ b/distribution/mcp/src/main/resources/conf/mcp-stdio.yaml
@@ -16,12 +16,4 @@
 transport:
   type: STDIO
 
-runtimeDatabases:
-  # Configure this entry before startup.
-  # Replace the placeholders with your ShardingSphere-Proxy logical database.
-  # Omit password or set it to an empty string when the Proxy account has no 
password.
-  "<logic-database>":
-    jdbcUrl: "jdbc:mysql://<proxy-host>:<proxy-port>/<logic-database>"
-    username: "<proxy-username>"
-    password: "<proxy-password>"
-    driverClassName: "com.mysql.cj.jdbc.Driver"
+runtimeDatabases: {}
diff --git a/docs/document/content/user-manual/shardingsphere-mcp/_index.cn.md 
b/docs/document/content/user-manual/shardingsphere-mcp/_index.cn.md
index 17708cbdbae..86e3ce4951b 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/_index.cn.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/_index.cn.md
@@ -11,7 +11,7 @@ MCP 是连接 AI 应用与外部数据源和工具的开放协议,协议说明
 AI 应用开发者可以将 ShardingSphere-MCP 作为受控数据库访问能力接入应用。
 接入后,用户可以通过自然语言查看数据库结构、执行受控查询,并规划需要审查的 ShardingSphere 规则变更。
 
-使用前需要准备可连接的数据库,并在 `runtimeDatabases` 中配置连接信息;如果需要使用数据加密、数据脱敏等规则变更能力,连接目标应为 
ShardingSphere-Proxy 逻辑库。
+使用依赖数据库的能力前,需要准备可连接的数据库,并在 `runtimeDatabases` 
中配置连接信息;如果需要使用数据加密、数据脱敏等规则变更能力,连接目标应为 ShardingSphere-Proxy 逻辑库。
 
 ## 面向 AI 应用的数据库访问
 
diff --git a/docs/document/content/user-manual/shardingsphere-mcp/_index.en.md 
b/docs/document/content/user-manual/shardingsphere-mcp/_index.en.md
index 736b8374e4e..1c8a11738ee 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/_index.en.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/_index.en.md
@@ -11,7 +11,7 @@ MCP is an open protocol for connecting AI applications to 
external data sources
 AI application developers can integrate ShardingSphere-MCP as a controlled 
database access capability.
 After integration, users can inspect database structure, run controlled 
queries, and plan reviewable ShardingSphere rule changes through natural 
language.
 
-Before use, prepare a reachable database and configure its connection 
information in `runtimeDatabases`. If data encryption, data masking, or other 
rule change capabilities are required, the connection target should be a 
ShardingSphere-Proxy logical database.
+Before using database-backed capabilities, prepare a reachable database and 
configure its connection information in `runtimeDatabases`. If data encryption, 
data masking, or other rule change capabilities are required, the connection 
target should be a ShardingSphere-Proxy logical database.
 
 ## Database Access for AI Applications
 
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 cf600c698c4..e549d1aac53 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/configuration.cn.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/configuration.cn.md
@@ -4,7 +4,7 @@ weight = 3
 +++
 
 ShardingSphere-MCP 使用 YAML 文件配置传输方式和 MCP Server 可以连接的数据库。
-发行包默认读取 `conf/mcp-http.yaml`,也内置 `conf/mcp-stdio.yaml`。
+发行包默认读取 `conf/mcp-http.yaml`,也内置 `conf/mcp-stdio.yaml` 和 
`conf/mcp-http-docker.yaml`。
 
 ## 传输方式
 
@@ -62,7 +62,7 @@ transport:
 
 ## 数据库配置
 
-`runtimeDatabases` 定义 MCP Server 可以连接并对外暴露的数据库。
+`runtimeDatabases` 定义 MCP Server 可以连接并对外暴露的数据库。Server 启动时可以省略或为空,此时不提供依赖数据库的能力。
 每个条目的 key 是用户在自然语言任务中引用的数据库名称,通常对应 ShardingSphere-Proxy 暴露的逻辑库。
 MCP Server 会从 `jdbcUrl` 解析数据库类型;请使用与该 JDBC URL 匹配的驱动类。
 
@@ -93,6 +93,7 @@ runtimeDatabases:
 - 数据库直连时,用户看到的是目标数据库自身的元数据,不代表 ShardingSphere 规则状态。
 - 模式、表、视图、索引和序列等元数据依赖连接目标的 JDBC 元数据;Proxy 和数据库直连的可见结果可能不同。
 - 如果目标 JDBC 驱动没有随发行包提供,请把驱动 jar 放入 `plugins/`。
+- `<logic-database>` 等占位值只能用于示例。运行时 YAML 文件会拒绝未替换的占位符。
 
 ## 敏感值占位符
 
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 0f079aea05e..71b7ba4d1f1 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/configuration.en.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/configuration.en.md
@@ -4,7 +4,7 @@ weight = 3
 +++
 
 ShardingSphere-MCP uses YAML files to configure the transport and the 
databases that the MCP Server can connect to.
-The packaged distribution reads `conf/mcp-http.yaml` by default and also ships 
`conf/mcp-stdio.yaml`.
+The packaged distribution reads `conf/mcp-http.yaml` by default and also ships 
`conf/mcp-stdio.yaml` and `conf/mcp-http-docker.yaml`.
 
 ## Transport configuration
 
@@ -62,7 +62,7 @@ Enable this only when clients cannot forge these headers 
directly.
 
 ## Database configuration
 
-`runtimeDatabases` defines the databases that the MCP Server can connect to 
and expose to users.
+`runtimeDatabases` defines the databases that the MCP Server can connect to 
and expose to users. It may be omitted or empty when the server starts without 
database-backed capabilities.
 Each entry key is the database name that users reference in natural-language 
tasks. It usually maps to a logical database exposed by ShardingSphere-Proxy.
 The MCP Server resolves the database type from `jdbcUrl`; use a JDBC driver 
class that matches the configured URL.
 
@@ -93,6 +93,7 @@ Notes:
 - With a direct database connection, users see metadata from the target 
database itself, not ShardingSphere rule state.
 - Schema, table, view, index, and sequence metadata depends on JDBC metadata 
from the connection target. Proxy-visible metadata and direct-connection 
metadata may differ.
 - If the target JDBC driver is not packaged, copy the driver jar under 
`plugins/`.
+- Keep placeholder values such as `<logic-database>` in examples only. Runtime 
YAML files reject unresolved placeholders.
 
 ## Secret Placeholders
 
diff --git 
a/docs/document/content/user-manual/shardingsphere-mcp/deployment.cn.md 
b/docs/document/content/user-manual/shardingsphere-mcp/deployment.cn.md
index 33b08c6d095..ec7292a9f6b 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/deployment.cn.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/deployment.cn.md
@@ -31,7 +31,8 @@ OCI image 形态是:
 ghcr.io/apache/shardingsphere-mcp:<version>
 ```
 
-使用 OCI 镜像前,应先准备自定义配置文件。
+OCI 镜像可以使用内置 `conf/mcp-http-docker.yaml` 配置和空的 `runtimeDatabases` 启动。
+使用元数据、SQL 或规则能力前,请先准备自定义配置文件。
 HTTP 模式在容器中运行时,`bindHost` 应绑定到容器可暴露的网络接口,例如 `0.0.0.0`:
 
 ```yaml
diff --git 
a/docs/document/content/user-manual/shardingsphere-mcp/deployment.en.md 
b/docs/document/content/user-manual/shardingsphere-mcp/deployment.en.md
index f8ca8d11dc2..7fef7281534 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/deployment.en.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/deployment.en.md
@@ -31,7 +31,8 @@ The OCI image shape is:
 ghcr.io/apache/shardingsphere-mcp:<version>
 ```
 
-Before using the OCI image, prepare a custom configuration file.
+The OCI image can start with its built-in `conf/mcp-http-docker.yaml` 
configuration and an empty `runtimeDatabases` map.
+Before using metadata, SQL, or rule capabilities, prepare a custom 
configuration file.
 When HTTP mode runs in a container, `bindHost` should bind to a network 
interface that the container can expose, such as `0.0.0.0`:
 
 ```yaml
diff --git 
a/docs/document/content/user-manual/shardingsphere-mcp/troubleshooting.cn.md 
b/docs/document/content/user-manual/shardingsphere-mcp/troubleshooting.cn.md
index e2e86db7a27..0d27d091432 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/troubleshooting.cn.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/troubleshooting.cn.md
@@ -12,7 +12,7 @@ weight = 7
 
 | 现象                           | 可能原因                                          
                    | 处理方式                                                      
                                           |
 
|------------------------------|-------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
-| MCP Server 启动失败              | Java 版本、配置文件路径、发行包目录或 YAML 必填字段不正确。           
                    | 查看启动终端和 `logs/mcp.log`;确认使用 Java 21 及以上版本,配置文件存在,发行包 
`lib/` 目录完整,并且 `runtimeDatabases` 至少配置一项运行时数据库。 |
+| MCP Server 启动失败              | Java 版本、配置文件路径、发行包目录或 YAML 必填字段不正确。           
                    | 查看启动终端和 `logs/mcp.log`;确认使用 Java 21 及以上版本,配置文件存在,并且发行包 
`lib/` 目录完整。 |
 | AI 应用无法连接 ShardingSphere-MCP | 传输方式、端口、端点路径、绑定地址或 AI 应用中的 MCP Server 配置不一致。  
                    | 检查 `transport.type`、`port`、`endpointPath`、`bindHost`,并确认 
AI 应用使用相同的连接地址。                             |
 | HTTP 远程访问失败或请求被拒绝            | HTTP 绑定地址、Origin 请求头、反向代理、网关转发或会话归属配置不符合安全策略。 
                    | 本机调试使用回环地址;远程访问放在受控网关或反向代理后面;检查网关是否转发了期望的请求头;详细原因看服务端日志。  
                                           |
 | STDIO 模式没有响应                 | STDIO 被当作命令行交互入口,或 AI 应用没有正确拉起 MCP 进程。        
                    | 由 AI 应用拉起 ShardingSphere-MCP;诊断信息看 stderr 或 
`logs/mcp.log`。                                          |
@@ -26,7 +26,7 @@ weight = 7
 补充说明:
 
 - `username` 和 `driverClassName` 必须显式写出且不能为空;无密码账号可以省略 `password` 或写 `""`。
-- `runtimeDatabases` 是启动必填项,至少需要配置一项运行时数据库。
+- YAML 中可以省略 `runtimeDatabases` 或将其配置为空;在元数据发现、SQL 执行或规则工作流前,至少需要配置一项运行时数据库。
 - 查询默认最多返回 100 行,单次查询最多可请求 5000 行;查询超时最大可请求 300000 毫秒。
 - 人工执行包中的密钥占位符应由执行人员在受控环境替换。
 - ShardingSphere-MCP 不获取真实敏感值,真实敏感值也不应进入 AI 应用;如果规则变更需要密钥或凭证,应在 MCP 和 AI 
应用之外的受控环境中替换人工执行包里的中性占位符。
diff --git 
a/docs/document/content/user-manual/shardingsphere-mcp/troubleshooting.en.md 
b/docs/document/content/user-manual/shardingsphere-mcp/troubleshooting.en.md
index 87668e2d9a2..c38a84867ea 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/troubleshooting.en.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/troubleshooting.en.md
@@ -12,7 +12,7 @@ If you have not completed the basic post-deployment checks 
yet, start with the h
 
 | Symptom                                                  | Possible cause    
                                                                                
                                                                                
                                         | Action                               
                                                                                
                                                                                
              [...]
 
|----------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 [...]
-| MCP Server startup failure                               | Java version, 
config file path, distribution directory, or required YAML field is wrong.      
                                                                                
                                             | Inspect the startup terminal and 
`logs/mcp.log`; use Java 21 or later, confirm that the config file exists, 
ensure that the distribution `lib/` directory is complete, and configure at 
least one runtime database [...]
+| MCP Server startup failure                               | Java version, 
config file path, distribution directory, or required YAML field is wrong.      
                                                                                
                                             | Inspect the startup terminal and 
`logs/mcp.log`; use Java 21 or later, confirm that the config file exists, and 
ensure that the distribution `lib/` directory is complete.                      
                   [...]
 | The AI application cannot connect to ShardingSphere-MCP  | Transport type, 
port, endpoint path, bind address, or the MCP Server configuration in the AI 
application is inconsistent.                                                    
                                              | Check `transport.type`, `port`, 
`endpointPath`, `bindHost`, and the address configured in the AI application.   
                                                                                
                   [...]
 | Remote HTTP access fails or the HTTP request is rejected | HTTP bind 
address, Origin header, reverse proxy, gateway forwarding, or session 
attribution configuration does not satisfy the security policy.                 
                                                           | Use loopback for 
local debugging; place remote access behind a controlled gateway or reverse 
proxy; check whether the gateway forwards the expected headers; inspect server 
logs for details.                      [...]
 | No response in STDIO mode                                | STDIO is used as 
an interactive command-line entry, or the AI application does not launch the 
MCP process correctly.                                                          
                                             | Let the AI application launch 
ShardingSphere-MCP; inspect stderr or `logs/mcp.log` for diagnostics.           
                                                                                
                     [...]
@@ -26,7 +26,7 @@ If you have not completed the basic post-deployment checks 
yet, start with the h
 Additional notes:
 
 - `username` and `driverClassName` must be declared explicitly and cannot be 
empty; a no-password account can omit `password` or use `""`.
-- `runtimeDatabases` is required at startup and must contain at least one 
runtime database.
+- `runtimeDatabases` may be omitted or empty. Configure at least one runtime 
database before metadata discovery, SQL execution, or rule workflows.
 - Queries return at most 100 rows by default. A single query can request at 
most 5000 rows, and the maximum requested query timeout is 300000 milliseconds.
 - Secret placeholders in manual packages should be replaced by operators in a 
controlled environment.
 - ShardingSphere-MCP does not fetch real sensitive values; real sensitive 
values must stay outside the AI application. If a rule change needs keys or 
credentials, replace the neutral placeholders in the manual execution package 
outside MCP and the AI application in a controlled environment.
diff --git 
a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/config/YamlMCPLaunchConfiguration.java
 
b/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/config/YamlMCPLaunchConfiguration.java
index c2d458486b7..d31a522d2c6 100644
--- 
a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/config/YamlMCPLaunchConfiguration.java
+++ 
b/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/config/YamlMCPLaunchConfiguration.java
@@ -24,7 +24,6 @@ import 
org.apache.shardingsphere.mcp.bootstrap.config.yaml.validator.ValidMCPLau
 import 
org.apache.shardingsphere.mcp.bootstrap.config.yaml.validator.ValidYamlRuntimeDatabaseConfigurations;
 
 import javax.validation.Valid;
-import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 import java.util.Map;
 
@@ -40,7 +39,7 @@ public final class YamlMCPLaunchConfiguration implements 
YamlConfiguration {
     @Valid
     private YamlMCPTransportConfiguration transport;
     
-    @NotEmpty(message = "is required")
+    @NotNull(message = "is required")
     @ValidYamlRuntimeDatabaseConfigurations
     private Map<String, Map<String, Object>> runtimeDatabases;
 }
diff --git 
a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/validator/YamlRuntimeDatabaseConfigurationsValidator.java
 
b/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/validator/YamlRuntimeDatabaseConfigurationsValidator.java
index 7adac305f1e..e699e3ae613 100644
--- 
a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/validator/YamlRuntimeDatabaseConfigurationsValidator.java
+++ 
b/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/validator/YamlRuntimeDatabaseConfigurationsValidator.java
@@ -42,11 +42,15 @@ public final class 
YamlRuntimeDatabaseConfigurationsValidator implements Constra
     }
     
     private boolean validateRuntimeDatabase(final Entry<String, Map<String, 
Object>> databaseEntry, final ConstraintValidatorContext context) {
+        if (isPlaceholder(databaseEntry.getKey())) {
+            addViolation(context, String.format("contains placeholder database 
name `%s`", databaseEntry.getKey()));
+            return false;
+        }
         if (null == databaseEntry.getValue()) {
             addViolation(context, String.format("contains null configuration 
for database `%s`", databaseEntry.getKey()));
             return false;
         }
-        return validateSupportedProperties(databaseEntry, context) && 
validateRequiredProperties(databaseEntry, context);
+        return validateSupportedProperties(databaseEntry, context) && 
validateRequiredProperties(databaseEntry, context) && 
validateNoPlaceholders(databaseEntry, context);
     }
     
     private boolean validateSupportedProperties(final Entry<String, 
Map<String, Object>> databaseEntry, final ConstraintValidatorContext context) {
@@ -75,6 +79,42 @@ public final class 
YamlRuntimeDatabaseConfigurationsValidator implements Constra
         return false;
     }
     
+    private boolean validateNoPlaceholders(final Entry<String, Map<String, 
Object>> databaseEntry, final ConstraintValidatorContext context) {
+        boolean result = validateNoPlaceholder(databaseEntry, 
YamlRuntimeDatabaseConfigurationProperties.JDBC_URL, context);
+        result = validateNoPlaceholder(databaseEntry, 
YamlRuntimeDatabaseConfigurationProperties.USERNAME, context) && result;
+        result = validateNoPlaceholder(databaseEntry, 
YamlRuntimeDatabaseConfigurationProperties.DRIVER_CLASS_NAME, context) && 
result;
+        return validateNoPasswordPlaceholder(databaseEntry, context) && result;
+    }
+    
+    private boolean validateNoPlaceholder(final Entry<String, Map<String, 
Object>> databaseEntry, final String key, final ConstraintValidatorContext 
context) {
+        Object value = databaseEntry.getValue().get(key);
+        if (null == value || !isPlaceholder(value.toString())) {
+            return true;
+        }
+        addViolation(context, String.format("contains placeholder database 
`%s` property `%s`", databaseEntry.getKey(), key));
+        return false;
+    }
+    
+    private boolean validateNoPasswordPlaceholder(final Entry<String, 
Map<String, Object>> databaseEntry, final ConstraintValidatorContext context) {
+        Object value = 
databaseEntry.getValue().get(YamlRuntimeDatabaseConfigurationProperties.PASSWORD);
+        if (null == value || !isStandalonePlaceholder(value.toString())) {
+            return true;
+        }
+        addViolation(context, String.format("contains placeholder database 
`%s` property `%s`", databaseEntry.getKey(), 
YamlRuntimeDatabaseConfigurationProperties.PASSWORD));
+        return false;
+    }
+    
+    private boolean isPlaceholder(final String value) {
+        String actualValue = value.trim();
+        int startIndex = actualValue.indexOf('<');
+        return startIndex >= 0 && actualValue.indexOf('>', startIndex + 1) > 
startIndex + 1;
+    }
+    
+    private boolean isStandalonePlaceholder(final String value) {
+        String actualValue = value.trim();
+        return actualValue.startsWith("<") && actualValue.endsWith(">") && 
actualValue.length() > 2;
+    }
+    
     private void addViolation(final ConstraintValidatorContext context, final 
String message) {
         context.disableDefaultConstraintViolation();
         
context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
diff --git 
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/config/MCPLaunchConfigurationTest.java
 
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/config/MCPLaunchConfigurationTest.java
index bedee3af09f..2a9ae5269a9 100644
--- 
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/config/MCPLaunchConfigurationTest.java
+++ 
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/config/MCPLaunchConfigurationTest.java
@@ -74,8 +74,7 @@ class MCPLaunchConfigurationTest {
     void assertValidateWhenDatabasesEmpty() {
         YamlMCPLaunchConfiguration yamlConfig = 
createYamlConfig(MCPTransportType.STREAMABLE_HTTP, null);
         yamlConfig.setRuntimeDatabases(Collections.emptyMap());
-        IllegalArgumentException actual = 
assertThrows(IllegalArgumentException.class, () -> validate(yamlConfig));
-        assertThat(actual.getMessage(), is("MCP launch configuration property 
`runtimeDatabases` is required."));
+        assertDoesNotThrow(() -> validate(yamlConfig));
     }
     
     @Test
diff --git 
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/swapper/YamlMCPLaunchConfigurationSwapperTest.java
 
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/swapper/YamlMCPLaunchConfigurationSwapperTest.java
index 87567329aa2..98d240b6d8c 100644
--- 
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/swapper/YamlMCPLaunchConfigurationSwapperTest.java
+++ 
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/swapper/YamlMCPLaunchConfigurationSwapperTest.java
@@ -113,10 +113,10 @@ class YamlMCPLaunchConfigurationSwapperTest {
     
     @Test
     void assertSwapToObjectWithoutRuntimeDatabasesSection() {
-        IllegalArgumentException actual = 
assertThrows(IllegalArgumentException.class, () -> 
swapper.swapToObject(YamlEngine.unmarshal(
+        MCPLaunchConfiguration actual = 
swapper.swapToObject(YamlEngine.unmarshal(
                 "transport:\n" + "  type: STDIO\n",
-                YamlMCPLaunchConfiguration.class)));
-        assertThat(actual.getMessage(), is("MCP launch configuration property 
`runtimeDatabases` is required."));
+                YamlMCPLaunchConfiguration.class));
+        assertThat(actual.getDatabases(), is(Collections.emptyMap()));
     }
     
     @Test
@@ -129,6 +129,14 @@ class YamlMCPLaunchConfigurationSwapperTest {
         assertThat(actual.getMessage(), is("MCP launch configuration property 
`runtimeDatabases` is required."));
     }
     
+    @Test
+    void assertSwapToObjectWithEmptyRuntimeDatabases() {
+        MCPLaunchConfiguration actual = 
swapper.swapToObject(YamlEngine.unmarshal(
+                "transport:\n" + "  type: STDIO\n" + "runtimeDatabases: {}\n",
+                YamlMCPLaunchConfiguration.class));
+        assertThat(actual.getDatabases(), is(Collections.emptyMap()));
+    }
+    
     @Test
     void assertSwapToObjectWithHttpDefaults() {
         MCPLaunchConfiguration actual = 
swapper.swapToObject(YamlEngine.unmarshal("transport:\n"
@@ -223,6 +231,48 @@ class YamlMCPLaunchConfigurationSwapperTest {
         assertThat(actual.getMessage(), is("MCP launch configuration property 
`runtimeDatabases` contains unsupported property `unsupported` for database 
`logic_db`."));
     }
     
+    @Test
+    void assertSwapToObjectWithPlaceholderRuntimeDatabaseName() {
+        String yamlContent = "transport:\n"
+                + "  type: STDIO\n"
+                + "runtimeDatabases:\n"
+                + "  <logic-database>:\n"
+                + "    jdbcUrl: jdbc:mysql://localhost:3306/logic_db\n"
+                + "    username: demo\n"
+                + "    password: ''\n"
+                + "    driverClassName: com.mysql.cj.jdbc.Driver\n";
+        IllegalArgumentException actual = 
assertThrows(IllegalArgumentException.class, () -> 
swapper.swapToObject(YamlEngine.unmarshal(yamlContent, 
YamlMCPLaunchConfiguration.class)));
+        assertThat(actual.getMessage(), is("MCP launch configuration property 
`runtimeDatabases` contains placeholder database name `<logic-database>`."));
+    }
+    
+    @Test
+    void assertSwapToObjectWithPlaceholderJdbcUrl() {
+        String yamlContent = "transport:\n"
+                + "  type: STDIO\n"
+                + "runtimeDatabases:\n"
+                + "  logic_db:\n"
+                + "    jdbcUrl: jdbc:mysql://<proxy-host>:3306/logic_db\n"
+                + "    username: demo\n"
+                + "    password: ''\n"
+                + "    driverClassName: com.mysql.cj.jdbc.Driver\n";
+        IllegalArgumentException actual = 
assertThrows(IllegalArgumentException.class, () -> 
swapper.swapToObject(YamlEngine.unmarshal(yamlContent, 
YamlMCPLaunchConfiguration.class)));
+        assertThat(actual.getMessage(), is("MCP launch configuration property 
`runtimeDatabases` contains placeholder database `logic_db` property 
`jdbcUrl`."));
+    }
+    
+    @Test
+    void assertSwapToObjectWithPlaceholderPassword() {
+        String yamlContent = "transport:\n"
+                + "  type: STDIO\n"
+                + "runtimeDatabases:\n"
+                + "  logic_db:\n"
+                + "    jdbcUrl: jdbc:mysql://localhost:3306/logic_db\n"
+                + "    username: demo\n"
+                + "    password: <proxy-password>\n"
+                + "    driverClassName: com.mysql.cj.jdbc.Driver\n";
+        IllegalArgumentException actual = 
assertThrows(IllegalArgumentException.class, () -> 
swapper.swapToObject(YamlEngine.unmarshal(yamlContent, 
YamlMCPLaunchConfiguration.class)));
+        assertThat(actual.getMessage(), is("MCP launch configuration property 
`runtimeDatabases` contains placeholder database `logic_db` property 
`password`."));
+    }
+    
     @Test
     void assertSwapToYamlConfigurationWithRuntimeDatabases() {
         Map<String, RuntimeDatabaseConfiguration> databases = new 
LinkedHashMap<>(1, 1F);
diff --git 
a/mcp/registry/src/main/java/org/apache/shardingsphere/mcp/registry/MCPRegistryMetadataCommand.java
 
b/mcp/registry/src/main/java/org/apache/shardingsphere/mcp/registry/MCPRegistryMetadataCommand.java
index 1e70519566b..8c100abc9f1 100644
--- 
a/mcp/registry/src/main/java/org/apache/shardingsphere/mcp/registry/MCPRegistryMetadataCommand.java
+++ 
b/mcp/registry/src/main/java/org/apache/shardingsphere/mcp/registry/MCPRegistryMetadataCommand.java
@@ -54,6 +54,10 @@ public final class MCPRegistryMetadataCommand {
     
     private static final String PACKAGE_SHAPE_ERROR_MESSAGE = "server.json 
packages must contain exactly one stdio OCI package and one streamable-http OCI 
package.";
     
+    private static final String STREAMABLE_HTTP_RUNTIME_HINT = "docker";
+    
+    private static final String STREAMABLE_HTTP_PORT_ARGUMENT = 
"127.0.0.1:18088:18088";
+    
     /**
      * Main entrance.
      *
@@ -186,12 +190,24 @@ public final class MCPRegistryMetadataCommand {
                 () -> new IllegalArgumentException("MCP Registry package 
transport type must be stdio or streamable-http."));
         if ("streamable-http".equals(transportType)) {
             validateHttpUrl(transport.get("url"));
+            validateStreamableHttpRuntime(packageMetadata);
         }
         validateEnvironmentVariable(packageMetadata, 
"SHARDINGSPHERE_MCP_TRANSPORT");
         validateEnvironmentVariable(packageMetadata, 
"SHARDINGSPHERE_MCP_CONFIG");
         return transportType;
     }
     
+    private static void validateStreamableHttpRuntime(final Map<String, 
Object> packageMetadata) {
+        
ShardingSpherePreconditions.checkState(STREAMABLE_HTTP_RUNTIME_HINT.equals(packageMetadata.get("runtimeHint")),
+                () -> new IllegalArgumentException("streamable-http OCI 
package runtimeHint must be docker."));
+        Object runtimeArguments = packageMetadata.get("runtimeArguments");
+        ShardingSpherePreconditions.checkState(runtimeArguments instanceof 
List<?> && 1 == ((List<?>) runtimeArguments).size(),
+                () -> new IllegalArgumentException("streamable-http OCI 
package must define one Docker port runtime argument."));
+        Map<String, Object> portArgument = asMap(((List<?>) 
runtimeArguments).get(0), "streamable-http OCI package runtime argument must be 
an object.");
+        
ShardingSpherePreconditions.checkState("named".equals(portArgument.get("type")) 
&& "-p".equals(portArgument.get("name")) && 
STREAMABLE_HTTP_PORT_ARGUMENT.equals(portArgument.get("value")),
+                () -> new IllegalArgumentException("streamable-http OCI 
package must expose Docker port 18088 on 127.0.0.1."));
+    }
+    
     private static List<Map<String, Object>> getPackages(final Map<String, 
Object> server) {
         Object packages = server.get("packages");
         ShardingSpherePreconditions.checkState(packages instanceof List<?>, () 
-> new IllegalArgumentException("server.json packages must be a non-empty 
array."));
diff --git 
a/mcp/registry/src/test/java/org/apache/shardingsphere/mcp/registry/MCPRegistryMetadataCommandTest.java
 
b/mcp/registry/src/test/java/org/apache/shardingsphere/mcp/registry/MCPRegistryMetadataCommandTest.java
index 17d3fa039a0..f3456e7a357 100644
--- 
a/mcp/registry/src/test/java/org/apache/shardingsphere/mcp/registry/MCPRegistryMetadataCommandTest.java
+++ 
b/mcp/registry/src/test/java/org/apache/shardingsphere/mcp/registry/MCPRegistryMetadataCommandTest.java
@@ -83,6 +83,26 @@ class MCPRegistryMetadataCommandTest {
         assertThat(actual.getMessage(), is("streamable-http transport must 
define a URL."));
     }
     
+    @Test
+    void assertExecuteRejectsMissingStreamableHttpRuntimeHint() throws 
IOException {
+        Map<String, Object> server = createServerMetadata();
+        getPackages(server).get(1).remove("runtimeHint");
+        Path serverPath = createServerJson(server);
+        IllegalArgumentException actual = 
assertThrows(IllegalArgumentException.class,
+                () -> MCPRegistryMetadataCommand.execute("--path", 
serverPath.toString(), "--validate-only", "--allow-snapshot"));
+        assertThat(actual.getMessage(), is("streamable-http OCI package 
runtimeHint must be docker."));
+    }
+    
+    @Test
+    void assertExecuteRejectsMismatchedStreamableHttpRuntimeArgument() throws 
IOException {
+        Map<String, Object> server = createServerMetadata();
+        getRuntimeArgument(server).put("value", "18088:18088");
+        Path serverPath = createServerJson(server);
+        IllegalArgumentException actual = 
assertThrows(IllegalArgumentException.class,
+                () -> MCPRegistryMetadataCommand.execute("--path", 
serverPath.toString(), "--validate-only", "--allow-snapshot"));
+        assertThat(actual.getMessage(), is("streamable-http OCI package must 
expose Docker port 18088 on 127.0.0.1."));
+    }
+    
     @Test
     void assertExecuteRejectsMismatchedPackageVersion() throws IOException {
         Map<String, Object> server = createServerMetadata();
@@ -266,11 +286,19 @@ class MCPRegistryMetadataCommandTest {
     }
     
     private Map<String, Object> createPackage(final String transportType, 
final String url) {
-        Map<String, Object> result = new LinkedHashMap<>(5, 1F);
+        Map<String, Object> result = new LinkedHashMap<>(7, 1F);
         result.put("registryType", "oci");
         result.put("identifier", 
"ghcr.io/apache/shardingsphere-mcp:5.5.4-SNAPSHOT");
         result.put("version", "5.5.4-SNAPSHOT");
         result.put("transport", createTransport(transportType, url));
+        if ("streamable-http".equals(transportType)) {
+            result.put("runtimeHint", "docker");
+            Map<String, Object> runtimeArgument = new LinkedHashMap<>(3, 1F);
+            runtimeArgument.put("type", "named");
+            runtimeArgument.put("name", "-p");
+            runtimeArgument.put("value", "127.0.0.1:18088:18088");
+            result.put("runtimeArguments", List.of(runtimeArgument));
+        }
         result.put("environmentVariables", List.of(
                 createEnvironmentVariable("SHARDINGSPHERE_MCP_TRANSPORT", 
"Launch the container in the selected transport mode."),
                 createEnvironmentVariable("SHARDINGSPHERE_MCP_CONFIG", 
"Optional absolute config path inside the OCI container.")));
@@ -310,6 +338,11 @@ class MCPRegistryMetadataCommandTest {
         return getEnvironmentVariables(server, 0).stream().filter(each -> 
"SHARDINGSPHERE_MCP_CONFIG".equals(each.get("name"))).findFirst().orElseThrow();
     }
     
+    @SuppressWarnings("unchecked")
+    private Map<String, Object> getRuntimeArgument(final Map<String, Object> 
server) {
+        return (Map<String, Object>) ((List<?>) 
getPackages(server).get(1).get("runtimeArguments")).get(0);
+    }
+    
     private List<Object> getPackageValues(final Map<String, Object> server, 
final String key) {
         return getPackages(server).stream().map(each -> 
each.get(key)).toList();
     }
diff --git a/mcp/server.json b/mcp/server.json
index cd461022b33..b4dfed13593 100644
--- a/mcp/server.json
+++ b/mcp/server.json
@@ -40,10 +40,18 @@
       "registryType": "oci",
       "identifier": "ghcr.io/apache/shardingsphere-mcp:5.5.4-SNAPSHOT",
       "version": "5.5.4-SNAPSHOT",
+      "runtimeHint": "docker",
       "transport": {
         "type": "streamable-http",
         "url": "http://127.0.0.1:18088/mcp";
       },
+      "runtimeArguments": [
+        {
+          "type": "named",
+          "name": "-p",
+          "value": "127.0.0.1:18088:18088"
+        }
+      ],
       "environmentVariables": [
         {
           "name": "SHARDINGSPHERE_MCP_TRANSPORT",


Reply via email to