You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by zh...@apache.org on 2026/07/24 10:35:24 UTC
(shardingsphere) branch master updated: Refactor MCP contracts and E2E runtime setup (#39235)
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 56b4594a381 Refactor MCP contracts and E2E runtime setup (#39235)
56b4594a381 is described below
commit 56b4594a3812351b7736a5a3b8ca82ddba0b446e
Author: Liang Zhang <zh...@apache.org>
AuthorDate: Fri Jul 24 18:35:17 2026 +0800
Refactor MCP contracts and E2E runtime setup (#39235)
* Refactor MCP contracts and E2E runtime setup
- centralize descriptor-driven metadata and simplify runtime configuration
- preserve algorithm discovery confidence across compatibility fallbacks
- remove unused runtime metadata and legacy test wrappers
- discover packaged and Docker E2E ports at runtime
* Refactor MCP contracts and E2E runtime setup
- centralize descriptor-driven metadata and simplify runtime configuration
- preserve algorithm discovery confidence across compatibility fallbacks
- remove unused runtime metadata and legacy test wrappers
- discover packaged and Docker E2E ports at runtime
---
.../content/reference/mcp/protocol-surface.cn.md | 2 +-
.../content/reference/mcp/protocol-surface.en.md | 2 +-
.../yaml/config/YamlMCPLaunchConfiguration.java | 3 +-
...YamlRuntimeDatabaseConfigurationProperties.java | 40 ------------
.../YamlRuntimeDatabaseConfigurationsSwapper.java | 51 +++------------
...YamlRuntimeDatabaseConfigurationsValidator.java | 63 ++++++-------------
.../config/MCPLaunchConfigurationTest.java | 14 +++--
.../YamlMCPLaunchConfigurationSwapperTest.java | 8 +--
...mlRuntimeDatabaseConfigurationsSwapperTest.java | 41 +++++-------
.../metadata/MetadataResourceResponseFactory.java | 6 +-
.../core/workflow/WorkflowExecutionService.java | 3 +-
.../core/workflow/WorkflowProxyQueryService.java | 3 +-
.../mcp-descriptors/mcp-descriptor-core.yaml | 7 ---
.../MetadataResourceResponseFactoryTest.java | 10 ++-
.../MetadataSearchResourceUriFactoryTest.java | 54 ++++++++++++++++
.../EncryptAlgorithmCompletionHandler.java | 2 +-
.../resource/handler/EncryptAlgorithmsHandler.java | 2 +-
.../EncryptAlgorithmRecommendationService.java | 53 ++++++++--------
.../tool/service/EncryptRuleInspectionService.java | 14 +++--
.../service/EncryptWorkflowPlanningService.java | 3 +-
.../handler/EncryptAlgorithmsHandlerTest.java | 3 +-
.../EncryptAlgorithmRecommendationServiceTest.java | 72 ++++++++++++++++-----
.../service/EncryptRuleInspectionServiceTest.java | 31 ++++-----
.../EncryptWorkflowPlanningServiceTest.java | 9 +--
.../completion/MaskAlgorithmCompletionHandler.java | 2 +-
.../resource/handler/MaskAlgorithmsHandler.java | 2 +-
.../MaskAlgorithmRecommendationService.java | 9 +--
.../tool/service/MaskRuleInspectionService.java | 12 ++--
.../tool/service/MaskWorkflowPlanningService.java | 3 +-
.../handler/MaskAlgorithmsHandlerTest.java | 3 +-
.../MaskAlgorithmRecommendationServiceTest.java | 27 ++++++--
.../service/MaskRuleInspectionServiceTest.java | 24 ++++---
.../service/MaskWorkflowPlanningServiceTest.java | 7 ++-
.../mcp-descriptors/mcp-descriptor-shadow.yaml | 2 +-
.../shadow/ShadowDescriptorContractTest.java | 11 ++++
.../service/ShardingWorkflowValidationService.java | 2 +-
.../mcp-descriptors/mcp-descriptor-sharding.yaml | 23 ++++---
.../sharding/ShardingDescriptorContractTest.java | 12 ++++
.../handler/ShardingWorkflowToolHandlersTest.java | 11 +++-
.../descriptor/MCPDescriptorCatalogIndex.java | 26 ++++++++
.../MCPDescriptorCatalogYamlSwapper.java | 2 +-
.../descriptor/MCPDescriptorYamlKeyValidator.java | 2 +-
.../descriptor/MCPGuidancePayloadBuilder.java | 23 +++----
.../MCPResourceNavigationPayloadBuilder.java | 20 +-----
.../MCPToolDescriptorCatalogValidator.java | 13 +---
.../descriptor/MCPToolRuntimeDescriptor.java | 4 --
.../yaml/YamlMCPToolRuntimeDescriptor.java | 5 --
.../protocol/MCPModelFacingPayloadContract.java | 20 +++---
.../workflow/model/SecretReferenceValue.java | 3 +-
.../workflow/model/WorkflowContextSnapshot.java | 6 +-
.../service/WorkflowArtifactMaskUtils.java | 3 +-
.../WorkflowGuidanceResourceHintProvider.java | 9 +--
.../mcp-descriptors/mcp-descriptor-support.yaml | 3 -
.../descriptor/MCPDescriptorCatalogIndexTest.java | 16 +++--
.../MCPDescriptorCatalogValidatorTest.java | 41 ++++--------
.../MCPResourceNavigationPayloadBuilderTest.java | 7 +--
.../MCPShardingSphereDescriptorCatalogTest.java | 2 +-
.../MCPModelFacingPayloadContractTest.java | 6 +-
.../distribution/DockerImageHttpRuntime.java | 73 ++++++++++++++++++----
.../distribution/DockerImageHttpRuntimeTest.java | 58 +++++++++++++++++
.../PackagedDistributionHttpRuntime.java | 32 +++++++++-
.../PackagedDistributionHttpRuntimeTest.java | 44 +++++++++++++
.../PackagedDistributionTestSupport.java | 31 +++------
.../PackagedDistributionTestSupportTest.java | 5 +-
.../runtime/MySQLRuntimeDockerSupportTest.java | 53 ++++++++++++++++
.../support/runtime/MySQLRuntimeTestSupport.java | 19 ------
.../runtime/MySQLRuntimeTestSupportTest.java | 26 --------
67 files changed, 690 insertions(+), 508 deletions(-)
diff --git a/docs/document/content/reference/mcp/protocol-surface.cn.md b/docs/document/content/reference/mcp/protocol-surface.cn.md
index 6601b6278f5..f3a9aa0d490 100644
--- a/docs/document/content/reference/mcp/protocol-surface.cn.md
+++ b/docs/document/content/reference/mcp/protocol-surface.cn.md
@@ -223,7 +223,7 @@ Client 应在选择不确定的 database、schema、table、column、storage uni
- `items`
- `count`
- `continuation_mode`
-- 存在后续结果时的 `next_page_token`
+- 支持偏移量续页时的 `has_more` 和 `next_offset`
大结果 payload 会使用:
diff --git a/docs/document/content/reference/mcp/protocol-surface.en.md b/docs/document/content/reference/mcp/protocol-surface.en.md
index 018e34dc8da..d50c0b24e5a 100644
--- a/docs/document/content/reference/mcp/protocol-surface.en.md
+++ b/docs/document/content/reference/mcp/protocol-surface.en.md
@@ -223,7 +223,7 @@ List-shaped business payloads usually contain:
- `items`
- `count`
- `continuation_mode`
-- `next_page_token` when continuation is available
+- `has_more` and `next_offset` when offset continuation is available
Large-result payloads use:
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 d31a522d2c6..df16e0b1113 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
@@ -40,6 +40,7 @@ public final class YamlMCPLaunchConfiguration implements YamlConfiguration {
private YamlMCPTransportConfiguration transport;
@NotNull(message = "is required")
+ @Valid
@ValidYamlRuntimeDatabaseConfigurations
- private Map<String, Map<String, Object>> runtimeDatabases;
+ private Map<String, YamlRuntimeDatabaseConfiguration> runtimeDatabases;
}
diff --git a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/config/YamlRuntimeDatabaseConfigurationProperties.java b/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/config/YamlRuntimeDatabaseConfigurationProperties.java
deleted file mode 100644
index 1cd76aba303..00000000000
--- a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/config/YamlRuntimeDatabaseConfigurationProperties.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.bootstrap.config.yaml.config;
-
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
-
-import java.util.Set;
-
-/**
- * YAML runtime database configuration properties.
- */
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-public final class YamlRuntimeDatabaseConfigurationProperties {
-
- public static final String JDBC_URL = "jdbcUrl";
-
- public static final String USERNAME = "username";
-
- public static final String PASSWORD = "password";
-
- public static final String DRIVER_CLASS_NAME = "driverClassName";
-
- public static final Set<String> SUPPORTED_PROPERTIES = Set.of(JDBC_URL, USERNAME, PASSWORD, DRIVER_CLASS_NAME);
-}
diff --git a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/swapper/YamlRuntimeDatabaseConfigurationsSwapper.java b/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/swapper/YamlRuntimeDatabaseConfigurationsSwapper.java
index 61058af308c..c0b2fb91f96 100644
--- a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/swapper/YamlRuntimeDatabaseConfigurationsSwapper.java
+++ b/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/swapper/YamlRuntimeDatabaseConfigurationsSwapper.java
@@ -19,7 +19,6 @@ package org.apache.shardingsphere.mcp.bootstrap.config.yaml.swapper;
import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
import org.apache.shardingsphere.mcp.bootstrap.config.yaml.config.YamlRuntimeDatabaseConfiguration;
-import org.apache.shardingsphere.mcp.bootstrap.config.yaml.config.YamlRuntimeDatabaseConfigurationProperties;
import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
import java.util.Collections;
@@ -40,14 +39,14 @@ public final class YamlRuntimeDatabaseConfigurationsSwapper {
* @param yamlRuntimeConfiguration YAML runtime configuration
* @return runtime configuration object
*/
- public Map<String, RuntimeDatabaseConfiguration> swapToObject(final Map<String, Map<String, Object>> yamlRuntimeConfiguration) {
+ public Map<String, RuntimeDatabaseConfiguration> swapToObject(final Map<String, YamlRuntimeDatabaseConfiguration> yamlRuntimeConfiguration) {
if (null == yamlRuntimeConfiguration) {
return Collections.emptyMap();
}
Map<String, RuntimeDatabaseConfiguration> result = new LinkedHashMap<>(yamlRuntimeConfiguration.size(), 1F);
- for (Entry<String, Map<String, Object>> entry : yamlRuntimeConfiguration.entrySet()) {
- validateYamlRuntimeDatabaseConfiguration(entry.getValue());
- result.put(entry.getKey(), runtimeDatabaseConfigSwapper.swapToObject(createYamlRuntimeDatabaseConfiguration(entry.getValue())));
+ for (Entry<String, YamlRuntimeDatabaseConfiguration> entry : yamlRuntimeConfiguration.entrySet()) {
+ ShardingSpherePreconditions.checkNotNull(entry.getValue(), () -> new IllegalArgumentException("MCP runtime database configuration cannot be null."));
+ result.put(entry.getKey(), runtimeDatabaseConfigSwapper.swapToObject(entry.getValue()));
}
return result;
}
@@ -58,48 +57,12 @@ public final class YamlRuntimeDatabaseConfigurationsSwapper {
* @param runtimeConfiguration runtime configuration
* @return YAML runtime configuration
*/
- public Map<String, Map<String, Object>> swapToYamlConfiguration(final Map<String, RuntimeDatabaseConfiguration> runtimeConfiguration) {
- Map<String, Map<String, Object>> result = new LinkedHashMap<>(runtimeConfiguration.size(), 1F);
+ public Map<String, YamlRuntimeDatabaseConfiguration> swapToYamlConfiguration(final Map<String, RuntimeDatabaseConfiguration> runtimeConfiguration) {
+ Map<String, YamlRuntimeDatabaseConfiguration> result = new LinkedHashMap<>(runtimeConfiguration.size(), 1F);
for (Entry<String, RuntimeDatabaseConfiguration> entry : runtimeConfiguration.entrySet()) {
ShardingSpherePreconditions.checkNotNull(entry.getValue(), () -> new IllegalArgumentException("Runtime database configuration cannot be null."));
- result.put(entry.getKey(), createYamlRuntimeDatabaseConfiguration(runtimeDatabaseConfigSwapper.swapToYamlConfiguration(entry.getValue())));
+ result.put(entry.getKey(), runtimeDatabaseConfigSwapper.swapToYamlConfiguration(entry.getValue()));
}
return result;
}
-
- private YamlRuntimeDatabaseConfiguration createYamlRuntimeDatabaseConfiguration(final Map<String, Object> yamlProperties) {
- if (null == yamlProperties) {
- return null;
- }
- YamlRuntimeDatabaseConfiguration result = new YamlRuntimeDatabaseConfiguration();
- result.setJdbcUrl(getYamlText(yamlProperties, YamlRuntimeDatabaseConfigurationProperties.JDBC_URL));
- result.setUsername(getYamlText(yamlProperties, YamlRuntimeDatabaseConfigurationProperties.USERNAME));
- result.setPassword(getYamlText(yamlProperties, YamlRuntimeDatabaseConfigurationProperties.PASSWORD));
- result.setDriverClassName(getYamlText(yamlProperties, YamlRuntimeDatabaseConfigurationProperties.DRIVER_CLASS_NAME));
- return result;
- }
-
- private Map<String, Object> createYamlRuntimeDatabaseConfiguration(final YamlRuntimeDatabaseConfiguration yamlRuntimeDatabaseConfig) {
- Map<String, Object> result = new LinkedHashMap<>(YamlRuntimeDatabaseConfigurationProperties.SUPPORTED_PROPERTIES.size(), 1F);
- result.put(YamlRuntimeDatabaseConfigurationProperties.JDBC_URL, yamlRuntimeDatabaseConfig.getJdbcUrl());
- result.put(YamlRuntimeDatabaseConfigurationProperties.USERNAME, yamlRuntimeDatabaseConfig.getUsername());
- result.put(YamlRuntimeDatabaseConfigurationProperties.PASSWORD, yamlRuntimeDatabaseConfig.getPassword());
- result.put(YamlRuntimeDatabaseConfigurationProperties.DRIVER_CLASS_NAME, yamlRuntimeDatabaseConfig.getDriverClassName());
- return result;
- }
-
- private void validateYamlRuntimeDatabaseConfiguration(final Map<String, Object> yamlProperties) {
- if (null == yamlProperties) {
- return;
- }
- for (String each : yamlProperties.keySet()) {
- ShardingSpherePreconditions.checkState(YamlRuntimeDatabaseConfigurationProperties.SUPPORTED_PROPERTIES.contains(each),
- () -> new IllegalArgumentException(String.format("Unsupported runtime database property `%s`.", each)));
- }
- }
-
- private String getYamlText(final Map<String, Object> yamlProperties, final String key) {
- Object result = yamlProperties.get(key);
- return null == result ? null : result.toString();
- }
}
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 e699e3ae613..8f312bd2782 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
@@ -17,7 +17,7 @@
package org.apache.shardingsphere.mcp.bootstrap.config.yaml.validator;
-import org.apache.shardingsphere.mcp.bootstrap.config.yaml.config.YamlRuntimeDatabaseConfigurationProperties;
+import org.apache.shardingsphere.mcp.bootstrap.config.yaml.config.YamlRuntimeDatabaseConfiguration;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
@@ -27,21 +27,21 @@ import java.util.Map.Entry;
/**
* YAML runtime database configurations validator.
*/
-public final class YamlRuntimeDatabaseConfigurationsValidator implements ConstraintValidator<ValidYamlRuntimeDatabaseConfigurations, Map<String, Map<String, Object>>> {
+public final class YamlRuntimeDatabaseConfigurationsValidator implements ConstraintValidator<ValidYamlRuntimeDatabaseConfigurations, Map<String, YamlRuntimeDatabaseConfiguration>> {
@Override
- public boolean isValid(final Map<String, Map<String, Object>> value, final ConstraintValidatorContext context) {
+ public boolean isValid(final Map<String, YamlRuntimeDatabaseConfiguration> value, final ConstraintValidatorContext context) {
if (null == value) {
return true;
}
boolean result = true;
- for (Entry<String, Map<String, Object>> entry : value.entrySet()) {
+ for (Entry<String, YamlRuntimeDatabaseConfiguration> entry : value.entrySet()) {
result = validateRuntimeDatabase(entry, context) && result;
}
return result;
}
- private boolean validateRuntimeDatabase(final Entry<String, Map<String, Object>> databaseEntry, final ConstraintValidatorContext context) {
+ private boolean validateRuntimeDatabase(final Entry<String, YamlRuntimeDatabaseConfiguration> databaseEntry, final ConstraintValidatorContext context) {
if (isPlaceholder(databaseEntry.getKey())) {
addViolation(context, String.format("contains placeholder database name `%s`", databaseEntry.getKey()));
return false;
@@ -50,57 +50,30 @@ public final class YamlRuntimeDatabaseConfigurationsValidator implements Constra
addViolation(context, String.format("contains null configuration for database `%s`", databaseEntry.getKey()));
return false;
}
- return validateSupportedProperties(databaseEntry, context) && validateRequiredProperties(databaseEntry, context) && validateNoPlaceholders(databaseEntry, context);
+ return validateNoPlaceholders(databaseEntry, context);
}
- private boolean validateSupportedProperties(final Entry<String, Map<String, Object>> databaseEntry, final ConstraintValidatorContext context) {
- boolean result = true;
- for (String each : databaseEntry.getValue().keySet()) {
- if (!YamlRuntimeDatabaseConfigurationProperties.SUPPORTED_PROPERTIES.contains(each)) {
- addViolation(context, String.format("contains unsupported property `%s` for database `%s`", each, databaseEntry.getKey()));
- result = false;
- }
- }
- return result;
- }
-
- private boolean validateRequiredProperties(final Entry<String, Map<String, Object>> databaseEntry, final ConstraintValidatorContext context) {
- boolean result = validateRequiredText(databaseEntry, YamlRuntimeDatabaseConfigurationProperties.JDBC_URL, context);
- result = validateRequiredText(databaseEntry, YamlRuntimeDatabaseConfigurationProperties.USERNAME, context) && result;
- return validateRequiredText(databaseEntry, YamlRuntimeDatabaseConfigurationProperties.DRIVER_CLASS_NAME, context) && result;
- }
-
- private boolean validateRequiredText(final Entry<String, Map<String, Object>> databaseEntry, final String key, final ConstraintValidatorContext context) {
- Object value = databaseEntry.getValue().get(key);
- if (null != value && !value.toString().isBlank()) {
- return true;
- }
- addViolation(context, String.format("contains database `%s` property `%s` is required", databaseEntry.getKey(), key));
- 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 validateNoPlaceholders(final Entry<String, YamlRuntimeDatabaseConfiguration> databaseEntry, final ConstraintValidatorContext context) {
+ YamlRuntimeDatabaseConfiguration config = databaseEntry.getValue();
+ boolean result = validateNoPlaceholder(databaseEntry.getKey(), "jdbcUrl", config.getJdbcUrl(), context);
+ result = validateNoPlaceholder(databaseEntry.getKey(), "username", config.getUsername(), context) && result;
+ result = validateNoPlaceholder(databaseEntry.getKey(), "driverClassName", config.getDriverClassName(), context) && result;
+ return validateNoPasswordPlaceholder(databaseEntry.getKey(), config.getPassword(), 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())) {
+ private boolean validateNoPlaceholder(final String databaseName, final String propertyName, final String value, final ConstraintValidatorContext context) {
+ if (null == value || !isPlaceholder(value)) {
return true;
}
- addViolation(context, String.format("contains placeholder database `%s` property `%s`", databaseEntry.getKey(), key));
+ addViolation(context, String.format("contains placeholder database `%s` property `%s`", databaseName, propertyName));
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())) {
+ private boolean validateNoPasswordPlaceholder(final String databaseName, final String value, final ConstraintValidatorContext context) {
+ if (null == value || !isStandalonePlaceholder(value)) {
return true;
}
- addViolation(context, String.format("contains placeholder database `%s` property `%s`", databaseEntry.getKey(), YamlRuntimeDatabaseConfigurationProperties.PASSWORD));
+ addViolation(context, String.format("contains placeholder database `%s` property `password`", databaseName));
return false;
}
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 2710009f62d..f7778fe2b50 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
@@ -21,6 +21,7 @@ import org.apache.shardingsphere.mcp.api.transport.MCPTransportType;
import org.apache.shardingsphere.mcp.bootstrap.config.yaml.config.YamlHttpTransportConfiguration;
import org.apache.shardingsphere.mcp.bootstrap.config.yaml.config.YamlMCPLaunchConfiguration;
import org.apache.shardingsphere.mcp.bootstrap.config.yaml.config.YamlMCPTransportConfiguration;
+import org.apache.shardingsphere.mcp.bootstrap.config.yaml.config.YamlRuntimeDatabaseConfiguration;
import org.apache.shardingsphere.mcp.support.configuration.MCPConfigurationValidator;
import org.junit.jupiter.api.Test;
@@ -132,11 +133,12 @@ class MCPLaunchConfigurationTest {
return result;
}
- private Map<String, Map<String, Object>> createRuntimeDatabases() {
- return Collections.singletonMap("logic_db", Map.of(
- "jdbcUrl", "jdbc:mysql://localhost:3306/logic_db",
- "username", "demo",
- "password", "",
- "driverClassName", "com.mysql.cj.jdbc.Driver"));
+ private Map<String, YamlRuntimeDatabaseConfiguration> createRuntimeDatabases() {
+ YamlRuntimeDatabaseConfiguration result = new YamlRuntimeDatabaseConfiguration();
+ result.setJdbcUrl("jdbc:mysql://localhost:3306/logic_db");
+ result.setUsername("demo");
+ result.setPassword("");
+ result.setDriverClassName("com.mysql.cj.jdbc.Driver");
+ return Collections.singletonMap("logic_db", result);
}
}
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 665f29d86e8..c4c79889cbc 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
@@ -85,7 +85,7 @@ class YamlMCPLaunchConfigurationSwapperTest {
+ " username: demo\n"
+ " driverClassName: ''\n";
IllegalArgumentException actual = assertThrows(IllegalArgumentException.class, () -> swapper.swapToObject(YamlEngine.unmarshal(yamlContent, YamlMCPLaunchConfiguration.class)));
- assertThat(actual.getMessage(), is("MCP launch configuration property `runtimeDatabases` contains database `logic_db` property `driverClassName` is required."));
+ assertThat(actual.getMessage(), is("MCP launch configuration property `runtimeDatabases[logic_db].driverClassName` is required."));
}
@Test
@@ -252,8 +252,8 @@ class YamlMCPLaunchConfigurationSwapperTest {
+ " password: ''\n"
+ " driverClassName: com.mysql.cj.jdbc.Driver\n"
+ " unsupported: true\n";
- IllegalArgumentException actual = assertThrows(IllegalArgumentException.class, () -> swapper.swapToObject(YamlEngine.unmarshal(yamlContent, YamlMCPLaunchConfiguration.class)));
- assertThat(actual.getMessage(), is("MCP launch configuration property `runtimeDatabases` contains unsupported property `unsupported` for database `logic_db`."));
+ ConstructorException actual = assertThrows(ConstructorException.class, () -> swapper.swapToObject(YamlEngine.unmarshal(yamlContent, YamlMCPLaunchConfiguration.class)));
+ assertThat(actual.getMessage(), containsString("Unable to find property 'unsupported'"));
}
@Test
@@ -304,7 +304,7 @@ class YamlMCPLaunchConfigurationSwapperTest {
databases.put("logic_db", new RuntimeDatabaseConfiguration("jdbc:mysql://localhost:3306/logic_db", "demo", "", "com.mysql.cj.jdbc.Driver"));
MCPLaunchConfiguration launchConfig = new MCPLaunchConfiguration(MCPTransportType.HTTP, new HttpTransportConfiguration("127.0.0.1", 18088, "/mcp"), databases);
YamlMCPLaunchConfiguration actual = swapper.swapToYamlConfiguration(launchConfig);
- assertThat(String.valueOf(actual.getRuntimeDatabases().get("logic_db").get("username")), is("demo"));
+ assertThat(actual.getRuntimeDatabases().get("logic_db").getUsername(), is("demo"));
assertThat(actual.getTransport().getType(), is(MCPTransportType.HTTP));
assertThat(actual.getTransport().getHttp().getBindHost(), is("127.0.0.1"));
}
diff --git a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/swapper/YamlRuntimeDatabaseConfigurationsSwapperTest.java b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/swapper/YamlRuntimeDatabaseConfigurationsSwapperTest.java
index 69eb284fc08..aa5455dfa6f 100644
--- a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/swapper/YamlRuntimeDatabaseConfigurationsSwapperTest.java
+++ b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/swapper/YamlRuntimeDatabaseConfigurationsSwapperTest.java
@@ -17,6 +17,7 @@
package org.apache.shardingsphere.mcp.bootstrap.config.yaml.swapper;
+import org.apache.shardingsphere.mcp.bootstrap.config.yaml.config.YamlRuntimeDatabaseConfiguration;
import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
import org.junit.jupiter.api.Test;
@@ -34,21 +35,16 @@ class YamlRuntimeDatabaseConfigurationsSwapperTest {
@Test
void assertSwapToObject() {
- Map<String, RuntimeDatabaseConfiguration> actual = swapper.swapToObject(Map.of("logic_db", Map.of(
- "jdbcUrl", "jdbc:mysql://localhost:3306/logic_db",
- "username", "demo",
- "password", "",
- "driverClassName", "com.mysql.cj.jdbc.Driver")));
+ Map<String, RuntimeDatabaseConfiguration> actual = swapper.swapToObject(Map.of(
+ "logic_db", createYamlRuntimeDatabaseConfiguration("jdbc:mysql://localhost:3306/logic_db", "demo", "", "com.mysql.cj.jdbc.Driver")));
assertThat(actual.get("logic_db").getJdbcUrl(), is("jdbc:mysql://localhost:3306/logic_db"));
}
@Test
void assertSwapToObjectWithPasswordMissing() {
- Map<String, RuntimeDatabaseConfiguration> actual = swapper.swapToObject(Map.of("logic_db", Map.of(
- "jdbcUrl", "jdbc:mysql://localhost:3306/logic_db",
- "username", "demo",
- "driverClassName", "com.mysql.cj.jdbc.Driver")));
+ Map<String, RuntimeDatabaseConfiguration> actual = swapper.swapToObject(Map.of(
+ "logic_db", createYamlRuntimeDatabaseConfiguration("jdbc:mysql://localhost:3306/logic_db", "demo", null, "com.mysql.cj.jdbc.Driver")));
assertThat(actual.get("logic_db").getPassword(), is(""));
}
@@ -62,7 +58,7 @@ class YamlRuntimeDatabaseConfigurationsSwapperTest {
@Test
void assertSwapToObjectWithNullRuntimeDatabaseConfiguration() {
- Map<String, Map<String, Object>> yamlRuntimeConfiguration = new LinkedHashMap<>(1, 1F);
+ Map<String, YamlRuntimeDatabaseConfiguration> yamlRuntimeConfiguration = new LinkedHashMap<>(1, 1F);
yamlRuntimeConfiguration.put("logic_db", null);
IllegalArgumentException actual = assertThrows(IllegalArgumentException.class, () -> swapper.swapToObject(yamlRuntimeConfiguration));
@@ -70,24 +66,12 @@ class YamlRuntimeDatabaseConfigurationsSwapperTest {
assertThat(actual.getMessage(), is("MCP runtime database configuration cannot be null."));
}
- @Test
- void assertSwapToObjectWithUnsupportedRuntimeDatabaseProperty() {
- IllegalArgumentException actual = assertThrows(IllegalArgumentException.class, () -> swapper.swapToObject(Map.of("logic_db", Map.of(
- "jdbcUrl", "jdbc:mysql://localhost:3306/logic_db",
- "username", "demo",
- "password", "",
- "driverClassName", "com.mysql.cj.jdbc.Driver",
- "unsupported", true))));
-
- assertThat(actual.getMessage(), is("Unsupported runtime database property `unsupported`."));
- }
-
@Test
void assertSwapToYamlConfiguration() {
- Map<String, Map<String, Object>> actual = swapper.swapToYamlConfiguration(Map.of(
+ Map<String, YamlRuntimeDatabaseConfiguration> actual = swapper.swapToYamlConfiguration(Map.of(
"logic_db", new RuntimeDatabaseConfiguration("jdbc:mysql://localhost:3306/logic_db", "demo", "", "com.mysql.cj.jdbc.Driver")));
- assertThat(String.valueOf(actual.get("logic_db").get("driverClassName")), is("com.mysql.cj.jdbc.Driver"));
+ assertThat(actual.get("logic_db").getDriverClassName(), is("com.mysql.cj.jdbc.Driver"));
}
@Test
@@ -99,4 +83,13 @@ class YamlRuntimeDatabaseConfigurationsSwapperTest {
assertThat(actual.getMessage(), is("Runtime database configuration cannot be null."));
}
+
+ private YamlRuntimeDatabaseConfiguration createYamlRuntimeDatabaseConfiguration(final String jdbcUrl, final String username, final String password, final String driverClassName) {
+ YamlRuntimeDatabaseConfiguration result = new YamlRuntimeDatabaseConfiguration();
+ result.setJdbcUrl(jdbcUrl);
+ result.setUsername(username);
+ result.setPassword(password);
+ result.setDriverClassName(driverClassName);
+ return result;
+ }
}
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceResponseFactory.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceResponseFactory.java
index 3512c19a35b..d1d9eedde0b 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceResponseFactory.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceResponseFactory.java
@@ -276,10 +276,8 @@ public final class MetadataResourceResponseFactory {
Map<String, Object> result = new LinkedHashMap<>(4, 1F);
String uriTemplate = descriptor.getUriTemplate();
Optional<String> selfUri = new MCPUriTemplate(uriTemplate).expandIfComplete(uriVariables);
- selfUri.ifPresent(uri -> {
- result.put(MCPPayloadFieldNames.SELF_RESOURCE,
- MCPResourceHintUtils.create(uri, resolveResourceKind(uri), "inspect_self", "Read this metadata resource.", MCPPayloadFieldNames.SELF_RESOURCE));
- });
+ selfUri.ifPresent(uri -> result.put(MCPPayloadFieldNames.SELF_RESOURCE,
+ MCPResourceHintUtils.create(uri, resolveResourceKind(uri), "inspect_self", "Read this metadata resource.", MCPPayloadFieldNames.SELF_RESOURCE)));
String parentUri = createParentUri(selfUri.orElse(""));
if (!parentUri.isEmpty()) {
result.put(MCPPayloadFieldNames.PARENT_RESOURCE, MCPResourceHintUtils.create(parentUri, resolveResourceKind(parentUri), "inspect_parent",
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionService.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionService.java
index 34a97add9fb..fece5edf28c 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionService.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionService.java
@@ -47,6 +47,7 @@ import org.apache.shardingsphere.mcp.support.workflow.spi.MCPWorkflowApplyArtifa
import org.apache.shardingsphere.mcp.support.workflow.spi.MCPWorkflowApplySynchronizationHandler;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
/**
@@ -103,7 +104,7 @@ public final class WorkflowExecutionService {
if (executionMode.isEmpty()) {
throw new MCPExecutionModeRequiredException(WorkflowToolDescriptors.APPLY_TOOL_NAME, EXECUTION_MODES, createPreviewSuggestedArguments(snapshot));
}
- String result = executionMode.toLowerCase();
+ String result = executionMode.toLowerCase(Locale.ENGLISH);
if (!EXECUTION_MODES.contains(result)) {
throw new MCPInvalidExecutionModeException(WorkflowToolDescriptors.APPLY_TOOL_NAME, EXECUTION_MODES, createPreviewSuggestedArguments(snapshot));
}
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowProxyQueryService.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowProxyQueryService.java
index b27d2216dcf..d929d63d919 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowProxyQueryService.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowProxyQueryService.java
@@ -39,6 +39,7 @@ import java.sql.Statement;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Optional;
@@ -108,7 +109,7 @@ public final class WorkflowProxyQueryService implements MCPFeatureQueryFacade {
while (resultSet.next()) {
Map<String, Object> row = new LinkedHashMap<>(resultSetMetaData.getColumnCount(), 1F);
for (int index = 1; index <= resultSetMetaData.getColumnCount(); index++) {
- row.put(resultSetMetaData.getColumnLabel(index).toLowerCase(), resultSet.getObject(index));
+ row.put(resultSetMetaData.getColumnLabel(index).toLowerCase(Locale.ENGLISH), resultSet.getObject(index));
}
result.add(row);
}
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 18337b9d077..77e207b4370 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
@@ -1796,13 +1796,6 @@ tools:
destructiveHint: true
idempotentHint: false
openWorldHint: true
- runtime:
- sideEffectScope:
- - physical-data
- - physical-structure
- - privilege-metadata
- - rule-metadata
- - transaction-state
meta:
org.apache.shardingsphere/related-resource-uris:
- "shardingsphere://databases/{database}/capabilities"
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceResponseFactoryTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceResponseFactoryTest.java
index 0051d76fb88..62dd90533ef 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceResponseFactoryTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/metadata/MetadataResourceResponseFactoryTest.java
@@ -65,14 +65,20 @@ class MetadataResourceResponseFactoryTest {
assertThat(actual.get("object_scope"), is("logical-database"));
assertThat(actual.get("items"), is(List.of(Map.of("database", "逻辑 库/2026?"))));
String expectedSelfUri = "shardingsphere://databases/%E9%80%BB%E8%BE%91%20%E5%BA%93%2F2026%3F";
- assertThat(((Map<?, ?>) actual.get("self_resource")).get("uri"), is(expectedSelfUri));
- assertThat(((Map<?, ?>) actual.get("parent_resource")).get("uri"), is("shardingsphere://databases"));
+ Map<?, ?> actualSelfResource = (Map<?, ?>) actual.get("self_resource");
+ assertThat(actualSelfResource.get("uri"), is(expectedSelfUri));
+ assertThat(actualSelfResource.get("resource_kind"), is("logical-database"));
+ Map<?, ?> actualParentResource = (Map<?, ?>) actual.get("parent_resource");
+ assertThat(actualParentResource.get("uri"), is("shardingsphere://databases"));
+ assertThat(actualParentResource.get("resource_kind"), is("logical-database"));
List<String> nextResourceUris = ((List<?>) actual.get("next_resources")).stream().map(each -> (String) ((Map<?, ?>) each).get("uri")).toList();
assertThat(nextResourceUris, is(List.of(
expectedSelfUri + "/schemas",
expectedSelfUri + "/storage-units",
expectedSelfUri + "/single-tables",
expectedSelfUri + "/single-table/default-storage-unit")));
+ List<String> nextResourceKinds = ((List<?>) actual.get("next_resources")).stream().map(each -> (String) ((Map<?, ?>) each).get("resource_kind")).toList();
+ assertThat(nextResourceKinds, is(List.of("schema", "storage-unit", "single-table", "single-table")));
}
@Test
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/MetadataSearchResourceUriFactoryTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/MetadataSearchResourceUriFactoryTest.java
new file mode 100644
index 00000000000..e72544d1344
--- /dev/null
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/MetadataSearchResourceUriFactoryTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.core.tool.handler.metadata.MetadataSearchResourceUriFactory.MetadataResourceUris;
+import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPMetadataObjectType;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class MetadataSearchResourceUriFactoryTest {
+
+ @Test
+ void assertCreateDatabaseResourceKinds() {
+ MetadataResourceUris actual = new MetadataSearchResourceUriFactory().create("logic_db", "", SupportedMCPMetadataObjectType.DATABASE, "", "", "");
+ assertThat(actual.resource().get("resource_kind"), is("database"));
+ assertThat(actual.parentResource().get("resource_kind"), is("database"));
+ assertThat(actual.nextResources().stream().map(each -> each.get("resource_kind")).toList(), is(List.of("database", "schema")));
+ }
+
+ @Test
+ void assertCreateTableResourceKinds() {
+ MetadataResourceUris actual = new MetadataSearchResourceUriFactory().create("logic_db", "public", SupportedMCPMetadataObjectType.TABLE, "orders", "", "");
+ assertThat(actual.resource().get("resource_kind"), is("table"));
+ assertThat(actual.parentResource().get("resource_kind"), is("table"));
+ assertThat(actual.nextResources().stream().map(each -> each.get("resource_kind")).toList(), is(List.of("column", "index")));
+ }
+
+ @Test
+ void assertCreateStorageUnitResourceKinds() {
+ MetadataResourceUris actual = new MetadataSearchResourceUriFactory().create("logic_db", "", SupportedMCPMetadataObjectType.STORAGE_UNIT, "", "", "write_ds");
+ assertThat(actual.resource().get("resource_kind"), is("storage-unit"));
+ assertThat(actual.parentResource().get("resource_kind"), is("storage-unit"));
+ assertThat(actual.nextResources().stream().map(each -> each.get("resource_kind")).toList(), is(List.of("storage-unit")));
+ }
+}
diff --git a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/completion/EncryptAlgorithmCompletionHandler.java b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/completion/EncryptAlgorithmCompletionHandler.java
index f3700afe080..9bbb672d220 100644
--- a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/completion/EncryptAlgorithmCompletionHandler.java
+++ b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/completion/EncryptAlgorithmCompletionHandler.java
@@ -55,7 +55,7 @@ public final class EncryptAlgorithmCompletionHandler implements MCPCompletionHan
@Override
public MCPCompletionHandlerResult complete(final MCPFeatureRequestContext handlerContext, final MCPCompletionRequest request) {
- return new MCPCompletionHandlerResult(ruleInspectionService.queryEncryptAlgorithms(handlerContext.getQueryFacade()).stream()
+ return new MCPCompletionHandlerResult(ruleInspectionService.queryEncryptAlgorithms(handlerContext.getQueryFacade()).getRows().stream()
.map(this::createAlgorithmCandidate).filter(each -> !each.getValue().isEmpty()).toList());
}
diff --git a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/resource/handler/EncryptAlgorithmsHandler.java b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/resource/handler/EncryptAlgorithmsHandler.java
index 9f0cde1ffa9..cf790eeb479 100644
--- a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/resource/handler/EncryptAlgorithmsHandler.java
+++ b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/resource/handler/EncryptAlgorithmsHandler.java
@@ -46,7 +46,7 @@ public final class EncryptAlgorithmsHandler implements MCPResourceHandler<MCPFea
@Override
public MCPSuccessPayload handle(final MCPFeatureRequestContext requestContext, final MCPResourceURIVariables uriVariables) {
- return new MCPItemsPayload(ruleInspectionService.queryEncryptAlgorithms(requestContext.getQueryFacade()),
+ return new MCPItemsPayload(ruleInspectionService.queryEncryptAlgorithms(requestContext.getQueryFacade()).getRows(),
MCPResourceNavigationPayloadBuilder.create(MCPDescriptorCatalogIndex.getRequiredResourceDescriptor(getResourceUriTemplate()), uriVariables));
}
}
diff --git a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptAlgorithmRecommendationService.java b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptAlgorithmRecommendationService.java
index e902b08aa90..b5734562aa3 100644
--- a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptAlgorithmRecommendationService.java
+++ b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptAlgorithmRecommendationService.java
@@ -23,6 +23,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowQueryResult;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowAlgorithmUtils;
import java.util.LinkedList;
@@ -38,48 +39,48 @@ public final class EncryptAlgorithmRecommendationService {
* Recommend encrypt algorithms.
*
* @param request workflow request
- * @param encryptAlgorithms encrypt algorithm plugins
+ * @param algorithmResult encrypt algorithm query result
* @param issues workflow issues
* @return selected candidates
*/
public List<AlgorithmCandidate> recommendEncryptAlgorithms(final EncryptWorkflowRequest request,
- final List<Map<String, Object>> encryptAlgorithms, final List<WorkflowIssue> issues) {
+ final WorkflowQueryResult algorithmResult, final List<WorkflowIssue> issues) {
List<AlgorithmCandidate> result = new LinkedList<>();
- String primaryType = resolvePrimaryEncryptAlgorithm(request, encryptAlgorithms, issues);
+ String primaryType = resolvePrimaryEncryptAlgorithm(request, algorithmResult, issues);
if (!primaryType.isEmpty()) {
result.add(createEncryptCandidate(EncryptFeatureDefinition.ALGORITHM_ROLE_PRIMARY, primaryType, request));
}
if (Boolean.TRUE.equals(request.getOptions().getRequiresEqualityFilter())) {
- addAssistedQueryCandidate(result, request, encryptAlgorithms, issues);
+ addAssistedQueryCandidate(result, request, algorithmResult, issues);
}
if (Boolean.TRUE.equals(request.getOptions().getRequiresLikeQuery())) {
- addLikeQueryCandidate(result, request, encryptAlgorithms, issues);
+ addLikeQueryCandidate(result, request, algorithmResult, issues);
}
return result;
}
- private String resolvePrimaryEncryptAlgorithm(final EncryptWorkflowRequest request, final List<Map<String, Object>> encryptAlgorithms,
+ private String resolvePrimaryEncryptAlgorithm(final EncryptWorkflowRequest request, final WorkflowQueryResult algorithmResult,
final List<WorkflowIssue> issues) {
String actualAlgorithmType = WorkflowAlgorithmUtils.normalizeAlgorithmType(request.getAlgorithmType());
if (!actualAlgorithmType.isEmpty()) {
- return resolveSpecifiedPrimaryEncryptAlgorithm(request, encryptAlgorithms, issues, actualAlgorithmType);
+ return resolveSpecifiedPrimaryEncryptAlgorithm(request, algorithmResult, issues, actualAlgorithmType);
}
if (Boolean.TRUE.equals(request.getOptions().getRequiresLikeQuery())) {
- for (Map<String, Object> each : encryptAlgorithms) {
+ for (Map<String, Object> each : algorithmResult.getRows()) {
if (Boolean.TRUE.equals(each.get(EncryptFeatureDefinition.ALGORITHM_CAPABILITY_LIKE))) {
return WorkflowAlgorithmUtils.getAlgorithmType(each);
}
}
}
- if (WorkflowAlgorithmUtils.containsAlgorithm(encryptAlgorithms, "AES")) {
+ if (WorkflowAlgorithmUtils.containsAlgorithm(algorithmResult.getRows(), "AES")) {
return "AES";
}
- return encryptAlgorithms.isEmpty() ? "" : WorkflowAlgorithmUtils.getAlgorithmType(encryptAlgorithms.getFirst());
+ return algorithmResult.getRows().isEmpty() ? "" : WorkflowAlgorithmUtils.getAlgorithmType(algorithmResult.getRows().getFirst());
}
- private String resolveSpecifiedPrimaryEncryptAlgorithm(final EncryptWorkflowRequest request, final List<Map<String, Object>> encryptAlgorithms,
+ private String resolveSpecifiedPrimaryEncryptAlgorithm(final EncryptWorkflowRequest request, final WorkflowQueryResult algorithmResult,
final List<WorkflowIssue> issues, final String algorithmType) {
- if (!WorkflowAlgorithmUtils.containsAlgorithm(encryptAlgorithms, algorithmType)) {
+ if (algorithmResult.isAvailabilityConfirmed() && !WorkflowAlgorithmUtils.containsAlgorithm(algorithmResult.getRows(), algorithmType)) {
issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM,
String.format("Encrypt algorithm `%s` is not visible from the current Proxy.", algorithmType), "Choose an available encrypt algorithm.", false, Map.of()));
return "";
@@ -94,8 +95,8 @@ public final class EncryptAlgorithmRecommendationService {
}
private void addAssistedQueryCandidate(final List<AlgorithmCandidate> candidates, final EncryptWorkflowRequest request,
- final List<Map<String, Object>> encryptAlgorithms, final List<WorkflowIssue> issues) {
- String assistedQueryType = resolveAssistedQueryAlgorithm(request, encryptAlgorithms, issues);
+ final WorkflowQueryResult algorithmResult, final List<WorkflowIssue> issues) {
+ String assistedQueryType = resolveAssistedQueryAlgorithm(request, algorithmResult, issues);
if (assistedQueryType.isEmpty()) {
addCapabilityConflictIssue(issues, request.getOptions().getAssistedQueryAlgorithmType(),
"No assisted-query algorithm is available for the current requirement.", "Install or specify a supported assisted-query algorithm.");
@@ -106,8 +107,8 @@ public final class EncryptAlgorithmRecommendationService {
}
private void addLikeQueryCandidate(final List<AlgorithmCandidate> candidates, final EncryptWorkflowRequest request,
- final List<Map<String, Object>> encryptAlgorithms, final List<WorkflowIssue> issues) {
- String likeQueryType = resolveLikeQueryAlgorithm(request, encryptAlgorithms, issues);
+ final WorkflowQueryResult algorithmResult, final List<WorkflowIssue> issues) {
+ String likeQueryType = resolveLikeQueryAlgorithm(request, algorithmResult, issues);
if (likeQueryType.isEmpty()) {
addCapabilityConflictIssue(issues, request.getOptions().getLikeQueryAlgorithmType(),
"No like-query algorithm is available for the current requirement.", "Install or specify a supported like-query algorithm.");
@@ -124,17 +125,17 @@ public final class EncryptAlgorithmRecommendationService {
issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_CAPABILITY_CONFLICT, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM, message, userAction, false, Map.of()));
}
- private String resolveAssistedQueryAlgorithm(final EncryptWorkflowRequest request, final List<Map<String, Object>> encryptAlgorithms,
+ private String resolveAssistedQueryAlgorithm(final EncryptWorkflowRequest request, final WorkflowQueryResult algorithmResult,
final List<WorkflowIssue> issues) {
String actualAlgorithmType = WorkflowAlgorithmUtils.normalizeAlgorithmType(request.getOptions().getAssistedQueryAlgorithmType());
if (!actualAlgorithmType.isEmpty()) {
- return resolveSpecifiedAssistedQueryAlgorithm(encryptAlgorithms, issues, actualAlgorithmType);
+ return resolveSpecifiedAssistedQueryAlgorithm(algorithmResult, issues, actualAlgorithmType);
}
- return WorkflowAlgorithmUtils.containsAlgorithm(encryptAlgorithms, "MD5") ? "MD5" : "";
+ return WorkflowAlgorithmUtils.containsAlgorithm(algorithmResult.getRows(), "MD5") ? "MD5" : "";
}
- private String resolveSpecifiedAssistedQueryAlgorithm(final List<Map<String, Object>> encryptAlgorithms, final List<WorkflowIssue> issues, final String algorithmType) {
- if (!WorkflowAlgorithmUtils.containsAlgorithm(encryptAlgorithms, algorithmType)) {
+ private String resolveSpecifiedAssistedQueryAlgorithm(final WorkflowQueryResult algorithmResult, final List<WorkflowIssue> issues, final String algorithmType) {
+ if (algorithmResult.isAvailabilityConfirmed() && !WorkflowAlgorithmUtils.containsAlgorithm(algorithmResult.getRows(), algorithmType)) {
issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM,
String.format("Assisted-query algorithm `%s` is not visible from the current Proxy.", algorithmType),
"Choose an available assisted-query algorithm.", false, Map.of()));
@@ -143,13 +144,13 @@ public final class EncryptAlgorithmRecommendationService {
return algorithmType;
}
- private String resolveLikeQueryAlgorithm(final EncryptWorkflowRequest request, final List<Map<String, Object>> encryptAlgorithms,
+ private String resolveLikeQueryAlgorithm(final EncryptWorkflowRequest request, final WorkflowQueryResult algorithmResult,
final List<WorkflowIssue> issues) {
String actualAlgorithmType = WorkflowAlgorithmUtils.normalizeAlgorithmType(request.getOptions().getLikeQueryAlgorithmType());
if (!actualAlgorithmType.isEmpty()) {
- return resolveSpecifiedLikeQueryAlgorithm(encryptAlgorithms, issues, actualAlgorithmType);
+ return resolveSpecifiedLikeQueryAlgorithm(algorithmResult, issues, actualAlgorithmType);
}
- for (Map<String, Object> each : encryptAlgorithms) {
+ for (Map<String, Object> each : algorithmResult.getRows()) {
if (Boolean.TRUE.equals(each.get(EncryptFeatureDefinition.ALGORITHM_CAPABILITY_LIKE))) {
return WorkflowAlgorithmUtils.getAlgorithmType(each);
}
@@ -157,8 +158,8 @@ public final class EncryptAlgorithmRecommendationService {
return "";
}
- private String resolveSpecifiedLikeQueryAlgorithm(final List<Map<String, Object>> encryptAlgorithms, final List<WorkflowIssue> issues, final String algorithmType) {
- if (!WorkflowAlgorithmUtils.containsAlgorithm(encryptAlgorithms, algorithmType)) {
+ private String resolveSpecifiedLikeQueryAlgorithm(final WorkflowQueryResult algorithmResult, final List<WorkflowIssue> issues, final String algorithmType) {
+ if (algorithmResult.isAvailabilityConfirmed() && !WorkflowAlgorithmUtils.containsAlgorithm(algorithmResult.getRows(), algorithmType)) {
issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM,
String.format("LIKE-query algorithm `%s` is not visible from the current Proxy.", algorithmType),
"Choose an available LIKE-query algorithm.", false, Map.of()));
diff --git a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionService.java b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionService.java
index 0278cc8dcaa..94efd197885 100644
--- a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionService.java
+++ b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionService.java
@@ -21,6 +21,7 @@ import org.apache.shardingsphere.mcp.api.exception.MCPQueryFailedException;
import org.apache.shardingsphere.mcp.feature.encrypt.EncryptFeatureDefinition;
import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmPropertyRequirement;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowQueryResult;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowAlgorithmUtils;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowDistSQLQueryUtils;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
@@ -66,9 +67,10 @@ public final class EncryptRuleInspectionService {
* @param queryFacade query facade
* @return encrypt algorithms
*/
- public List<Map<String, Object>> queryEncryptAlgorithms(final MCPFeatureQueryFacade queryFacade) {
+ public WorkflowQueryResult queryEncryptAlgorithms(final MCPFeatureQueryFacade queryFacade) {
+ WorkflowQueryResult algorithmResult = queryAlgorithmRows(queryFacade);
List<Map<String, Object>> result = new LinkedList<>();
- for (Map<String, Object> each : queryAlgorithmRows(queryFacade)) {
+ for (Map<String, Object> each : algorithmResult.getRows()) {
String type = WorkflowAlgorithmUtils.getAlgorithmType(each);
Map<String, Boolean> capability = EncryptAlgorithmCatalog.findCapability(type);
List<AlgorithmPropertyRequirement> propertyTemplates = EncryptAlgorithmCatalog.findRequirements(EncryptFeatureDefinition.ALGORITHM_ROLE_PRIMARY, type);
@@ -83,15 +85,15 @@ public final class EncryptRuleInspectionService {
row.put("secret_properties", propertyTemplates.stream().filter(AlgorithmPropertyRequirement::isSecret).map(AlgorithmPropertyRequirement::getPropertyKey).toList());
result.add(row);
}
- return result;
+ return new WorkflowQueryResult(result, algorithmResult.isAvailabilityConfirmed());
}
- private List<Map<String, Object>> queryAlgorithmRows(final MCPFeatureQueryFacade queryFacade) {
+ private WorkflowQueryResult queryAlgorithmRows(final MCPFeatureQueryFacade queryFacade) {
try {
- return queryFacade.queryWithAnyDatabase("SHOW ENCRYPT ALGORITHM PLUGINS");
+ return WorkflowQueryResult.confirmed(queryFacade.queryWithAnyDatabase("SHOW ENCRYPT ALGORITHM PLUGINS"));
} catch (final MCPQueryFailedException ex) {
if (WorkflowDistSQLQueryUtils.isUnsupportedDistSQLQueryFailure(ex)) {
- return EncryptAlgorithmCatalog.getSupportedAlgorithmTypes().stream().map(each -> Map.<String, Object>of("type", each)).toList();
+ return WorkflowQueryResult.fallback(EncryptAlgorithmCatalog.getSupportedAlgorithmTypes().stream().map(each -> Map.<String, Object>of("type", each)).toList());
}
throw ex;
}
diff --git a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningService.java b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningService.java
index bbf14107d42..7c7bdcaaade 100644
--- a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningService.java
+++ b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningService.java
@@ -32,6 +32,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowFieldNames;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowQueryResult;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowPlanningSupport;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowRuleValueUtils;
@@ -200,7 +201,7 @@ public final class EncryptWorkflowPlanningService {
}
private void planAlgorithms(final MCPFeatureQueryFacade queryFacade, final EncryptWorkflowRequest request, final WorkflowContextSnapshot snapshot) {
- List<Map<String, Object>> encryptAlgorithms = ruleInspectionService.queryEncryptAlgorithms(queryFacade);
+ WorkflowQueryResult encryptAlgorithms = ruleInspectionService.queryEncryptAlgorithms(queryFacade);
List<AlgorithmCandidate> algorithmCandidates = algorithmRecommendationService.recommendEncryptAlgorithms(request, encryptAlgorithms, snapshot.getIssues());
snapshot.getAlgorithmCandidates().addAll(algorithmCandidates);
applyRecommendedAlgorithms(request, algorithmCandidates);
diff --git a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/resource/handler/EncryptAlgorithmsHandlerTest.java b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/resource/handler/EncryptAlgorithmsHandlerTest.java
index 4657b1b5f35..ed4a9b9145d 100644
--- a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/resource/handler/EncryptAlgorithmsHandlerTest.java
+++ b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/resource/handler/EncryptAlgorithmsHandlerTest.java
@@ -22,6 +22,7 @@ import org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceURIVaria
import org.apache.shardingsphere.mcp.feature.encrypt.tool.service.EncryptRuleInspectionService;
import org.apache.shardingsphere.mcp.support.MCPFeatureRequestContext;
import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowQueryResult;
import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
@@ -45,7 +46,7 @@ class EncryptAlgorithmsHandlerTest {
when(requestContext.getQueryFacade()).thenReturn(queryFacade);
try (
MockedConstruction<EncryptRuleInspectionService> mockedConstruction = mockConstruction(EncryptRuleInspectionService.class,
- (mock, context) -> when(mock.queryEncryptAlgorithms(queryFacade)).thenReturn(List.of(Map.of("type", "AES"))))) {
+ (mock, context) -> when(mock.queryEncryptAlgorithms(queryFacade)).thenReturn(WorkflowQueryResult.confirmed(List.of(Map.of("type", "AES")))))) {
MCPSuccessPayload actual = new EncryptAlgorithmsHandler().handle(requestContext, new MCPResourceURIVariables(Map.of()));
verify(mockedConstruction.constructed().getFirst()).queryEncryptAlgorithms(queryFacade);
assertThat(((Collection<?>) actual.toPayload().get("items")).size(), is(1));
diff --git a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptAlgorithmRecommendationServiceTest.java b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptAlgorithmRecommendationServiceTest.java
index 3003a52c801..a5d27126caf 100644
--- a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptAlgorithmRecommendationServiceTest.java
+++ b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptAlgorithmRecommendationServiceTest.java
@@ -21,6 +21,7 @@ import org.apache.shardingsphere.mcp.feature.encrypt.tool.model.EncryptWorkflowR
import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowQueryResult;
import org.junit.jupiter.api.Test;
import java.util.LinkedHashMap;
@@ -43,7 +44,7 @@ class EncryptAlgorithmRecommendationServiceTest {
request.getOptions().setRequiresDecrypt(true);
request.setAlgorithmType("AES");
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, List.of(createAlgorithmRow("AES", true, true, false)), issues);
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, confirmed(createAlgorithmRow("AES", true, true, false)), issues);
assertThat(actual.size(), is(1));
assertThat(actual.getFirst().getAlgorithmRole(), is("primary"));
assertThat(actual.getFirst().getAlgorithmType(), is("AES"));
@@ -56,7 +57,7 @@ class EncryptAlgorithmRecommendationServiceTest {
request.getOptions().setRequiresDecrypt(true);
request.setAlgorithmType("MD5");
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, List.of(createAlgorithmRow("MD5", false, true, false)), issues);
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, confirmed(createAlgorithmRow("MD5", false, true, false)), issues);
assertTrue(actual.isEmpty());
assertThat(issues.getFirst().getCode(), is(WorkflowIssueCode.ALGORITHM_CAPABILITY_CONFLICT));
}
@@ -66,7 +67,7 @@ class EncryptAlgorithmRecommendationServiceTest {
EncryptWorkflowRequest request = new EncryptWorkflowRequest();
request.setAlgorithmType("SM4");
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, List.of(createAlgorithmRow("AES", true, true, false)), issues);
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, confirmed(createAlgorithmRow("AES", true, true, false)), issues);
assertTrue(actual.isEmpty());
assertThat(issues.getFirst().getCode(), is(WorkflowIssueCode.ALGORITHM_NOT_FOUND));
}
@@ -74,7 +75,7 @@ class EncryptAlgorithmRecommendationServiceTest {
@Test
void assertRecommendEncryptAlgorithmsWithoutAvailableAlgorithm() {
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(new EncryptWorkflowRequest(), List.of(), issues);
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(new EncryptWorkflowRequest(), WorkflowQueryResult.confirmed(List.of()), issues);
assertTrue(actual.isEmpty());
assertTrue(issues.isEmpty());
}
@@ -83,7 +84,7 @@ class EncryptAlgorithmRecommendationServiceTest {
void assertRecommendEncryptAlgorithmsFallsBackToFirstAvailableAlgorithm() {
List<WorkflowIssue> issues = new LinkedList<>();
List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(
- new EncryptWorkflowRequest(), List.of(createAlgorithmRow("FPE", null, null, null)), issues);
+ new EncryptWorkflowRequest(), confirmed(createAlgorithmRow("FPE", null, null, null)), issues);
assertThat(actual.getFirst().getAlgorithmType(), is("FPE"));
assertTrue(issues.isEmpty());
}
@@ -93,7 +94,7 @@ class EncryptAlgorithmRecommendationServiceTest {
EncryptWorkflowRequest request = new EncryptWorkflowRequest();
request.getOptions().setRequiresEqualityFilter(true);
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, List.of(createAlgorithmRow("AES", true, true, false)), issues);
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, confirmed(createAlgorithmRow("AES", true, true, false)), issues);
assertThat(actual.size(), is(1));
assertThat(actual.getFirst().getAlgorithmType(), is("AES"));
assertThat(issues.getFirst().getCode(), is(WorkflowIssueCode.ALGORITHM_CAPABILITY_CONFLICT));
@@ -104,7 +105,7 @@ class EncryptAlgorithmRecommendationServiceTest {
EncryptWorkflowRequest request = new EncryptWorkflowRequest();
request.getOptions().setRequiresEqualityFilter(true);
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, List.of(
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, confirmed(
createAlgorithmRow("AES", true, true, false), createAlgorithmRow("MD5", false, true, false)), issues);
assertThat(actual.size(), is(2));
assertThat(actual.get(1).getAlgorithmRole(), is("assisted_query"));
@@ -118,7 +119,7 @@ class EncryptAlgorithmRecommendationServiceTest {
request.getOptions().setRequiresEqualityFilter(true);
request.getOptions().setAssistedQueryAlgorithmType("CUSTOM_HASH");
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, List.of(
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, confirmed(
createAlgorithmRow("AES", true, true, false), createAlgorithmRow("CUSTOM_HASH", null, true, false)), issues);
assertThat(actual.size(), is(2));
assertThat(actual.get(1).getAlgorithmType(), is("CUSTOM_HASH"));
@@ -131,7 +132,7 @@ class EncryptAlgorithmRecommendationServiceTest {
request.getOptions().setRequiresEqualityFilter(true);
request.getOptions().setAssistedQueryAlgorithmType("MD5");
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, List.of(createAlgorithmRow("AES", true, true, false)), issues);
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, confirmed(createAlgorithmRow("AES", true, true, false)), issues);
assertThat(actual.size(), is(1));
assertThat(issues.size(), is(1));
assertThat(issues.getFirst().getCode(), is(WorkflowIssueCode.ALGORITHM_NOT_FOUND));
@@ -142,7 +143,7 @@ class EncryptAlgorithmRecommendationServiceTest {
EncryptWorkflowRequest request = new EncryptWorkflowRequest();
request.setAlgorithmType("FPE");
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, List.of(createAlgorithmRow("FPE", null, null, null)), issues);
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, confirmed(createAlgorithmRow("FPE", null, null, null)), issues);
assertThat(actual.size(), is(1));
assertFalse(actual.getFirst().getRiskNotes().isEmpty());
assertTrue(issues.isEmpty());
@@ -153,7 +154,7 @@ class EncryptAlgorithmRecommendationServiceTest {
EncryptWorkflowRequest request = new EncryptWorkflowRequest();
request.getOptions().setRequiresLikeQuery(true);
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, List.of(
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, confirmed(
createAlgorithmRow("AES", true, true, false),
createAlgorithmRow("FPE", null, null, true)), issues);
assertThat(actual.size(), is(2));
@@ -169,7 +170,7 @@ class EncryptAlgorithmRecommendationServiceTest {
request.getOptions().setRequiresLikeQuery(true);
request.getOptions().setLikeQueryAlgorithmType("AES");
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, List.of(createAlgorithmRow("AES", true, true, false)), issues);
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, confirmed(createAlgorithmRow("AES", true, true, false)), issues);
assertThat(actual.size(), is(1));
assertThat(issues.getFirst().getCode(), is(WorkflowIssueCode.ALGORITHM_CAPABILITY_CONFLICT));
}
@@ -179,7 +180,7 @@ class EncryptAlgorithmRecommendationServiceTest {
EncryptWorkflowRequest request = new EncryptWorkflowRequest();
request.getOptions().setRequiresLikeQuery(true);
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, List.of(createAlgorithmRow("AES", true, true, false)), issues);
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, confirmed(createAlgorithmRow("AES", true, true, false)), issues);
assertThat(actual.size(), is(1));
assertThat(issues.getFirst().getCode(), is(WorkflowIssueCode.ALGORITHM_CAPABILITY_CONFLICT));
}
@@ -190,7 +191,7 @@ class EncryptAlgorithmRecommendationServiceTest {
request.getOptions().setRequiresLikeQuery(true);
request.getOptions().setLikeQueryAlgorithmType("FPE");
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, List.of(createAlgorithmRow("AES", true, true, false)), issues);
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, confirmed(createAlgorithmRow("AES", true, true, false)), issues);
assertThat(actual.size(), is(1));
assertThat(issues.size(), is(1));
assertThat(issues.getFirst().getCode(), is(WorkflowIssueCode.ALGORITHM_NOT_FOUND));
@@ -202,7 +203,7 @@ class EncryptAlgorithmRecommendationServiceTest {
request.getOptions().setRequiresLikeQuery(true);
request.getOptions().setLikeQueryAlgorithmType("FPE");
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, List.of(
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, confirmed(
createAlgorithmRow("AES", true, true, false), createAlgorithmRow("FPE", null, null, true)), issues);
assertThat(actual.size(), is(2));
assertThat(actual.get(1).getAlgorithmType(), is("FPE"));
@@ -210,6 +211,47 @@ class EncryptAlgorithmRecommendationServiceTest {
assertTrue(issues.isEmpty());
}
+ @Test
+ void assertRecommendEncryptAlgorithmsWithUnconfirmedAvailability() {
+ EncryptWorkflowRequest request = new EncryptWorkflowRequest();
+ request.setAlgorithmType("CUSTOM");
+ List<WorkflowIssue> issues = new LinkedList<>();
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(request, WorkflowQueryResult.fallback(List.of()), issues);
+ assertThat(actual.getFirst().getAlgorithmType(), is("CUSTOM"));
+ assertTrue(issues.isEmpty());
+ }
+
+ @Test
+ void assertRecommendAssistedQueryAlgorithmWithUnconfirmedAvailability() {
+ EncryptWorkflowRequest request = new EncryptWorkflowRequest();
+ request.setAlgorithmType("AES");
+ request.getOptions().setRequiresEqualityFilter(true);
+ request.getOptions().setAssistedQueryAlgorithmType("CUSTOM_HASH");
+ List<WorkflowIssue> issues = new LinkedList<>();
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(
+ request, WorkflowQueryResult.fallback(List.of(createAlgorithmRow("AES", true, true, false))), issues);
+ assertThat(actual.get(1).getAlgorithmType(), is("CUSTOM_HASH"));
+ assertTrue(issues.isEmpty());
+ }
+
+ @Test
+ void assertRecommendLikeQueryAlgorithmWithUnconfirmedAvailability() {
+ EncryptWorkflowRequest request = new EncryptWorkflowRequest();
+ request.setAlgorithmType("AES");
+ request.getOptions().setRequiresLikeQuery(true);
+ request.getOptions().setLikeQueryAlgorithmType("CUSTOM_LIKE");
+ List<WorkflowIssue> issues = new LinkedList<>();
+ List<AlgorithmCandidate> actual = service.recommendEncryptAlgorithms(
+ request, WorkflowQueryResult.fallback(List.of(createAlgorithmRow("AES", true, true, false))), issues);
+ assertThat(actual.get(1).getAlgorithmType(), is("CUSTOM_LIKE"));
+ assertTrue(issues.isEmpty());
+ }
+
+ @SafeVarargs
+ private WorkflowQueryResult confirmed(final Map<String, Object>... rows) {
+ return WorkflowQueryResult.confirmed(List.of(rows));
+ }
+
private Map<String, Object> createAlgorithmRow(final String type, final Boolean decrypt, final Boolean equivalentFilter, final Boolean like) {
Map<String, Object> result = new LinkedHashMap<>(4, 1F);
result.put("type", type);
diff --git a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionServiceTest.java b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionServiceTest.java
index 63c66a47b51..2554f2dd1f8 100644
--- a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionServiceTest.java
+++ b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionServiceTest.java
@@ -20,6 +20,7 @@ package org.apache.shardingsphere.mcp.feature.encrypt.tool.service;
import org.apache.shardingsphere.mcp.api.exception.MCPInvalidRequestException;
import org.apache.shardingsphere.mcp.api.exception.MCPQueryFailedException;
import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowQueryResult;
import org.junit.jupiter.api.Test;
import java.sql.SQLException;
@@ -102,18 +103,19 @@ class EncryptRuleInspectionServiceTest {
void assertQueryEncryptAlgorithms() {
MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
when(queryFacade.queryWithAnyDatabase("SHOW ENCRYPT ALGORITHM PLUGINS")).thenReturn(List.of(Map.of("type", "AES"), Map.of("type", "CUSTOM")));
- List<Map<String, Object>> actual = service.queryEncryptAlgorithms(queryFacade);
- assertTrue((Boolean) actual.getFirst().get("supports_decrypt"));
- assertTrue((Boolean) actual.getFirst().get("capability_confirmed"));
- assertThat(actual.getFirst().get("required_properties"), is(List.of("aes-key-value")));
- assertThat(actual.getFirst().get("optional_properties"), is(List.of("digest-algorithm-name")));
- assertThat(actual.getFirst().get("secret_properties"), is(List.of("aes-key-value")));
- List<?> propertyTemplates = (List<?>) actual.getFirst().get("property_templates");
+ WorkflowQueryResult actual = service.queryEncryptAlgorithms(queryFacade);
+ assertTrue((Boolean) actual.getRows().getFirst().get("supports_decrypt"));
+ assertTrue((Boolean) actual.getRows().getFirst().get("capability_confirmed"));
+ assertThat(actual.getRows().getFirst().get("required_properties"), is(List.of("aes-key-value")));
+ assertThat(actual.getRows().getFirst().get("optional_properties"), is(List.of("digest-algorithm-name")));
+ assertThat(actual.getRows().getFirst().get("secret_properties"), is(List.of("aes-key-value")));
+ List<?> propertyTemplates = (List<?>) actual.getRows().getFirst().get("property_templates");
assertThat(((Map<?, ?>) propertyTemplates.getFirst()).get("property_key"), is("aes-key-value"));
- assertThat(actual.get(1).get("type"), is("CUSTOM"));
- assertNull(actual.get(1).get("supports_like"));
- assertFalse((Boolean) actual.get(1).get("capability_confirmed"));
- assertThat(actual.get(1).get("property_templates"), is(List.of()));
+ assertThat(actual.getRows().get(1).get("type"), is("CUSTOM"));
+ assertNull(actual.getRows().get(1).get("supports_like"));
+ assertFalse((Boolean) actual.getRows().get(1).get("capability_confirmed"));
+ assertThat(actual.getRows().get(1).get("property_templates"), is(List.of()));
+ assertTrue(actual.isAvailabilityConfirmed());
}
@Test
@@ -121,9 +123,10 @@ class EncryptRuleInspectionServiceTest {
MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
when(queryFacade.queryWithAnyDatabase("SHOW ENCRYPT ALGORITHM PLUGINS"))
.thenThrow(new MCPQueryFailedException("syntax error near 'ENCRYPT ALGORITHM PLUGINS'", new SQLSyntaxErrorException("syntax error")));
- List<Map<String, Object>> actual = service.queryEncryptAlgorithms(queryFacade);
- assertThat(actual.getFirst().get("type"), is("AES"));
- assertThat(actual.getFirst().get("required_properties"), is(List.of("aes-key-value")));
+ WorkflowQueryResult actual = service.queryEncryptAlgorithms(queryFacade);
+ assertThat(actual.getRows().getFirst().get("type"), is("AES"));
+ assertThat(actual.getRows().getFirst().get("required_properties"), is(List.of("aes-key-value")));
+ assertFalse(actual.isAvailabilityConfirmed());
}
@Test
diff --git a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningServiceTest.java b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningServiceTest.java
index b0c0cf30cb2..3205f76020e 100644
--- a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningServiceTest.java
+++ b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningServiceTest.java
@@ -41,6 +41,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnaps
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowFieldNames;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowQueryResult;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowPlanPayloadBuilder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -155,7 +156,7 @@ class EncryptWorkflowPlanningServiceTest {
final String expectedOperationType, final String expectedFieldSemantics, final String expectedStatus) {
EncryptRuleInspectionService ruleInspectionService = mock(EncryptRuleInspectionService.class);
when(ruleInspectionService.queryEncryptRules(any(), any(), any())).thenReturn(ruleExists ? List.of(Map.of("logic_column", "phone")) : List.of());
- when(ruleInspectionService.queryEncryptAlgorithms(any())).thenReturn(List.of(Map.of("type", "AES", "supports_like", false)));
+ when(ruleInspectionService.queryEncryptAlgorithms(any())).thenReturn(WorkflowQueryResult.confirmed(List.of(Map.of("type", "AES", "supports_like", false))));
EncryptWorkflowRequest request = createNaturalLanguageRequest(naturalLanguageIntent);
request.getOptions().setCipherColumnName("phone_cipher");
request.getPrimaryAlgorithmProperties().put("aes-key-value", "123456");
@@ -173,7 +174,7 @@ class EncryptWorkflowPlanningServiceTest {
void assertPlanStopsOnBlockingAlgorithmIssue() {
EncryptRuleInspectionService ruleInspectionService = mock(EncryptRuleInspectionService.class);
when(ruleInspectionService.queryEncryptRules(any(), any(), any())).thenReturn(List.of());
- when(ruleInspectionService.queryEncryptAlgorithms(any())).thenReturn(List.of());
+ when(ruleInspectionService.queryEncryptAlgorithms(any())).thenReturn(WorkflowQueryResult.confirmed(List.of()));
EncryptAlgorithmRecommendationService algorithmRecommendationService = mock(EncryptAlgorithmRecommendationService.class);
when(algorithmRecommendationService.recommendEncryptAlgorithms(any(), any(), any())).thenAnswer(invocation -> {
List<WorkflowIssue> issues = invocation.getArgument(2);
@@ -213,9 +214,9 @@ class EncryptWorkflowPlanningServiceTest {
void assertPlanDoesNotApplyLikeQueryCandidateWithoutExplicitInput() {
EncryptRuleInspectionService ruleInspectionService = mock(EncryptRuleInspectionService.class);
when(ruleInspectionService.queryEncryptRules(any(), any(), any())).thenReturn(List.of());
- when(ruleInspectionService.queryEncryptAlgorithms(any())).thenReturn(List.of(
+ when(ruleInspectionService.queryEncryptAlgorithms(any())).thenReturn(WorkflowQueryResult.confirmed(List.of(
Map.of("type", "AES", "supports_like", false),
- Map.of("type", "FPE", "supports_like", true)));
+ Map.of("type", "FPE", "supports_like", true))));
EncryptAlgorithmPropertyTemplateService propertyTemplateService = mock(EncryptAlgorithmPropertyTemplateService.class);
when(propertyTemplateService.findRequirements(any(), any(), any())).thenReturn(List.of());
EncryptWorkflowRequest request = createRequest("create");
diff --git a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/completion/MaskAlgorithmCompletionHandler.java b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/completion/MaskAlgorithmCompletionHandler.java
index 757bbd9a242..8bb4d5ecfa2 100644
--- a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/completion/MaskAlgorithmCompletionHandler.java
+++ b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/completion/MaskAlgorithmCompletionHandler.java
@@ -52,7 +52,7 @@ public final class MaskAlgorithmCompletionHandler implements MCPCompletionHandle
@Override
public MCPCompletionHandlerResult complete(final MCPFeatureRequestContext handlerContext, final MCPCompletionRequest request) {
- return new MCPCompletionHandlerResult(ruleInspectionService.queryMaskAlgorithms(handlerContext.getQueryFacade()).stream()
+ return new MCPCompletionHandlerResult(ruleInspectionService.queryMaskAlgorithms(handlerContext.getQueryFacade()).getRows().stream()
.map(this::createAlgorithmCandidate).filter(each -> !each.getValue().isEmpty()).toList());
}
diff --git a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/resource/handler/MaskAlgorithmsHandler.java b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/resource/handler/MaskAlgorithmsHandler.java
index 94099e45fb7..d0197c4c37a 100644
--- a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/resource/handler/MaskAlgorithmsHandler.java
+++ b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/resource/handler/MaskAlgorithmsHandler.java
@@ -46,7 +46,7 @@ public final class MaskAlgorithmsHandler implements MCPResourceHandler<MCPFeatur
@Override
public MCPSuccessPayload handle(final MCPFeatureRequestContext requestContext, final MCPResourceURIVariables uriVariables) {
- return new MCPItemsPayload(ruleInspectionService.queryMaskAlgorithms(requestContext.getQueryFacade()),
+ return new MCPItemsPayload(ruleInspectionService.queryMaskAlgorithms(requestContext.getQueryFacade()).getRows(),
MCPResourceNavigationPayloadBuilder.create(MCPDescriptorCatalogIndex.getRequiredResourceDescriptor(getResourceUriTemplate()), uriVariables));
}
}
diff --git a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskAlgorithmRecommendationService.java b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskAlgorithmRecommendationService.java
index f2a83f723c5..49e9be072ba 100644
--- a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskAlgorithmRecommendationService.java
+++ b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskAlgorithmRecommendationService.java
@@ -22,6 +22,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.ClarifiedIntent;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowQueryResult;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowAlgorithmUtils;
@@ -39,15 +40,15 @@ public final class MaskAlgorithmRecommendationService {
*
* @param intent clarified intent
* @param request workflow request
- * @param maskAlgorithms mask algorithm plugins
+ * @param algorithmResult mask algorithm query result
* @param issues workflow issues
* @return selected candidates
*/
public List<AlgorithmCandidate> recommendMaskAlgorithms(final ClarifiedIntent intent, final WorkflowRequest request,
- final List<Map<String, Object>> maskAlgorithms, final List<WorkflowIssue> issues) {
+ final WorkflowQueryResult algorithmResult, final List<WorkflowIssue> issues) {
String actualAlgorithmType = WorkflowAlgorithmUtils.normalizeAlgorithmType(request.getAlgorithmType());
if (!actualAlgorithmType.isEmpty()) {
- if (WorkflowAlgorithmUtils.containsAlgorithm(maskAlgorithms, actualAlgorithmType)) {
+ if (!algorithmResult.isAvailabilityConfirmed() || WorkflowAlgorithmUtils.containsAlgorithm(algorithmResult.getRows(), actualAlgorithmType)) {
return List.of(AlgorithmCandidate.builder().algorithmRole("primary").algorithmType(actualAlgorithmType).recommendationScore(100)
.recommendationReason("User specified algorithm.").riskNotes("").build());
}
@@ -55,7 +56,7 @@ public final class MaskAlgorithmRecommendationService {
String.format("Mask algorithm `%s` is not visible from the current Proxy.", actualAlgorithmType), "Choose an available mask algorithm.", false, Map.of()));
return List.of();
}
- String recommendedType = resolveRecommendedAlgorithm(intent, request, maskAlgorithms);
+ String recommendedType = resolveRecommendedAlgorithm(intent, request, algorithmResult.getRows());
if (recommendedType.isEmpty()) {
issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", WorkflowLifecycle.STEP_SELECTING_ALGORITHM,
"No mask algorithm is available from the current Proxy.", "Install or register at least one visible mask algorithm.", false, Map.of()));
diff --git a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionService.java b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionService.java
index 209602485e8..79cbc641b60 100644
--- a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionService.java
+++ b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionService.java
@@ -20,6 +20,7 @@ package org.apache.shardingsphere.mcp.feature.mask.tool.service;
import org.apache.shardingsphere.mcp.api.exception.MCPQueryFailedException;
import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmPropertyRequirement;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowQueryResult;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowAlgorithmUtils;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowDistSQLQueryUtils;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
@@ -66,16 +67,17 @@ public final class MaskRuleInspectionService {
* @param queryFacade query facade
* @return mask algorithms
*/
- public List<Map<String, Object>> queryMaskAlgorithms(final MCPFeatureQueryFacade queryFacade) {
- return decorateMaskAlgorithms(queryAlgorithmRows(queryFacade));
+ public WorkflowQueryResult queryMaskAlgorithms(final MCPFeatureQueryFacade queryFacade) {
+ WorkflowQueryResult result = queryAlgorithmRows(queryFacade);
+ return new WorkflowQueryResult(decorateMaskAlgorithms(result.getRows()), result.isAvailabilityConfirmed());
}
- private List<Map<String, Object>> queryAlgorithmRows(final MCPFeatureQueryFacade queryFacade) {
+ private WorkflowQueryResult queryAlgorithmRows(final MCPFeatureQueryFacade queryFacade) {
try {
- return queryFacade.queryWithAnyDatabase("SHOW MASK ALGORITHM PLUGINS");
+ return WorkflowQueryResult.confirmed(queryFacade.queryWithAnyDatabase("SHOW MASK ALGORITHM PLUGINS"));
} catch (final MCPQueryFailedException ex) {
if (WorkflowDistSQLQueryUtils.isUnsupportedDistSQLQueryFailure(ex)) {
- return propertyTemplateService.getSupportedAlgorithmTypes().stream().map(each -> Map.<String, Object>of("type", each)).toList();
+ return WorkflowQueryResult.fallback(propertyTemplateService.getSupportedAlgorithmTypes().stream().map(each -> Map.<String, Object>of("type", each)).toList());
}
throw ex;
}
diff --git a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningService.java b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningService.java
index 74d5f1ce359..6d4f9ee8983 100644
--- a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningService.java
+++ b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningService.java
@@ -29,6 +29,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnaps
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowQueryResult;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowPlanningSupport;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowRuleValueUtils;
@@ -170,7 +171,7 @@ public final class MaskWorkflowPlanningService {
}
private void planAlgorithms(final MCPFeatureQueryFacade queryFacade, final ClarifiedIntent clarifiedIntent, final WorkflowRequest request, final WorkflowContextSnapshot snapshot) {
- List<Map<String, Object>> maskAlgorithms = ruleInspectionService.queryMaskAlgorithms(queryFacade);
+ WorkflowQueryResult maskAlgorithms = ruleInspectionService.queryMaskAlgorithms(queryFacade);
List<AlgorithmCandidate> algorithmCandidates = algorithmRecommendationService.recommendMaskAlgorithms(clarifiedIntent, request, maskAlgorithms, snapshot.getIssues());
snapshot.getAlgorithmCandidates().addAll(algorithmCandidates);
if (!algorithmCandidates.isEmpty()) {
diff --git a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/resource/handler/MaskAlgorithmsHandlerTest.java b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/resource/handler/MaskAlgorithmsHandlerTest.java
index 757e1f844e4..6a5b6efeb7c 100644
--- a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/resource/handler/MaskAlgorithmsHandlerTest.java
+++ b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/resource/handler/MaskAlgorithmsHandlerTest.java
@@ -22,6 +22,7 @@ import org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceURIVaria
import org.apache.shardingsphere.mcp.feature.mask.tool.service.MaskRuleInspectionService;
import org.apache.shardingsphere.mcp.support.MCPFeatureRequestContext;
import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowQueryResult;
import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
@@ -45,7 +46,7 @@ class MaskAlgorithmsHandlerTest {
when(requestContext.getQueryFacade()).thenReturn(queryFacade);
try (
MockedConstruction<MaskRuleInspectionService> mockedConstruction = mockConstruction(MaskRuleInspectionService.class,
- (mock, context) -> when(mock.queryMaskAlgorithms(queryFacade)).thenReturn(List.of(Map.of("type", "MD5"))))) {
+ (mock, context) -> when(mock.queryMaskAlgorithms(queryFacade)).thenReturn(WorkflowQueryResult.confirmed(List.of(Map.of("type", "MD5")))))) {
MCPSuccessPayload actual = new MaskAlgorithmsHandler().handle(requestContext, new MCPResourceURIVariables(Map.of()));
verify(mockedConstruction.constructed().getFirst()).queryMaskAlgorithms(queryFacade);
assertThat(((Collection<?>) actual.toPayload().get("items")).size(), is(1));
diff --git a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskAlgorithmRecommendationServiceTest.java b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskAlgorithmRecommendationServiceTest.java
index 6972988f77a..46b210c716a 100644
--- a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskAlgorithmRecommendationServiceTest.java
+++ b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskAlgorithmRecommendationServiceTest.java
@@ -21,6 +21,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
import org.apache.shardingsphere.mcp.support.workflow.model.ClarifiedIntent;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowQueryResult;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
import org.junit.jupiter.api.Test;
@@ -41,7 +42,7 @@ class MaskAlgorithmRecommendationServiceTest {
WorkflowRequest request = new WorkflowRequest();
request.setAlgorithmType("MD5");
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendMaskAlgorithms(new ClarifiedIntent(), request, List.of(Map.of("type", "MD5")), issues);
+ List<AlgorithmCandidate> actual = service.recommendMaskAlgorithms(new ClarifiedIntent(), request, confirmed(Map.of("type", "MD5")), issues);
assertThat(actual.size(), is(1));
assertThat(actual.get(0).getAlgorithmType(), is("MD5"));
assertTrue(issues.isEmpty());
@@ -52,7 +53,7 @@ class MaskAlgorithmRecommendationServiceTest {
WorkflowRequest request = new WorkflowRequest();
request.setAlgorithmType("CUSTOM");
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendMaskAlgorithms(new ClarifiedIntent(), request, List.of(Map.of("type", "MD5")), issues);
+ List<AlgorithmCandidate> actual = service.recommendMaskAlgorithms(new ClarifiedIntent(), request, confirmed(Map.of("type", "MD5")), issues);
assertTrue(actual.isEmpty());
assertThat(issues.get(0).getCode(), is(WorkflowIssueCode.ALGORITHM_NOT_FOUND));
}
@@ -63,7 +64,7 @@ class MaskAlgorithmRecommendationServiceTest {
clarifiedIntent.setFieldSemantics("phone");
WorkflowRequest request = new WorkflowRequest();
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendMaskAlgorithms(clarifiedIntent, request, List.of(
+ List<AlgorithmCandidate> actual = service.recommendMaskAlgorithms(clarifiedIntent, request, confirmed(
Map.of("type", "MASK_FROM_X_TO_Y"),
Map.of("type", "KEEP_FIRST_N_LAST_M")), issues);
assertThat(actual.size(), is(1));
@@ -76,7 +77,7 @@ class MaskAlgorithmRecommendationServiceTest {
WorkflowRequest request = new WorkflowRequest();
request.setNaturalLanguageIntent("保留前3后4位");
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendMaskAlgorithms(clarifiedIntent, request, List.of(
+ List<AlgorithmCandidate> actual = service.recommendMaskAlgorithms(clarifiedIntent, request, confirmed(
Map.of("type", "MASK_FROM_X_TO_Y"),
Map.of("type", "KEEP_FIRST_N_LAST_M")), issues);
assertThat(actual.size(), is(1));
@@ -86,8 +87,24 @@ class MaskAlgorithmRecommendationServiceTest {
@Test
void assertRecommendMaskAlgorithmsWithoutAvailableAlgorithms() {
List<WorkflowIssue> issues = new LinkedList<>();
- List<AlgorithmCandidate> actual = service.recommendMaskAlgorithms(new ClarifiedIntent(), new WorkflowRequest(), List.of(), issues);
+ List<AlgorithmCandidate> actual = service.recommendMaskAlgorithms(
+ new ClarifiedIntent(), new WorkflowRequest(), WorkflowQueryResult.confirmed(List.of()), issues);
assertTrue(actual.isEmpty());
assertThat(issues.get(0).getCode(), is(WorkflowIssueCode.ALGORITHM_NOT_FOUND));
}
+
+ @Test
+ void assertRecommendMaskAlgorithmsWithUnconfirmedAvailability() {
+ WorkflowRequest request = new WorkflowRequest();
+ request.setAlgorithmType("CUSTOM");
+ List<WorkflowIssue> issues = new LinkedList<>();
+ List<AlgorithmCandidate> actual = service.recommendMaskAlgorithms(new ClarifiedIntent(), request, WorkflowQueryResult.fallback(List.of()), issues);
+ assertThat(actual.getFirst().getAlgorithmType(), is("CUSTOM"));
+ assertTrue(issues.isEmpty());
+ }
+
+ @SafeVarargs
+ private WorkflowQueryResult confirmed(final Map<String, Object>... rows) {
+ return WorkflowQueryResult.confirmed(List.of(rows));
+ }
}
diff --git a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionServiceTest.java b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionServiceTest.java
index bb7698be5d8..5a6251016c5 100644
--- a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionServiceTest.java
+++ b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionServiceTest.java
@@ -20,6 +20,7 @@ package org.apache.shardingsphere.mcp.feature.mask.tool.service;
import org.apache.shardingsphere.mcp.api.exception.MCPInvalidRequestException;
import org.apache.shardingsphere.mcp.api.exception.MCPQueryFailedException;
import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowQueryResult;
import org.junit.jupiter.api.Test;
import java.sql.SQLException;
@@ -29,6 +30,7 @@ import java.util.Map;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
@@ -102,14 +104,15 @@ class MaskRuleInspectionServiceTest {
void assertQueryMaskAlgorithms() {
MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
when(queryFacade.queryWithAnyDatabase("SHOW MASK ALGORITHM PLUGINS")).thenReturn(List.of(Map.of("type", "MASK_FROM_X_TO_Y"), Map.of("type", "CUSTOM")));
- List<Map<String, Object>> actual = service.queryMaskAlgorithms(queryFacade);
- assertThat(actual.getFirst().get("type"), is("MASK_FROM_X_TO_Y"));
- assertThat(actual.getFirst().get("required_properties"), is(List.of("from-x", "to-y", "replace-char")));
- assertThat(actual.getFirst().get("optional_properties"), is(List.of()));
- List<?> propertyTemplates = (List<?>) actual.get(0).get("property_templates");
+ WorkflowQueryResult actual = service.queryMaskAlgorithms(queryFacade);
+ assertThat(actual.getRows().getFirst().get("type"), is("MASK_FROM_X_TO_Y"));
+ assertThat(actual.getRows().getFirst().get("required_properties"), is(List.of("from-x", "to-y", "replace-char")));
+ assertThat(actual.getRows().getFirst().get("optional_properties"), is(List.of()));
+ List<?> propertyTemplates = (List<?>) actual.getRows().getFirst().get("property_templates");
assertThat(((Map<?, ?>) propertyTemplates.getFirst()).get("property_key"), is("from-x"));
- assertThat(actual.get(1).get("type"), is("CUSTOM"));
- assertThat(actual.get(1).get("property_templates"), is(List.of()));
+ assertThat(actual.getRows().get(1).get("type"), is("CUSTOM"));
+ assertThat(actual.getRows().get(1).get("property_templates"), is(List.of()));
+ assertTrue(actual.isAvailabilityConfirmed());
}
@Test
@@ -117,9 +120,10 @@ class MaskRuleInspectionServiceTest {
MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
when(queryFacade.queryWithAnyDatabase("SHOW MASK ALGORITHM PLUGINS"))
.thenThrow(new MCPQueryFailedException("syntax error near 'MASK ALGORITHM PLUGINS'", new SQLSyntaxErrorException("syntax error")));
- List<Map<String, Object>> actual = service.queryMaskAlgorithms(queryFacade);
- assertThat(actual.getFirst().get("type"), is("KEEP_FIRST_N_LAST_M"));
- assertThat(actual.getFirst().get("required_properties"), is(List.of("first-n", "last-m", "replace-char")));
+ WorkflowQueryResult actual = service.queryMaskAlgorithms(queryFacade);
+ assertThat(actual.getRows().getFirst().get("type"), is("KEEP_FIRST_N_LAST_M"));
+ assertThat(actual.getRows().getFirst().get("required_properties"), is(List.of("first-n", "last-m", "replace-char")));
+ assertFalse(actual.isAvailabilityConfirmed());
}
@Test
diff --git a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningServiceTest.java b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningServiceTest.java
index 363de26165c..2f34f988112 100644
--- a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningServiceTest.java
+++ b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningServiceTest.java
@@ -41,6 +41,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnaps
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowFieldNames;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowQueryResult;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowPlanPayloadBuilder;
import org.junit.jupiter.api.AfterEach;
@@ -140,7 +141,7 @@ class MaskWorkflowPlanningServiceTest {
void assertPlanRejectsAddingColumnToExistingTableRule() {
MaskRuleInspectionService ruleInspectionService = mock(MaskRuleInspectionService.class);
when(ruleInspectionService.queryMaskRules(any(), any(), any())).thenReturn(List.of(Map.of("column", "phone", "algorithm_type", "MD5")));
- when(ruleInspectionService.queryMaskAlgorithms(any())).thenReturn(List.of());
+ when(ruleInspectionService.queryMaskAlgorithms(any())).thenReturn(WorkflowQueryResult.confirmed(List.of()));
WorkflowRequest request = createRequest("create");
request.setColumn("amount");
request.getPrimaryAlgorithmProperties().put("from-x", "1");
@@ -200,7 +201,7 @@ class MaskWorkflowPlanningServiceTest {
final String expectedStatus) {
MaskRuleInspectionService ruleInspectionService = mock(MaskRuleInspectionService.class);
when(ruleInspectionService.queryMaskRules(any(), any(), any())).thenReturn(ruleExists ? List.of(Map.of("column", "phone")) : List.of());
- when(ruleInspectionService.queryMaskAlgorithms(any())).thenReturn(List.of(Map.of("type", "MASK_FROM_X_TO_Y")));
+ when(ruleInspectionService.queryMaskAlgorithms(any())).thenReturn(WorkflowQueryResult.confirmed(List.of(Map.of("type", "MASK_FROM_X_TO_Y"))));
WorkflowContextSnapshot actual = createService(ruleInspectionService, new MaskAlgorithmRecommendationService(), new MaskAlgorithmPropertyTemplateService(),
new MaskRuleDistSQLPlanningService()).plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(),
createNaturalLanguageRequest(naturalLanguageIntent));
@@ -216,7 +217,7 @@ class MaskWorkflowPlanningServiceTest {
void assertPlanStopsOnBlockingAlgorithmIssue() {
MaskRuleInspectionService ruleInspectionService = mock(MaskRuleInspectionService.class);
when(ruleInspectionService.queryMaskRules(any(), any(), any())).thenReturn(List.of());
- when(ruleInspectionService.queryMaskAlgorithms(any())).thenReturn(List.of());
+ when(ruleInspectionService.queryMaskAlgorithms(any())).thenReturn(WorkflowQueryResult.confirmed(List.of()));
MaskAlgorithmRecommendationService algorithmRecommendationService = mock(MaskAlgorithmRecommendationService.class);
when(algorithmRecommendationService.recommendMaskAlgorithms(any(), any(), any(), any())).thenAnswer(invocation -> {
List<WorkflowIssue> issues = invocation.getArgument(3);
diff --git a/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-shadow.yaml b/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-shadow.yaml
index b7065ccd822..f43c029a495 100644
--- a/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-shadow.yaml
+++ b/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-shadow.yaml
@@ -125,7 +125,7 @@ resourceTemplates:
required: true
scope: logical-database
resourceKind: list
- objectScope: rule
+ objectScope: algorithm
feature: shadow
relatedTools:
- database_gateway_plan_default_shadow_algorithm
diff --git a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/ShadowDescriptorContractTest.java b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/ShadowDescriptorContractTest.java
index c5075e95974..f602a6d866d 100644
--- a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/ShadowDescriptorContractTest.java
+++ b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/ShadowDescriptorContractTest.java
@@ -21,6 +21,7 @@ import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolDescriptor;
import org.apache.shardingsphere.mcp.api.capability.completion.MCPCompletionTargetDescriptor;
import org.apache.shardingsphere.mcp.support.descriptor.MCPDescriptorCatalog;
import org.apache.shardingsphere.mcp.support.descriptor.MCPDescriptorCatalogLoader;
+import org.apache.shardingsphere.mcp.support.descriptor.ShardingSphereMCPResourceMetadata;
import org.junit.jupiter.api.Test;
import java.util.List;
@@ -61,10 +62,20 @@ class ShadowDescriptorContractTest {
assertThat(((Map<String, Object>) structuredIntentProperties.get("algorithm_type")).get("enum"), is(List.of("SQL_HINT")));
}
+ @Test
+ void assertAlgorithmResourceScope() {
+ MCPDescriptorCatalog catalog = MCPDescriptorCatalogLoader.load();
+ assertThat(findResourceMetadata(catalog, "shardingsphere://features/shadow/databases/{database}/algorithms").getObjectScope(), is("algorithm"));
+ }
+
private MCPToolDescriptor findTool(final MCPDescriptorCatalog catalog, final String toolName) {
return catalog.getProtocolDescriptors().getToolDescriptors().stream().filter(each -> toolName.equals(each.getName())).findFirst().orElseThrow();
}
+ private ShardingSphereMCPResourceMetadata findResourceMetadata(final MCPDescriptorCatalog catalog, final String uriTemplate) {
+ return catalog.getShardingSphereDescriptors().getResourceMetadata().stream().filter(each -> uriTemplate.equals(each.getUriTemplate())).findFirst().orElseThrow();
+ }
+
private void assertCompletionTargetArguments(final MCPDescriptorCatalog catalog, final String promptName, final String... expectedArguments) {
MCPCompletionTargetDescriptor actual = catalog.getShardingSphereDescriptors().getCompletionTargetDescriptors().stream()
.filter(each -> "prompt".equals(each.getReferenceType()) && promptName.equals(each.getReference())).findFirst().orElseThrow();
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationService.java
index 306c3a340c1..5b68ec393d2 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationService.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationService.java
@@ -266,7 +266,7 @@ public final class ShardingWorkflowValidationService implements MCPWorkflowRunti
}
private List<Map<String, Object>> queryCleanupRows(final MCPFeatureQueryFacade queryFacade, final ShardingWorkflowRequest request) {
- switch (request.getComponentType().toLowerCase().replace('_', '-')) {
+ switch (request.getComponentType().toLowerCase(Locale.ENGLISH).replace('_', '-')) {
case "algorithm":
return inspectionService.queryAlgorithms(queryFacade, request.getDatabase());
case "key-generator":
diff --git a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-sharding.yaml b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-sharding.yaml
index e0d6817ca9e..d12185c12ca 100644
--- a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-sharding.yaml
+++ b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-sharding.yaml
@@ -49,16 +49,18 @@ resourceTemplates:
title: Sharding Algorithms
description: "List configured sharding algorithms for one logical database."
mimeType: application/json
- shardingSphereMetadata: &databaseListMetadata
- uriVariables:
- - name: database
- title: Logical Database
- description: "ShardingSphere logical database."
- required: true
- scope: logical-database
- resourceKind: list
- objectScope: rule
- feature: sharding
+ shardingSphereMetadata:
+ <<: &databaseListMetadata
+ uriVariables:
+ - name: database
+ title: Logical Database
+ description: "ShardingSphere logical database."
+ required: true
+ scope: logical-database
+ resourceKind: list
+ objectScope: rule
+ feature: sharding
+ objectScope: algorithm
relatedTools:
- database_gateway_plan_sharding_table_rule
- database_gateway_plan_sharding_default_strategy
@@ -237,6 +239,7 @@ resourceTemplates:
mimeType: application/json
shardingSphereMetadata:
<<: *databaseListMetadata
+ objectScope: algorithm
relatedTools:
- database_gateway_plan_sharding_rule_component_cleanup
- uriTemplate: shardingsphere://features/sharding/databases/{database}/unused-key-generators
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/ShardingDescriptorContractTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/ShardingDescriptorContractTest.java
index 2ea4410c8c9..12ef1a2670e 100644
--- a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/ShardingDescriptorContractTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/ShardingDescriptorContractTest.java
@@ -22,6 +22,7 @@ import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolDescriptor;
import org.apache.shardingsphere.mcp.api.capability.completion.MCPCompletionTargetDescriptor;
import org.apache.shardingsphere.mcp.support.descriptor.MCPDescriptorCatalog;
import org.apache.shardingsphere.mcp.support.descriptor.MCPDescriptorCatalogLoader;
+import org.apache.shardingsphere.mcp.support.descriptor.ShardingSphereMCPResourceMetadata;
import org.junit.jupiter.api.Test;
import java.util.List;
@@ -83,6 +84,13 @@ class ShardingDescriptorContractTest {
assertOperationTypeEnum(findTool(catalog, ShardingFeatureDefinition.PLAN_COMPONENT_CLEANUP_TOOL_NAME), List.of("drop"));
}
+ @Test
+ void assertAlgorithmResourceScopes() {
+ MCPDescriptorCatalog catalog = MCPDescriptorCatalogLoader.load();
+ assertThat(findResourceMetadata(catalog, "shardingsphere://features/sharding/databases/{database}/algorithms").getObjectScope(), is("algorithm"));
+ assertThat(findResourceMetadata(catalog, "shardingsphere://features/sharding/databases/{database}/unused-algorithms").getObjectScope(), is("algorithm"));
+ }
+
private MCPPromptDescriptor findPrompt(final MCPDescriptorCatalog catalog, final String promptName) {
return catalog.getProtocolDescriptors().getPromptDescriptors().stream().filter(each -> promptName.equals(each.getName())).findFirst().orElseThrow();
}
@@ -91,6 +99,10 @@ class ShardingDescriptorContractTest {
return catalog.getProtocolDescriptors().getToolDescriptors().stream().filter(each -> toolName.equals(each.getName())).findFirst().orElseThrow();
}
+ private ShardingSphereMCPResourceMetadata findResourceMetadata(final MCPDescriptorCatalog catalog, final String uriTemplate) {
+ return catalog.getShardingSphereDescriptors().getResourceMetadata().stream().filter(each -> uriTemplate.equals(each.getUriTemplate())).findFirst().orElseThrow();
+ }
+
private void assertOperationTypeEnum(final MCPToolDescriptor descriptor, final List<String> expectedValues) {
Map<?, ?> properties = (Map<?, ?>) descriptor.getInputSchema().get("properties");
Map<?, ?> operationType = (Map<?, ?>) properties.get("operation_type");
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/ShardingWorkflowToolHandlersTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/ShardingWorkflowToolHandlersTest.java
index 1b4200eb63a..bc616644fc1 100644
--- a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/ShardingWorkflowToolHandlersTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/ShardingWorkflowToolHandlersTest.java
@@ -38,6 +38,7 @@ import org.mockito.MockedConstruction;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -169,8 +170,14 @@ class ShardingWorkflowToolHandlersTest {
"shardingsphere://features/sharding/databases/logic_db/unused-algorithms",
"shardingsphere://features/sharding/databases/logic_db/unused-key-generators",
"shardingsphere://features/sharding/databases/logic_db/unused-auditors")));
- for (String each : actualResourceUris) {
- assertThat(findResourceKind(actualResourcesToRead, each), is("rule"));
+ for (Entry<String, String> entry : Map.of(
+ "shardingsphere://features/sharding/databases/logic_db/algorithms", "algorithm",
+ "shardingsphere://features/sharding/databases/logic_db/key-generators", "rule",
+ "shardingsphere://features/sharding/databases/logic_db/auditors", "rule",
+ "shardingsphere://features/sharding/databases/logic_db/unused-algorithms", "algorithm",
+ "shardingsphere://features/sharding/databases/logic_db/unused-key-generators", "rule",
+ "shardingsphere://features/sharding/databases/logic_db/unused-auditors", "rule").entrySet()) {
+ assertThat(findResourceKind(actualResourcesToRead, entry.getKey()), is(entry.getValue()));
}
}
}
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogIndex.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogIndex.java
index e45d567ca0f..404abb99b5e 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogIndex.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogIndex.java
@@ -23,12 +23,14 @@ import org.apache.shardingsphere.mcp.api.capability.completion.MCPCompletionTarg
import org.apache.shardingsphere.mcp.api.capability.prompt.MCPPromptDescriptor;
import org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceDescriptor;
import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolDescriptor;
+import org.apache.shardingsphere.mcp.support.resource.MCPUriTemplate;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -45,6 +47,8 @@ public final class MCPDescriptorCatalogIndex {
private static final Map<String, ShardingSphereMCPResourceMetadata> SHARDINGSPHERE_RESOURCE_METADATA = createShardingSphereResourceMetadata();
+ private static final List<Entry<MCPUriTemplate, ShardingSphereMCPResourceMetadata>> RESOURCE_METADATA_TEMPLATES = createResourceMetadataTemplates();
+
private static final Map<String, MCPToolDescriptor> TOOL_DESCRIPTORS = createToolDescriptors();
private static final Collection<MCPPromptDescriptor> PROMPT_DESCRIPTORS = CATALOG.getProtocolDescriptors().getPromptDescriptors();
@@ -78,6 +82,10 @@ public final class MCPDescriptorCatalogIndex {
.collect(Collectors.toMap(ShardingSphereMCPResourceMetadata::getUriTemplate, each -> each));
}
+ private static List<Entry<MCPUriTemplate, ShardingSphereMCPResourceMetadata>> createResourceMetadataTemplates() {
+ return CATALOG.getShardingSphereDescriptors().getResourceMetadata().stream().map(each -> Map.entry(new MCPUriTemplate(each.getUriTemplate()), each)).toList();
+ }
+
private static Map<String, MCPToolDescriptor> createToolDescriptors() {
return CATALOG.getProtocolDescriptors().getToolDescriptors().stream()
.collect(Collectors.toMap(MCPToolDescriptor::getName, each -> each, (a, b) -> b, () -> new LinkedHashMap<>(CATALOG.getProtocolDescriptors().getToolDescriptors().size(), 1F)));
@@ -185,6 +193,24 @@ public final class MCPDescriptorCatalogIndex {
() -> new IllegalStateException(String.format("ShardingSphere MCP resource metadata descriptor is required for `%s`.", uriTemplate)));
}
+ /**
+ * Resolve resource kind from a descriptor URI template or expanded resource URI.
+ *
+ * @param uri resource URI or URI template
+ * @return descriptor resource kind, or generic resource kind when the URI is unknown
+ */
+ public static String resolveResourceKind(final String uri) {
+ ShardingSphereMCPResourceMetadata metadata = SHARDINGSPHERE_RESOURCE_METADATA.get(uri);
+ if (null == metadata) {
+ metadata = RESOURCE_METADATA_TEMPLATES.stream().filter(each -> each.getKey().parse(uri).isPresent())
+ .map(Entry::getValue).findFirst().orElse(null);
+ }
+ if (null == metadata) {
+ return "resource";
+ }
+ return null == metadata.getObjectScope() ? metadata.getResourceKind() : metadata.getObjectScope();
+ }
+
/**
* Get tool descriptors.
*
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogYamlSwapper.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogYamlSwapper.java
index 421696266cf..b2e82cb1a7f 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogYamlSwapper.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogYamlSwapper.java
@@ -194,7 +194,7 @@ final class MCPDescriptorCatalogYamlSwapper {
}
private static MCPToolRuntimeDescriptor swapToolRuntimeDescriptor(final String toolName, final YamlMCPToolRuntimeDescriptor yamlRuntime) {
- return new MCPToolRuntimeDescriptor(toolName, null == yamlRuntime.getWorkflowRole() ? "" : yamlRuntime.getWorkflowRole(), emptyIfNull(yamlRuntime.getSideEffectScope()));
+ return new MCPToolRuntimeDescriptor(toolName, null == yamlRuntime.getWorkflowRole() ? "" : yamlRuntime.getWorkflowRole());
}
private static <T> Collection<T> emptyIfNull(final Collection<T> values) {
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorYamlKeyValidator.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorYamlKeyValidator.java
index 3a095ca42cc..0e1030eb666 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorYamlKeyValidator.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorYamlKeyValidator.java
@@ -48,7 +48,7 @@ final class MCPDescriptorYamlKeyValidator {
private static final Collection<String> REQUIRED_TOOL_ANNOTATION_HINT_KEYS = Set.of("readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint");
- private static final Collection<String> TOOL_RUNTIME_KEYS = Set.of("workflowRole", "sideEffectScope");
+ private static final Collection<String> TOOL_RUNTIME_KEYS = Set.of("workflowRole");
private static final Collection<String> PROMPT_KEYS = Set.of("name", "title", "description", "arguments", "binding", "meta");
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPGuidancePayloadBuilder.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPGuidancePayloadBuilder.java
index 47dc80d6be4..57d6de6e0a6 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPGuidancePayloadBuilder.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPGuidancePayloadBuilder.java
@@ -19,9 +19,11 @@ package org.apache.shardingsphere.mcp.support.descriptor;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
+import org.apache.shardingsphere.mcp.support.protocol.MCPModelFacingPayloadContract;
import org.apache.shardingsphere.mcp.support.protocol.MCPResponseMode;
import org.apache.shardingsphere.mcp.support.security.MCPClientSafetyPolicy;
+import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -76,18 +78,11 @@ final class MCPGuidancePayloadBuilder {
static List<Map<String, Object>> createNextActionContract() {
return List.of(
- createNextActionContractEntry("resource_read", List.of("order", "type", "title", "resource_uri"),
- List.of("reason", "depends_on"), "Read an MCP resource before choosing another call."),
- createNextActionContractEntry("tool_call", List.of("order", "type", "title", "tool_name", "arguments"),
- List.of("reason", "depends_on"), "Call another MCP tool with server-suggested arguments."),
- createNextActionContractEntry("completion",
- List.of("order", "type", "title", "ref", "argument"),
- List.of("context", "missing_context_arguments", "resume_ref", "resume_arguments", "reason", "depends_on"),
- "Use MCP completion for one argument before retrying."),
- createNextActionContractEntry("ask_user", List.of("order", "type", "title", "question"),
- List.of("required_inputs", "depends_on"), "Ask the user for missing input."),
- createNextActionContractEntry("terminal", List.of("order", "type", "title"),
- List.of("reason", "depends_on"), "Stop the current MCP flow and report the result."));
+ createNextActionContractEntry("resource_read", "Read an MCP resource before choosing another call."),
+ createNextActionContractEntry("tool_call", "Call another MCP tool with server-suggested arguments."),
+ createNextActionContractEntry("completion", "Use MCP completion for one argument before retrying."),
+ createNextActionContractEntry("ask_user", "Ask the user for missing input."),
+ createNextActionContractEntry("terminal", "Stop the current MCP flow and report the result."));
}
static List<Map<String, Object>> createCommonFlows() {
@@ -135,7 +130,9 @@ final class MCPGuidancePayloadBuilder {
return result;
}
- private static Map<String, Object> createNextActionContractEntry(final String actionType, final List<String> requiredFields, final List<String> optionalFields, final String description) {
+ private static Map<String, Object> createNextActionContractEntry(final String actionType, final String description) {
+ Collection<String> requiredFields = MCPModelFacingPayloadContract.getNextActionRequiredFields(actionType);
+ List<String> optionalFields = MCPModelFacingPayloadContract.getNextActionAllowedFields(actionType).stream().filter(each -> !requiredFields.contains(each)).toList();
Map<String, Object> result = new LinkedHashMap<>(4, 1F);
result.put("type", actionType);
result.put("required_fields", requiredFields);
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationPayloadBuilder.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationPayloadBuilder.java
index 905407d78ae..a98b935b5c0 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationPayloadBuilder.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationPayloadBuilder.java
@@ -57,7 +57,7 @@ public final class MCPResourceNavigationPayloadBuilder {
public static Map<String, Object> create(final MCPResourceDescriptor descriptor, final MCPResourceURIVariables uriVariables, final String parentUriTemplate) {
Map<String, Object> result = new LinkedHashMap<>(2, 1F);
new MCPUriTemplate(descriptor.getUriTemplate()).expandIfComplete(uriVariables)
- .ifPresent(optional -> result.put(MCPPayloadFieldNames.SELF_RESOURCE, MCPResourceHintUtils.create(optional, resolveResourceKind(optional), "inspect_self",
+ .ifPresent(optional -> result.put(MCPPayloadFieldNames.SELF_RESOURCE, MCPResourceHintUtils.create(optional, MCPDescriptorCatalogIndex.resolveResourceKind(optional), "inspect_self",
"Read this resource.", MCPPayloadFieldNames.SELF_RESOURCE)));
if (!parentUriTemplate.isEmpty()) {
Optional<String> parentUri = new MCPUriTemplate(parentUriTemplate).expandIfComplete(uriVariables);
@@ -67,22 +67,6 @@ public final class MCPResourceNavigationPayloadBuilder {
}
private static Map<String, Object> createParentResourceHint(final String uri) {
- return MCPResourceHintUtils.create(uri, resolveResourceKind(uri), "inspect_parent", "Read the parent resource.", MCPPayloadFieldNames.PARENT_RESOURCE);
- }
-
- private static String resolveResourceKind(final String uri) {
- if (uri.contains("/rules")) {
- return "rule";
- }
- if (uri.contains("/algorithms")) {
- return "algorithm";
- }
- if (uri.contains("/columns")) {
- return "column";
- }
- if (uri.contains("/indexes")) {
- return "index";
- }
- return "resource";
+ return MCPResourceHintUtils.create(uri, MCPDescriptorCatalogIndex.resolveResourceKind(uri), "inspect_parent", "Read the parent resource.", MCPPayloadFieldNames.PARENT_RESOURCE);
}
}
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorCatalogValidator.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorCatalogValidator.java
index 7f71bb1009d..cb38478ca7c 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorCatalogValidator.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorCatalogValidator.java
@@ -53,9 +53,6 @@ public final class MCPToolDescriptorCatalogValidator {
private static final Collection<String> SECRET_WORKFLOW_OUTPUT_FIELDS = List.of("masked_property_preview", "secret_reference_summary");
- private static final Collection<String> SUPPORTED_SIDE_EFFECT_SCOPES = Set.of(
- "physical-data", "physical-structure", "privilege-metadata", "rule-metadata", "transaction-state");
-
/**
* Validate tool descriptors in one descriptor catalog.
*
@@ -77,7 +74,7 @@ public final class MCPToolDescriptorCatalogValidator {
MCPToolInputSchemaValidator.validate(each);
MCPToolOutputSchemaValidator.validate(each);
validateToolDescriptorContract(each, runtimes.get(each.getName()));
- validateDestructiveToolDescriptor(each, runtimes.get(each.getName()));
+ validateDestructiveToolDescriptor(each);
validateNonDestructiveExecutionMode(each);
validateRelatedResourceUris(each, resourceIdentifiers, shardingSphereResourceIdentifiers);
}
@@ -88,10 +85,6 @@ public final class MCPToolDescriptorCatalogValidator {
for (MCPToolRuntimeDescriptor each : runtimeDescriptors) {
ShardingSpherePreconditions.checkState(each.getWorkflowRole().isBlank() || "plan".equals(each.getWorkflowRole()),
() -> new IllegalStateException(String.format("Tool `%s` runtime workflowRole `%s` is unsupported.", each.getToolName(), each.getWorkflowRole())));
- for (String eachScope : each.getSideEffectScope()) {
- ShardingSpherePreconditions.checkState(SUPPORTED_SIDE_EFFECT_SCOPES.contains(eachScope),
- () -> new IllegalStateException(String.format("Tool `%s` runtime sideEffectScope `%s` is unsupported.", each.getToolName(), eachScope)));
- }
}
}
@@ -293,7 +286,7 @@ public final class MCPToolDescriptorCatalogValidator {
() -> new IllegalStateException(String.format("Planning tool `%s` annotations.idempotentHint must be false.", descriptor.getName())));
}
- private static void validateDestructiveToolDescriptor(final MCPToolDescriptor descriptor, final MCPToolRuntimeDescriptor runtimeDescriptor) {
+ private static void validateDestructiveToolDescriptor(final MCPToolDescriptor descriptor) {
if (!descriptor.getAnnotations().isDestructiveHint()) {
return;
}
@@ -306,8 +299,6 @@ public final class MCPToolDescriptorCatalogValidator {
() -> new IllegalStateException(String.format("Destructive tool `%s` execution_mode must allow preview.", descriptor.getName())));
ShardingSpherePreconditions.checkState(!executionModes.contains("auto-execute"),
() -> new IllegalStateException(String.format("Destructive tool `%s` execution_mode must not expose auto-execute.", descriptor.getName())));
- ShardingSpherePreconditions.checkState(null != runtimeDescriptor && !runtimeDescriptor.getSideEffectScope().isEmpty(),
- () -> new IllegalStateException(String.format("Destructive tool `%s` must declare sideEffectScope in internal runtime.", descriptor.getName())));
}
private static void validateNonDestructiveExecutionMode(final MCPToolDescriptor descriptor) {
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolRuntimeDescriptor.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolRuntimeDescriptor.java
index d2c9139fd4b..646bef62dbd 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolRuntimeDescriptor.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolRuntimeDescriptor.java
@@ -20,8 +20,6 @@ package org.apache.shardingsphere.mcp.support.descriptor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
-import java.util.Collection;
-
/**
* MCP tool runtime descriptor.
*/
@@ -32,6 +30,4 @@ public final class MCPToolRuntimeDescriptor {
private final String toolName;
private final String workflowRole;
-
- private final Collection<String> sideEffectScope;
}
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/YamlMCPToolRuntimeDescriptor.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/YamlMCPToolRuntimeDescriptor.java
index f3b500a9d9d..e25d0f14950 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/YamlMCPToolRuntimeDescriptor.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/YamlMCPToolRuntimeDescriptor.java
@@ -20,9 +20,6 @@ package org.apache.shardingsphere.mcp.support.descriptor.yaml;
import lombok.Getter;
import lombok.Setter;
-import java.util.Collection;
-import java.util.LinkedList;
-
/**
* YAML MCP tool runtime descriptor.
*/
@@ -31,6 +28,4 @@ import java.util.LinkedList;
public final class YamlMCPToolRuntimeDescriptor {
private String workflowRole;
-
- private Collection<String> sideEffectScope = new LinkedList<>();
}
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/protocol/MCPModelFacingPayloadContract.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/protocol/MCPModelFacingPayloadContract.java
index 795495755f4..a333e620e0d 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/protocol/MCPModelFacingPayloadContract.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/protocol/MCPModelFacingPayloadContract.java
@@ -37,18 +37,18 @@ public final class MCPModelFacingPayloadContract {
"recommended_recovery", "suggested_next_action", "approved_by_user", "requires_user_approval", "approval_required", "user_overrides");
private static final Map<String, Collection<String>> NEXT_ACTION_REQUIRED_FIELDS = Map.of(
- "resource_read", Set.of("order", "type", "title", "resource_uri"),
- "tool_call", Set.of("order", "type", "title", "tool_name", "arguments"),
- "completion", Set.of("order", "type", "title", "ref", "argument"),
- "ask_user", Set.of("order", "type", "title", "question"),
- "terminal", Set.of("order", "type", "title"));
+ "resource_read", List.of("order", "type", "title", "resource_uri"),
+ "tool_call", List.of("order", "type", "title", "tool_name", "arguments"),
+ "completion", List.of("order", "type", "title", "ref", "argument"),
+ "ask_user", List.of("order", "type", "title", "question"),
+ "terminal", List.of("order", "type", "title"));
private static final Map<String, Collection<String>> NEXT_ACTION_ALLOWED_FIELDS = Map.of(
- "resource_read", Set.of("order", "type", "title", "resource_uri", "reason", "depends_on"),
- "tool_call", Set.of("order", "type", "title", "tool_name", "arguments", "reason", "depends_on"),
- "completion", Set.of("order", "type", "title", "ref", "argument", "context", "missing_context_arguments", "resume_ref", "resume_arguments", "reason", "depends_on"),
- "ask_user", Set.of("order", "type", "title", "question", "required_inputs", "depends_on"),
- "terminal", Set.of("order", "type", "title", "reason", "depends_on"));
+ "resource_read", List.of("order", "type", "title", "resource_uri", "reason", "depends_on"),
+ "tool_call", List.of("order", "type", "title", "tool_name", "arguments", "reason", "depends_on"),
+ "completion", List.of("order", "type", "title", "ref", "argument", "context", "missing_context_arguments", "resume_ref", "resume_arguments", "reason", "depends_on"),
+ "ask_user", List.of("order", "type", "title", "question", "required_inputs", "depends_on"),
+ "terminal", List.of("order", "type", "title", "reason", "depends_on"));
private static final Collection<String> NEXT_ACTION_SCHEMA_ALLOWED_FIELDS = createNextActionSchemaAllowedFields();
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/SecretReferenceValue.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/SecretReferenceValue.java
index a52c3ec45c6..949b6ac3a17 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/SecretReferenceValue.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/SecretReferenceValue.java
@@ -22,6 +22,7 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.LinkedHashMap;
+import java.util.Locale;
import java.util.Map;
/**
@@ -96,7 +97,7 @@ public final class SecretReferenceValue {
}
private static String normalizeForManualPlaceholder(final String value) {
- String result = normalize(value).toUpperCase().replaceAll("[^A-Z0-9]+", "_");
+ String result = normalize(value).toUpperCase(Locale.ENGLISH).replaceAll("[^A-Z0-9]+", "_");
return result.isEmpty() ? "VALUE" : result;
}
}
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowContextSnapshot.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowContextSnapshot.java
index 678e1384a53..a0fd5a39b75 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowContextSnapshot.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/model/WorkflowContextSnapshot.java
@@ -88,7 +88,7 @@ public final class WorkflowContextSnapshot {
result.setWorkflowKind(workflowKind);
result.setSessionId(sessionId);
result.setStatus(status);
- result.setUpdateTime(copyInstant(updateTime));
+ result.setUpdateTime(updateTime);
result.setRequest(null == request ? null : request.copy());
result.setClarifiedIntent(copyClarifiedIntent(clarifiedIntent));
result.setFeatureData(null == featureData ? null : featureData.copy());
@@ -102,10 +102,6 @@ public final class WorkflowContextSnapshot {
return result;
}
- private static Instant copyInstant(final Instant original) {
- return null == original ? null : Instant.ofEpochMilli(original.toEpochMilli());
- }
-
private static ClarifiedIntent copyClarifiedIntent(final ClarifiedIntent original) {
if (null == original) {
return null;
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowArtifactMaskUtils.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowArtifactMaskUtils.java
index 07e80f290e1..2c9ca530416 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowArtifactMaskUtils.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowArtifactMaskUtils.java
@@ -28,6 +28,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowFieldNames;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -178,7 +179,7 @@ public final class WorkflowArtifactMaskUtils {
return true;
}
}
- String actualPropertyKey = propertyKey.toLowerCase();
+ String actualPropertyKey = propertyKey.toLowerCase(Locale.ENGLISH);
return actualPropertyKey.contains("password") || actualPropertyKey.contains("secret") || actualPropertyKey.contains("token")
|| "key".equals(actualPropertyKey) || actualPropertyKey.contains("-key") || actualPropertyKey.contains("key-")
|| actualPropertyKey.contains("_key") || actualPropertyKey.contains("key_");
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidanceResourceHintProvider.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidanceResourceHintProvider.java
index c4c7c0e691a..f561da78a92 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidanceResourceHintProvider.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidanceResourceHintProvider.java
@@ -21,7 +21,6 @@ import org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceDescript
import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolDescriptor;
import org.apache.shardingsphere.mcp.support.descriptor.MCPDescriptorCatalogIndex;
import org.apache.shardingsphere.mcp.support.descriptor.MCPShardingSphereMetadataKeys;
-import org.apache.shardingsphere.mcp.support.descriptor.ShardingSphereMCPResourceMetadata;
import org.apache.shardingsphere.mcp.support.protocol.MCPPayloadFieldNames;
import org.apache.shardingsphere.mcp.support.protocol.MCPResourceHintUtils;
import org.apache.shardingsphere.mcp.support.resource.MCPUriPathSegmentUtils;
@@ -71,7 +70,7 @@ public final class WorkflowGuidanceResourceHintProvider {
for (Object each : uriTemplates) {
String uriTemplate = String.valueOf(each);
findResourceDescriptor(uriTemplate).ifPresent(resourceDescriptor -> createConcreteResourceUri(uriTemplate, request)
- .ifPresent(uri -> addResourceHint(resourcesToRead, uri, resolveResourceKind(uriTemplate), resolveDescriptorResourceAction(resourceDescriptor),
+ .ifPresent(uri -> addResourceHint(resourcesToRead, uri, MCPDescriptorCatalogIndex.resolveResourceKind(uriTemplate), resolveDescriptorResourceAction(resourceDescriptor),
resourceDescriptor.getDescription())));
}
}
@@ -101,12 +100,6 @@ public final class WorkflowGuidanceResourceHintProvider {
return value.isEmpty() ? Optional.empty() : Optional.of(uriTemplate.replace(placeholder, MCPUriPathSegmentUtils.encodePathSegment(value)));
}
- private String resolveResourceKind(final String uriTemplate) {
- ShardingSphereMCPResourceMetadata metadata = MCPDescriptorCatalogIndex.getRequiredShardingSphereResourceMetadata(uriTemplate);
- String result = metadata.getObjectScope();
- return null == result ? metadata.getResourceKind() : result;
- }
-
private String resolveDescriptorResourceAction(final MCPResourceDescriptor descriptor) {
return descriptor.isTemplated() ? "inspect_detail" : "read_first";
}
diff --git a/mcp/support/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-support.yaml b/mcp/support/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-support.yaml
index 93c8bbae007..377dad59350 100644
--- a/mcp/support/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-support.yaml
+++ b/mcp/support/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-support.yaml
@@ -371,9 +371,6 @@ tools:
destructiveHint: true
idempotentHint: false
openWorldHint: true
- runtime:
- sideEffectScope:
- - rule-metadata
meta:
org.apache.shardingsphere/follow-up-tools:
- database_gateway_validate_workflow
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogIndexTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogIndexTest.java
index 0a9dd08e22f..3a48e2ff0f5 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogIndexTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogIndexTest.java
@@ -58,6 +58,16 @@ class MCPDescriptorCatalogIndexTest {
assertThat(actual.getResourceKind(), is("detail"));
}
+ @Test
+ void assertResolveResourceKind() {
+ assertThat(MCPDescriptorCatalogIndex.resolveResourceKind("shardingsphere://workflows/plan-1"), is("workflow-plan"));
+ }
+
+ @Test
+ void assertResolveUnknownResourceKind() {
+ assertThat(MCPDescriptorCatalogIndex.resolveResourceKind("shardingsphere://unknown"), is("resource"));
+ }
+
@Test
void assertGetToolDescriptors() {
Collection<MCPToolDescriptor> actualDescriptors = MCPDescriptorCatalogIndex.getToolDescriptors();
@@ -87,12 +97,6 @@ class MCPDescriptorCatalogIndexTest {
assertThat(actual.getTemplateResource(), is("META-INF/shardingsphere-mcp/prompts/inspect-metadata.md"));
}
- @Test
- void assertFindToolRuntimeDescriptor() {
- assertTrue(MCPDescriptorCatalogIndex.findToolRuntimeDescriptor("database_gateway_apply_workflow")
- .filter(optional -> optional.getWorkflowRole().isEmpty() && optional.getSideEffectScope().contains("rule-metadata")).isPresent());
- }
-
@Test
void assertFindToolRuntimeDescriptorWithoutRuntimeMetadata() {
assertFalse(MCPDescriptorCatalogIndex.findToolRuntimeDescriptor("database_gateway_validate_workflow").isPresent());
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidatorTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidatorTest.java
index 69b482cf57e..a2ca8d8db96 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidatorTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidatorTest.java
@@ -125,7 +125,7 @@ class MCPDescriptorCatalogValidatorTest {
String toolName = "database_gateway_test_plan_rule";
assertValidationError(createToolRuntimeCatalog(List.of(), List.of(createToolDescriptor(toolName, createPlanningToolAnnotations(),
createWorkflowPlanOutputSchema(), createPlanningToolMetaWithoutWorkflowKind())),
- List.of(new MCPToolRuntimeDescriptor(toolName, "plan", List.of()))),
+ List.of(new MCPToolRuntimeDescriptor(toolName, "plan"))),
"Tool `database_gateway_test_plan_rule` metadata must declare `org.apache.shardingsphere/workflow-kind`.");
}
@@ -136,8 +136,8 @@ class MCPDescriptorCatalogValidatorTest {
MCPToolDescriptor secondTool = createToolDescriptor("database_gateway_test_plan_rule_again", createPlanningToolAnnotations(),
createWorkflowPlanOutputSchema(), createPlanningToolMeta("test.rule"));
assertValidationError(createToolRuntimeCatalog(List.of(), List.of(firstTool, secondTool), List.of(
- new MCPToolRuntimeDescriptor(firstTool.getName(), "plan", List.of()),
- new MCPToolRuntimeDescriptor(secondTool.getName(), "plan", List.of()))),
+ new MCPToolRuntimeDescriptor(firstTool.getName(), "plan"),
+ new MCPToolRuntimeDescriptor(secondTool.getName(), "plan"))),
"Planning workflow kind `test.rule` is used by both `database_gateway_test_plan_rule` and `database_gateway_test_plan_rule_again`.");
}
@@ -145,24 +145,16 @@ class MCPDescriptorCatalogValidatorTest {
void assertValidateRejectsUnsupportedWorkflowRole() {
MCPToolDescriptor descriptor = createToolDescriptor(createReadOnlyToolAnnotations());
assertValidationError(createToolRuntimeCatalog(List.of(), List.of(descriptor),
- List.of(new MCPToolRuntimeDescriptor(descriptor.getName(), "execute", List.of()))),
+ List.of(new MCPToolRuntimeDescriptor(descriptor.getName(), "execute"))),
"Tool `database_gateway_test_tool` runtime workflowRole `execute` is unsupported.");
}
- @Test
- void assertValidateRejectsUnsupportedSideEffectScope() {
- MCPToolDescriptor descriptor = createToolDescriptor(createReadOnlyToolAnnotations());
- assertValidationError(createToolRuntimeCatalog(List.of(), List.of(descriptor),
- List.of(new MCPToolRuntimeDescriptor(descriptor.getName(), "", List.of("database")))),
- "Tool `database_gateway_test_tool` runtime sideEffectScope `database` is unsupported.");
- }
-
@Test
void assertValidateRejectsPlanningToolReadOnlyHint() {
String toolName = "database_gateway_test_plan_rule";
assertValidationError(createToolRuntimeCatalog(List.of(), List.of(createToolDescriptor(toolName, MCPToolAnnotations.builder()
.title("Test Tool").readOnlyHint(true).destructiveHint(false).idempotentHint(false).openWorldHint(true).build(),
- createWorkflowPlanOutputSchema(), createPlanningToolMeta("test.rule"))), List.of(new MCPToolRuntimeDescriptor(toolName, "plan", List.of()))),
+ createWorkflowPlanOutputSchema(), createPlanningToolMeta("test.rule"))), List.of(new MCPToolRuntimeDescriptor(toolName, "plan"))),
"Planning tool `database_gateway_test_plan_rule` annotations.readOnlyHint must be false.");
}
@@ -171,7 +163,7 @@ class MCPDescriptorCatalogValidatorTest {
String toolName = "database_gateway_test_plan_rule";
assertValidationError(createToolRuntimeCatalog(List.of(), List.of(createToolDescriptor(toolName, MCPToolAnnotations.builder()
.title("Test Tool").readOnlyHint(false).destructiveHint(true).idempotentHint(false).openWorldHint(true).build(),
- createWorkflowPlanOutputSchema(), createPlanningToolMeta("test.rule"))), List.of(new MCPToolRuntimeDescriptor(toolName, "plan", List.of()))),
+ createWorkflowPlanOutputSchema(), createPlanningToolMeta("test.rule"))), List.of(new MCPToolRuntimeDescriptor(toolName, "plan"))),
"Planning tool `database_gateway_test_plan_rule` annotations.destructiveHint must be false.");
}
@@ -180,40 +172,34 @@ class MCPDescriptorCatalogValidatorTest {
String toolName = "database_gateway_test_plan_rule";
assertValidationError(createToolRuntimeCatalog(List.of(), List.of(createToolDescriptor(toolName, MCPToolAnnotations.builder()
.title("Test Tool").readOnlyHint(false).destructiveHint(false).idempotentHint(true).openWorldHint(true).build(),
- createWorkflowPlanOutputSchema(), createPlanningToolMeta("test.rule"))), List.of(new MCPToolRuntimeDescriptor(toolName, "plan", List.of()))),
+ createWorkflowPlanOutputSchema(), createPlanningToolMeta("test.rule"))), List.of(new MCPToolRuntimeDescriptor(toolName, "plan"))),
"Planning tool `database_gateway_test_plan_rule` annotations.idempotentHint must be false.");
}
@Test
void assertValidateRejectsDestructiveToolWithoutExecutionMode() {
- assertDestructiveToolValidationError(false, List.of(), false, List.of("rule-metadata"),
+ assertDestructiveToolValidationError(false, List.of(), false,
"Destructive tool `database_gateway_test_tool` must declare execution_mode.");
}
@Test
void assertValidateRejectsDestructiveToolWithOptionalExecutionMode() {
- assertDestructiveToolValidationError(true, List.of("preview"), false, List.of("rule-metadata"),
+ assertDestructiveToolValidationError(true, List.of("preview"), false,
"Destructive tool `database_gateway_test_tool` execution_mode must be required.");
}
@Test
void assertValidateRejectsDestructiveToolWithoutPreview() {
- assertDestructiveToolValidationError(true, List.of("review-then-execute"), true, List.of("rule-metadata"),
+ assertDestructiveToolValidationError(true, List.of("review-then-execute"), true,
"Destructive tool `database_gateway_test_tool` execution_mode must allow preview.");
}
@Test
void assertValidateRejectsDestructiveToolWithAutoExecute() {
- assertDestructiveToolValidationError(true, List.of("preview", "auto-execute"), true, List.of("rule-metadata"),
+ assertDestructiveToolValidationError(true, List.of("preview", "auto-execute"), true,
"Destructive tool `database_gateway_test_tool` execution_mode must not expose auto-execute.");
}
- @Test
- void assertValidateRejectsDestructiveToolWithoutSideEffectScope() {
- assertDestructiveToolValidationError(true, List.of("preview"), true, List.of(),
- "Destructive tool `database_gateway_test_tool` must declare sideEffectScope in internal runtime.");
- }
-
@ParameterizedTest(name = "{0}")
@ValueSource(strings = {"preview", "auto-execute"})
void assertValidateRejectsNonDestructiveExecutionMode(final String executionMode) {
@@ -334,10 +320,9 @@ class MCPDescriptorCatalogValidatorTest {
}
private void assertDestructiveToolValidationError(final boolean executionModePresent, final List<String> executionModes, final boolean executionModeRequired,
- final List<String> sideEffectScope, final String expectedMessage) {
+ final String expectedMessage) {
MCPToolDescriptor descriptor = createExecutionModeTool(true, executionModePresent, executionModes, executionModeRequired);
- assertValidationError(createToolRuntimeCatalog(List.of(), List.of(descriptor),
- List.of(new MCPToolRuntimeDescriptor(descriptor.getName(), "", sideEffectScope))), expectedMessage);
+ assertValidationError(createToolRuntimeCatalog(List.of(), List.of(descriptor), List.of()), expectedMessage);
}
private MCPToolDescriptor createExecutionModeTool(final boolean destructive, final boolean executionModePresent, final List<String> executionModes,
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationPayloadBuilderTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationPayloadBuilderTest.java
index 372a37fc6cc..4c5ca14404f 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationPayloadBuilderTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationPayloadBuilderTest.java
@@ -64,11 +64,8 @@ class MCPResourceNavigationPayloadBuilderTest {
private static Stream<Arguments> resourceKinds() {
return Stream.of(
- Arguments.of("rule", "shardingsphere://databases/{database}/rules", "rule"),
- Arguments.of("algorithm", "shardingsphere://databases/{database}/algorithms", "algorithm"),
- Arguments.of("column", "shardingsphere://databases/{database}/columns", "column"),
- Arguments.of("index", "shardingsphere://databases/{database}/indexes", "index"),
- Arguments.of("resource", "shardingsphere://databases/{database}", "resource"));
+ Arguments.of("workflow", "shardingsphere://workflows/{database}", "workflow-plan"),
+ Arguments.of("resource", "shardingsphere://unknown/{database}", "resource"));
}
private MCPResourceDescriptor createDescriptor(final String uriTemplate) {
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPShardingSphereDescriptorCatalogTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPShardingSphereDescriptorCatalogTest.java
index d10d1b0f26b..d29eea723c2 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPShardingSphereDescriptorCatalogTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPShardingSphereDescriptorCatalogTest.java
@@ -60,7 +60,7 @@ class MCPShardingSphereDescriptorCatalogTest {
@Test
void assertGetToolRuntimeDescriptors() {
- Collection<MCPToolRuntimeDescriptor> toolRuntimeDescriptors = List.of(new MCPToolRuntimeDescriptor("database_gateway_test_tool", "plan", List.of("metadata")));
+ Collection<MCPToolRuntimeDescriptor> toolRuntimeDescriptors = List.of(new MCPToolRuntimeDescriptor("database_gateway_test_tool", "plan"));
MCPShardingSphereDescriptorCatalog actual = new MCPShardingSphereDescriptorCatalog(List.of(), List.of(), List.of(), List.of(), toolRuntimeDescriptors);
assertThat(actual.getToolRuntimeDescriptors(), is(toolRuntimeDescriptors));
}
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/protocol/MCPModelFacingPayloadContractTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/protocol/MCPModelFacingPayloadContractTest.java
index 2107a16ea63..1f329e4993e 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/protocol/MCPModelFacingPayloadContractTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/protocol/MCPModelFacingPayloadContractTest.java
@@ -20,7 +20,7 @@ package org.apache.shardingsphere.mcp.support.protocol;
import org.junit.jupiter.api.Test;
import java.util.Collection;
-import java.util.Set;
+import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -41,13 +41,13 @@ class MCPModelFacingPayloadContractTest {
@Test
void assertGetNextActionRequiredFields() {
- assertThat(MCPModelFacingPayloadContract.getNextActionRequiredFields("tool_call"), is(Set.of("order", "type", "title", "tool_name", "arguments")));
+ assertThat(MCPModelFacingPayloadContract.getNextActionRequiredFields("tool_call"), is(List.of("order", "type", "title", "tool_name", "arguments")));
}
@Test
void assertGetNextActionAllowedFields() {
assertThat(MCPModelFacingPayloadContract.getNextActionAllowedFields("completion"),
- is(Set.of("order", "type", "title", "ref", "argument", "context", "missing_context_arguments", "resume_ref", "resume_arguments", "reason", "depends_on")));
+ is(List.of("order", "type", "title", "ref", "argument", "context", "missing_context_arguments", "resume_ref", "resume_arguments", "reason", "depends_on")));
}
@Test
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/DockerImageHttpRuntime.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/DockerImageHttpRuntime.java
index 26982f99b4a..2acd33151dd 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/DockerImageHttpRuntime.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/DockerImageHttpRuntime.java
@@ -26,13 +26,14 @@ import org.apache.shardingsphere.test.e2e.mcp.support.transport.client.MCPIntera
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.net.ServerSocket;
import java.net.URI;
import java.net.http.HttpClient;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.List;
+import java.util.OptionalInt;
+import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
@@ -48,6 +49,8 @@ public final class DockerImageHttpRuntime implements AutoCloseable {
private static final long STARTUP_POLL_INTERVAL_MILLIS = 250L;
+ private static final long PORT_QUERY_TIMEOUT_SECONDS = 5L;
+
private final String imageName;
private final Path configFile;
@@ -60,6 +63,8 @@ public final class DockerImageHttpRuntime implements AutoCloseable {
private int httpPort;
+ private String containerName;
+
/**
* Open an HTTP interaction client after the Docker image becomes ready.
*
@@ -92,6 +97,8 @@ public final class DockerImageHttpRuntime implements AutoCloseable {
} finally {
process = null;
outputCollector = null;
+ httpPort = 0;
+ containerName = null;
outputMessages.clear();
}
}
@@ -100,14 +107,19 @@ public final class DockerImageHttpRuntime implements AutoCloseable {
if (null != process) {
return;
}
- httpPort = allocatePort();
- process = new ProcessBuilder(createDockerCommand(httpPort)).redirectErrorStream(true).start();
- outputCollector = startOutputCollector(process);
+ containerName = "shardingsphere-mcp-e2e-" + UUID.randomUUID();
+ try {
+ process = new ProcessBuilder(createDockerCommand(imageName, configFile, containerName)).redirectErrorStream(true).start();
+ outputCollector = startOutputCollector(process);
+ } catch (final IOException ex) {
+ containerName = null;
+ throw ex;
+ }
}
- private List<String> createDockerCommand(final int httpPort) {
- List<String> result = new LinkedList<>(List.of("docker", "run", "--rm", "--add-host=host.docker.internal:host-gateway", "-p",
- "127.0.0.1:" + httpPort + ":18088", "-e", "SHARDINGSPHERE_MCP_TRANSPORT=http"));
+ static List<String> createDockerCommand(final String imageName, final Path configFile, final String containerName) {
+ List<String> result = new LinkedList<>(List.of("docker", "run", "--rm", "--name", containerName,
+ "--add-host=host.docker.internal:host-gateway", "-p", "127.0.0.1::18088", "-e", "SHARDINGSPHERE_MCP_TRANSPORT=http"));
if (null != configFile) {
result.addAll(List.of("-v", configFile.toAbsolutePath().normalize() + ":" + CONTAINER_CONFIG_FILE + ":ro", "-e",
"SHARDINGSPHERE_MCP_CONFIG=" + CONTAINER_CONFIG_FILE));
@@ -116,12 +128,6 @@ public final class DockerImageHttpRuntime implements AutoCloseable {
return result;
}
- private int allocatePort() throws IOException {
- try (ServerSocket serverSocket = new ServerSocket(0)) {
- return serverSocket.getLocalPort();
- }
- }
-
private Thread startOutputCollector(final Process process) {
Thread result = new Thread(() -> collectOutput(process), "mcp-container-http-e2e");
result.setDaemon(true);
@@ -143,6 +149,16 @@ public final class DockerImageHttpRuntime implements AutoCloseable {
if (!process.isAlive()) {
return ReadinessResult.failed(null);
}
+ if (0 == httpPort) {
+ try {
+ httpPort = queryPublishedPort();
+ } catch (final IOException ex) {
+ return ReadinessResult.retry(new IllegalStateException("Docker MCP HTTP published port is not available yet.", ex));
+ }
+ if (0 == httpPort) {
+ return ReadinessResult.retry(new IllegalStateException("Docker MCP HTTP published port has not been assigned yet."));
+ }
+ }
MCPHttpInteractionClient result = new MCPHttpInteractionClient(URI.create("http://127.0.0.1:" + httpPort + "/mcp"), HttpClient.newHttpClient());
try {
result.open();
@@ -153,6 +169,37 @@ public final class DockerImageHttpRuntime implements AutoCloseable {
}
}
+ private int queryPublishedPort() throws IOException, InterruptedException {
+ Process portQuery = new ProcessBuilder("docker", "port", containerName, "18088/tcp").redirectErrorStream(true).start();
+ try {
+ if (!portQuery.waitFor(PORT_QUERY_TIMEOUT_SECONDS, TimeUnit.SECONDS) || 0 != portQuery.exitValue()) {
+ return 0;
+ }
+ return parsePublishedPort(new String(portQuery.getInputStream().readAllBytes(), StandardCharsets.UTF_8)).orElse(0);
+ } finally {
+ if (portQuery.isAlive()) {
+ portQuery.destroyForcibly();
+ }
+ }
+ }
+
+ static OptionalInt parsePublishedPort(final String output) {
+ for (String each : output.lines().toList()) {
+ int separatorIndex = each.lastIndexOf(':');
+ if (separatorIndex < 0 || separatorIndex == each.length() - 1) {
+ continue;
+ }
+ try {
+ int result = Integer.parseInt(each.substring(separatorIndex + 1).trim());
+ if (result > 0 && result <= 65535) {
+ return OptionalInt.of(result);
+ }
+ } catch (final NumberFormatException ignored) {
+ }
+ }
+ return OptionalInt.empty();
+ }
+
private IllegalStateException createStartupFailure(final Exception cause, final int ignoredAttemptCount, final long ignoredElapsedMillis) {
return new IllegalStateException("Docker MCP HTTP distribution failed to become ready. output: " + ProcessOutputDiagnostics.format(outputMessages), cause);
}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/DockerImageHttpRuntimeTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/DockerImageHttpRuntimeTest.java
new file mode 100644
index 00000000000..829e6ece466
--- /dev/null
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/DockerImageHttpRuntimeTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.test.e2e.mcp.support.distribution;
+
+import org.junit.jupiter.api.Test;
+
+import java.nio.file.Path;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class DockerImageHttpRuntimeTest {
+
+ @Test
+ void assertCreateDockerCommandUsesRuntimeAssignedPort() {
+ List<String> actual = DockerImageHttpRuntime.createDockerCommand("shardingsphere-mcp:test", Path.of("mcp.yaml"), "mcp-e2e-test");
+ assertThat(actual.get(actual.indexOf("-p") + 1), is("127.0.0.1::18088"));
+ assertThat(actual, hasItems("--name", "mcp-e2e-test", "SHARDINGSPHERE_MCP_TRANSPORT=http", "shardingsphere-mcp:test"));
+ }
+
+ @Test
+ void assertParsePublishedIPv4Port() {
+ assertThat(DockerImageHttpRuntime.parsePublishedPort("127.0.0.1:49152\n").orElseThrow(), is(49152));
+ }
+
+ @Test
+ void assertParsePublishedIPv6Port() {
+ assertThat(DockerImageHttpRuntime.parsePublishedPort("[::1]:49153\n").orElseThrow(), is(49153));
+ }
+
+ @Test
+ void assertParsePublishedPortRejectsMissingPort() {
+ assertTrue(DockerImageHttpRuntime.parsePublishedPort("published port is unavailable").isEmpty());
+ }
+
+ @Test
+ void assertParsePublishedPortRejectsOutOfRangePort() {
+ assertTrue(DockerImageHttpRuntime.parsePublishedPort("127.0.0.1:70000").isEmpty());
+ }
+}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionHttpRuntime.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionHttpRuntime.java
index db02c0aee4a..2f1e4c9bc15 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionHttpRuntime.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionHttpRuntime.java
@@ -24,7 +24,10 @@ import org.apache.shardingsphere.test.e2e.mcp.support.transport.client.MCPHttpIn
import org.apache.shardingsphere.test.e2e.mcp.support.transport.client.MCPInteractionClient;
import java.io.IOException;
+import java.net.URI;
import java.net.http.HttpClient;
+import java.util.Collection;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
@@ -36,12 +39,11 @@ public final class PackagedDistributionHttpRuntime implements AutoCloseable {
private static final long STARTUP_POLL_INTERVAL_MILLIS = 250L;
- private final PreparedPackagedDistribution distribution;
+ private static final String ENDPOINT_MARKER = "endpoint=";
private final PackagedDistributionProcessSupport processSupport;
public PackagedDistributionHttpRuntime(final PreparedPackagedDistribution distribution) {
- this.distribution = distribution;
processSupport = new PackagedDistributionProcessSupport(distribution, "mcp-packaged-http-e2e");
}
@@ -67,7 +69,11 @@ public final class PackagedDistributionHttpRuntime implements AutoCloseable {
if (!processSupport.isAlive()) {
return ReadinessResult.failed(null);
}
- MCPHttpInteractionClient result = new MCPHttpInteractionClient(distribution.getEndpointUri(), HttpClient.newHttpClient());
+ Optional<URI> endpointUri = findEndpointUri(processSupport.getOutputMessages());
+ if (endpointUri.isEmpty()) {
+ return ReadinessResult.retry(new IllegalStateException("Packaged MCP HTTP endpoint has not been reported yet."));
+ }
+ MCPHttpInteractionClient result = new MCPHttpInteractionClient(endpointUri.get(), HttpClient.newHttpClient());
try {
result.open();
return ReadinessResult.ready(result);
@@ -89,4 +95,24 @@ public final class PackagedDistributionHttpRuntime implements AutoCloseable {
Thread.currentThread().interrupt();
}
}
+
+ static Optional<URI> findEndpointUri(final Collection<String> outputMessages) {
+ for (String each : outputMessages) {
+ int startIndex = each.indexOf(ENDPOINT_MARKER);
+ if (startIndex < 0) {
+ continue;
+ }
+ startIndex += ENDPOINT_MARKER.length();
+ int endIndex = each.indexOf(',', startIndex);
+ String candidate = each.substring(startIndex, endIndex < 0 ? each.length() : endIndex).trim();
+ try {
+ URI result = URI.create(candidate);
+ if ("http".equalsIgnoreCase(result.getScheme()) && null != result.getHost() && result.getPort() > 0) {
+ return Optional.of(result);
+ }
+ } catch (final IllegalArgumentException ignored) {
+ }
+ }
+ return Optional.empty();
+ }
}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionHttpRuntimeTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionHttpRuntimeTest.java
new file mode 100644
index 00000000000..8d68afd9ea8
--- /dev/null
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionHttpRuntimeTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.test.e2e.mcp.support.distribution;
+
+import org.junit.jupiter.api.Test;
+
+import java.net.URI;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class PackagedDistributionHttpRuntimeTest {
+
+ @Test
+ void assertFindEndpointUri() {
+ URI actual = PackagedDistributionHttpRuntime.findEndpointUri(List.of(
+ "ShardingSphere MCP Server started, transport=http, endpoint=http://127.0.0.1:49152/mcp, logs=logs.")).orElseThrow();
+ assertThat(actual, is(URI.create("http://127.0.0.1:49152/mcp")));
+ }
+
+ @Test
+ void assertFindEndpointUriIgnoresInvalidMessages() {
+ assertTrue(PackagedDistributionHttpRuntime.findEndpointUri(List.of(
+ "ShardingSphere MCP Server started, transport=http, endpoint=http://127.0.0.1:0/mcp.",
+ "Unrelated startup output.")).isEmpty());
+ }
+}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionTestSupport.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionTestSupport.java
index 10bca0f9aee..33aec6f703d 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionTestSupport.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionTestSupport.java
@@ -30,8 +30,6 @@ import org.apache.shardingsphere.test.e2e.env.runtime.EnvironmentPropertiesLoade
import org.apache.shardingsphere.test.e2e.mcp.support.runtime.RuntimeTransport;
import java.io.IOException;
-import java.net.ServerSocket;
-import java.net.URI;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -104,9 +102,8 @@ public final class PackagedDistributionTestSupport {
private static PreparedPackagedDistribution createPreparedDistribution(final Path workingHome, final RuntimeTransport transport,
final Map<String, RuntimeDatabaseConfiguration> runtimeDatabases) throws IOException {
- int httpPort = resolveHttpPort(transport);
- Path configFile = rewriteConfiguration(workingHome, transport, httpPort, runtimeDatabases);
- return new PreparedPackagedDistribution(workingHome, configFile, transport, httpPort);
+ Path configFile = rewriteConfiguration(workingHome, transport, runtimeDatabases);
+ return new PreparedPackagedDistribution(workingHome, configFile, transport);
}
/**
@@ -261,21 +258,11 @@ public final class PackagedDistributionTestSupport {
}
}
- private static int allocatePort() throws IOException {
- try (ServerSocket serverSocket = new ServerSocket(0)) {
- return serverSocket.getLocalPort();
- }
- }
-
- private static int resolveHttpPort(final RuntimeTransport transport) throws IOException {
- return RuntimeTransport.HTTP == transport ? allocatePort() : -1;
- }
-
- private static Path rewriteConfiguration(final Path workingHome, final RuntimeTransport transport, final int httpPort,
+ private static Path rewriteConfiguration(final Path workingHome, final RuntimeTransport transport,
final Map<String, RuntimeDatabaseConfiguration> runtimeDatabases) throws IOException {
Path result = resolveConfigFile(workingHome, transport);
MCPLaunchConfiguration sourceConfig = MCPConfigurationLoader.load(result.toString());
- return writeConfiguration(result, createRuntimeConfiguration(sourceConfig, transport, httpPort, runtimeDatabases));
+ return writeConfiguration(result, createRuntimeConfiguration(sourceConfig, transport, runtimeDatabases));
}
private static Path resolveConfigFile(final Path workingHome, final RuntimeTransport transport) {
@@ -283,9 +270,9 @@ public final class PackagedDistributionTestSupport {
}
private static MCPLaunchConfiguration createRuntimeConfiguration(final MCPLaunchConfiguration sourceConfig, final RuntimeTransport transport,
- final int httpPort, final Map<String, RuntimeDatabaseConfiguration> runtimeDatabases) {
+ final Map<String, RuntimeDatabaseConfiguration> runtimeDatabases) {
HttpTransportConfiguration actualHttpTransport = new HttpTransportConfiguration(sourceConfig.getHttpTransport().getBindHost(),
- RuntimeTransport.HTTP == transport ? httpPort : sourceConfig.getHttpTransport().getPort(), sourceConfig.getHttpTransport().getEndpointPath());
+ RuntimeTransport.HTTP == transport ? 0 : sourceConfig.getHttpTransport().getPort(), sourceConfig.getHttpTransport().getEndpointPath());
return new MCPLaunchConfiguration(resolveTransportType(transport), actualHttpTransport, runtimeDatabases.isEmpty() ? sourceConfig.getDatabases() : runtimeDatabases);
}
@@ -293,14 +280,10 @@ public final class PackagedDistributionTestSupport {
return RuntimeTransport.HTTP == transport ? MCPTransportType.HTTP : MCPTransportType.STDIO;
}
- public record PreparedPackagedDistribution(Path home, Path configFile, RuntimeTransport transport, int httpPort) {
+ public record PreparedPackagedDistribution(Path home, Path configFile, RuntimeTransport transport) {
public Path getStartScript() {
return PackagedDistributionProcessSupport.resolveStartScript(home);
}
-
- public URI getEndpointUri() {
- return URI.create(String.format("http://127.0.0.1:%d/mcp", httpPort));
- }
}
}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionTestSupportTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionTestSupportTest.java
index a533b1bc0a2..5cc12dc23ba 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionTestSupportTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionTestSupportTest.java
@@ -17,7 +17,6 @@
package org.apache.shardingsphere.test.e2e.mcp.support.distribution;
-import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import org.apache.shardingsphere.mcp.bootstrap.config.MCPLaunchConfiguration;
@@ -152,10 +151,8 @@ class PackagedDistributionTestSupportTest {
MCPLaunchConfiguration actualConfig = MCPConfigurationLoader.load(actual.configFile().toString());
assertThat(actualConfig.getTransportType(), is(expectedTransportType));
if (RuntimeTransport.HTTP == transport) {
- assertThat(actual.httpPort(), greaterThan(0));
- assertThat(actualConfig.getHttpTransport().getPort(), is(actual.httpPort()));
+ assertThat(actualConfig.getHttpTransport().getPort(), is(0));
} else {
- assertThat(actual.httpPort(), is(-1));
assertThat(actualConfig.getHttpTransport().getPort(), is(18088));
}
} finally {
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeDockerSupportTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeDockerSupportTest.java
new file mode 100644
index 00000000000..42b0ca497b5
--- /dev/null
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeDockerSupportTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.test.e2e.mcp.support.runtime;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Properties;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class MySQLRuntimeDockerSupportTest {
+
+ @Test
+ void assertCreateDockerRequiredMessageWithReadinessDiagnostic() {
+ assertThat(MySQLRuntimeDockerSupport.createDockerRequiredMessage("Docker is required.", "daemon unavailable"),
+ is("Docker is required. Docker readiness diagnostic: daemon unavailable"));
+ }
+
+ @Test
+ void assertCreateDockerRequiredMessageWithoutReadinessDiagnostic() {
+ assertThat(MySQLRuntimeDockerSupport.createDockerRequiredMessage("Docker is required.", ""), is("Docker is required."));
+ }
+
+ @Test
+ void assertGetMySQLImage() {
+ Properties props = new Properties();
+ props.setProperty("mcp.e2e.mysql.image", "mysql:8.4.0");
+ assertThat(MySQLRuntimeDockerSupport.getMySQLImage(props), is("mysql:8.4.0"));
+ }
+
+ @Test
+ void assertGetMySQLImageWithMissingProperty() {
+ IllegalStateException actualException = assertThrows(IllegalStateException.class, () -> MySQLRuntimeDockerSupport.getMySQLImage(new Properties()));
+ assertThat(actualException.getMessage(), is("MCP E2E MySQL image property `mcp.e2e.mysql.image` is required."));
+ }
+}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java
index 1cb3c2e1f7f..2a110f03e4d 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java
@@ -36,8 +36,6 @@ import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
-import java.util.Optional;
-import java.util.Properties;
/**
* E2E-local MySQL-backed runtime test support.
@@ -83,10 +81,6 @@ public final class MySQLRuntimeTestSupport {
.withStartupTimeout(Duration.ofMinutes(2));
}
- static String getMySQLImage(final Properties props) {
- return MySQLRuntimeDockerSupport.getMySQLImage(props);
- }
-
/**
* Check whether Docker is available for Testcontainers-backed tests.
*
@@ -96,15 +90,6 @@ public final class MySQLRuntimeTestSupport {
return MySQLRuntimeDockerSupport.isDockerAvailable();
}
- /**
- * Get Docker readiness diagnostic when Testcontainers cannot use Docker.
- *
- * @return Docker unavailable reason
- */
- public static Optional<String> getDockerUnavailableReason() {
- return MySQLRuntimeDockerSupport.getDockerUnavailableReason();
- }
-
/**
* Create Docker-required message with bounded readiness diagnostics.
*
@@ -115,10 +100,6 @@ public final class MySQLRuntimeTestSupport {
return MySQLRuntimeDockerSupport.createDockerRequiredMessage(scenarioMessage);
}
- static String createDockerRequiredMessage(final String scenarioMessage, final String unavailableReason) {
- return MySQLRuntimeDockerSupport.createDockerRequiredMessage(scenarioMessage, unavailableReason);
- }
-
/**
* Create runtime databases for the MySQL-backed runtime.
*
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupportTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupportTest.java
index ceac136ac3b..aee9f40e929 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupportTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupportTest.java
@@ -21,8 +21,6 @@ import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.testcontainers.containers.GenericContainer;
-import java.util.Properties;
-
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;
@@ -34,30 +32,6 @@ import static org.mockito.Mockito.mockStatic;
class MySQLRuntimeTestSupportTest {
- @Test
- void assertCreateDockerRequiredMessageWithReadinessDiagnostic() {
- assertThat(MySQLRuntimeTestSupport.createDockerRequiredMessage("Docker is required.", "daemon unavailable"),
- is("Docker is required. Docker readiness diagnostic: daemon unavailable"));
- }
-
- @Test
- void assertCreateDockerRequiredMessageWithoutReadinessDiagnostic() {
- assertThat(MySQLRuntimeTestSupport.createDockerRequiredMessage("Docker is required.", ""), is("Docker is required."));
- }
-
- @Test
- void assertGetMySQLImage() {
- Properties props = new Properties();
- props.setProperty("mcp.e2e.mysql.image", "mysql:8.4.0");
- assertThat(MySQLRuntimeTestSupport.getMySQLImage(props), is("mysql:8.4.0"));
- }
-
- @Test
- void assertGetMySQLImageWithMissingProperty() {
- IllegalStateException actualException = assertThrows(IllegalStateException.class, () -> MySQLRuntimeTestSupport.getMySQLImage(new Properties()));
- assertThat(actualException.getMessage(), is("MCP E2E MySQL image property `mcp.e2e.mysql.image` is required."));
- }
-
@Test
void assertCreateLLMRuntimeFixturePreservesInitializationFailure() {
GenericContainer<?> container = mock(GenericContainer.class);