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",