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 29f273621e3 Refine MCP E2E coverage and diagnostics (#39281)
29f273621e3 is described below

commit 29f273621e304c1d7133f7bc919c9b8b61100f9e
Author: Liang Zhang <[email protected]>
AuthorDate: Thu Jul 30 21:59:36 2026 +0800

    Refine MCP E2E coverage and diagnostics (#39281)
    
    * Refine MCP E2E coverage and diagnostics
    
    - limit PR triggers to MCP-owned paths
    - add STDIO parity and persist bounded redacted runtime artifacts
    - simplify E2E fixtures and remove low-value test-only checks
    
    * Refine MCP E2E coverage and diagnostics
    
    - limit PR triggers to MCP-owned paths
    - add STDIO parity and persist bounded redacted runtime artifacts
    - simplify E2E fixtures and remove low-value test-only checks
---
 .github/workflows/e2e-mcp.yml                      |  13 +--
 .../HttpProxySecretReferenceWorkflowE2ETest.java   |  19 +++-
 .../mcp/functionality/MySQLReadOnlySQLE2ETest.java |   2 +-
 .../functionality/PackagedDistributionE2ETest.java |   9 +-
 .../test/e2e/mcp/llm/LLMHttpE2ETest.java           |  60 +++++++---
 .../artifact/LLMConversationArtifactWriter.java    |  30 ++---
 .../artifact/LLME2EArtifactRedactor.java           |  35 ------
 .../mcp/llm/fixture/LLMRuntimeFixtureFactory.java  | 101 -----------------
 .../llm/fixture/LLMRuntimeFixtureFactoryTest.java  |  42 -------
 .../test/e2e/mcp/support/OfficialMCPToolNames.java |  43 --------
 .../e2e/mcp/support/artifact/MCPArtifactUtils.java | 121 +++++++++++++++++++++
 .../distribution/DockerImageHttpRuntime.java       |   2 +
 .../PackagedDistributionProcessSupport.java        |   3 +
 ...PWorkflowCustomEncryptAlgorithmFixtureTest.java |  65 -----------
 .../MCPWorkflowCustomMaskAlgorithmFixtureTest.java |  36 ------
 .../fixture/MCPWorkflowSecretReferenceFixture.java |  56 ----------
 .../plugin/PluginFixtureHandlerProviderTest.java   |  51 ---------
 .../plugin/PluginFixturePingToolHandlerTest.java   |  34 ------
 .../PluginFixtureStatusResourceHandlerTest.java    |  36 ------
 .../transport/MCPPayloadAssertionsTest.java        |  53 ---------
 .../AbstractProcessMCPStdioInteractionClient.java  |   2 +
 .../resources/proxy/workflow/cluster/global.yaml   |   2 +
 22 files changed, 214 insertions(+), 601 deletions(-)

diff --git a/.github/workflows/e2e-mcp.yml b/.github/workflows/e2e-mcp.yml
index a81396d0815..bd470111a7c 100644
--- a/.github/workflows/e2e-mcp.yml
+++ b/.github/workflows/e2e-mcp.yml
@@ -27,15 +27,8 @@ on:
       - ready_for_review
     paths:
       - '.github/workflows/e2e-mcp.yml'
-      - '.github/workflows/resources/actions/download-e2e-artifacts/**'
-      - '.github/workflows/resources/actions/setup-build-environment/**'
-      - '.github/workflows/resources/actions/upload-e2e-artifacts/**'
-      - 'pom.xml'
-      - 'database/connector/**'
       - 'distribution/mcp/**'
       - 'mcp/**'
-      - 'test/e2e/pom.xml'
-      - 'test/e2e/env/**'
       - 'test/e2e/mcp/**'
   schedule:
     - cron: '0 19 * * 1-5'
@@ -161,13 +154,15 @@ jobs:
           mkdir -p distribution/mcp/target
           tar -xzf 
"${RUNNER_TEMP}/mcp-e2e-build-artifacts/mcp-distribution-home.tar.gz" -C 
distribution/mcp/target
       - name: Run MCP Functionality E2E
-        run: ./mvnw -pl test/e2e/mcp test -Pe2e.mcp.functionality 
-Dmcp.e2e.container.image=${{ env.MCP_DISTRIBUTION_IMAGE }} -DskipITs 
-Dspotless.skip=true -B -ntp
+        run: ./mvnw -pl test/e2e/mcp test -Pe2e.mcp.functionality 
-Dmcp.e2e.container.image=${{ env.MCP_DISTRIBUTION_IMAGE }} 
-Dmcp.e2e.artifact-root="${{ github.workspace 
}}/test/e2e/mcp/target/functionality-e2e" -DskipITs -Dspotless.skip=true -B -ntp
       - name: Upload MCP Functionality E2E Artifacts
         if: always()
         uses: actions/upload-artifact@v6
         with:
           name: e2e-mcp-functionality-${{ github.run_id }}-${{ 
github.run_attempt }}
-          path: test/e2e/mcp/target/surefire-reports
+          path: |
+            test/e2e/mcp/target/surefire-reports
+            test/e2e/mcp/target/functionality-e2e
           if-no-files-found: warn
 
   e2e-mcp-conformance:
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/HttpProxySecretReferenceWorkflowE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/HttpProxySecretReferenceWorkflowE2ETest.java
index a4a23652c91..f53ad351484 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/HttpProxySecretReferenceWorkflowE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/HttpProxySecretReferenceWorkflowE2ETest.java
@@ -19,7 +19,6 @@ package org.apache.shardingsphere.test.e2e.mcp.functionality;
 
 import org.apache.shardingsphere.mcp.support.diagnostic.MCPDiagnosticCategory;
 import 
org.apache.shardingsphere.mcp.support.workflow.descriptor.WorkflowToolDescriptors;
-import 
org.apache.shardingsphere.test.e2e.mcp.support.fixture.MCPWorkflowSecretReferenceFixture;
 import 
org.apache.shardingsphere.test.e2e.mcp.support.transport.client.MCPInteractionClient;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledIf;
@@ -43,6 +42,10 @@ class HttpProxySecretReferenceWorkflowE2ETest extends 
AbstractHttpProxyWorkflowE
     
     private static final String RULES_RESOURCE_URI = 
"shardingsphere://features/encrypt/databases/%s/rules";
     
+    private static final String SECRET_REF = "placeholder://secret-value-1";
+    
+    private static final String INPUT_LABEL = "placeholder-secret-1";
+    
     @Test
     void assertSecretReferenceApplyRequiresManualExecution() throws 
IOException, InterruptedException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
@@ -57,7 +60,7 @@ class HttpProxySecretReferenceWorkflowE2ETest extends 
AbstractHttpProxyWorkflowE
             assertThat(String.valueOf(applyResponse.get("category")), 
is(MCPDiagnosticCategory.SECRET_REFERENCE_MANUAL_EXECUTION_REQUIRED));
             assertModelFacingPayloadContract(applyResponse);
             
assertThat(getObjectListOrEmpty(applyResponse.get("step_results")).size(), 
is(0));
-            
MCPWorkflowSecretReferenceFixture.assertSecretReferenceRedacted(applyResponse);
+            assertSecretReferenceRedacted(applyResponse);
             
assertTrue(getPayloadItems(interactionClient.readResource(String.format(RULES_RESOURCE_URI,
 getLogicalDatabaseName()))).isEmpty());
         }
     }
@@ -67,7 +70,7 @@ class HttpProxySecretReferenceWorkflowE2ETest extends 
AbstractHttpProxyWorkflowE
                 Map.of("database", getLogicalDatabaseName(), "table", 
"orders", "column", "status",
                         "natural_language_intent", "encrypt status with 
reversible encryption, no equality, no like", "algorithm_type", "AES",
                         "cipher_column_name", "status_cipher",
-                        "primary_algorithm_properties", 
Map.of("aes-key-value", 
MCPWorkflowSecretReferenceFixture.createSecretReferenceInput())));
+                        "primary_algorithm_properties", 
Map.of("aes-key-value", Map.of("secret_ref", SECRET_REF, "label", 
INPUT_LABEL))));
     }
     
     private void assertPlannedSecretReferencePayload(final Map<String, Object> 
planResponse) {
@@ -81,7 +84,7 @@ class HttpProxySecretReferenceWorkflowE2ETest extends 
AbstractHttpProxyWorkflowE
         assertThat(String.valueOf(secretReference.get("property_key")), 
is("aes-key-value"));
         assertThat(String.valueOf(secretReference.get("label")), 
is("secret_placeholder:primary.aes-key-value"));
         assertThat(String.valueOf(secretReference.get("manual_placeholder")), 
is("<SECRET_VALUE_PRIMARY_AES_KEY_VALUE>"));
-        
MCPWorkflowSecretReferenceFixture.assertSecretReferenceRedacted(planResponse);
+        assertSecretReferenceRedacted(planResponse);
     }
     
     private void assertSecretReferencedPreview(final Map<String, Object> 
previewResponse) {
@@ -91,6 +94,12 @@ class HttpProxySecretReferenceWorkflowE2ETest extends 
AbstractHttpProxyWorkflowE
         assertThat(previewArtifacts.size(), is(1));
         assertThat(String.valueOf(previewArtifacts.getFirst().get("sql")), 
containsString("'aes-key-value'='<SECRET_VALUE_PRIMARY_AES_KEY_VALUE>'"));
         
assertFalse(String.valueOf(previewArtifacts.getFirst().get("sql")).contains("secret_reference:primary.aes-key-value"));
-        
MCPWorkflowSecretReferenceFixture.assertSecretReferenceRedacted(previewResponse);
+        assertSecretReferenceRedacted(previewResponse);
+    }
+    
+    private void assertSecretReferenceRedacted(final Object payload) {
+        String actual = String.valueOf(payload);
+        assertFalse(actual.contains(SECRET_REF));
+        assertFalse(actual.contains(INPUT_LABEL));
     }
 }
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/MySQLReadOnlySQLE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/MySQLReadOnlySQLE2ETest.java
index e631abd8775..02364479502 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/MySQLReadOnlySQLE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/MySQLReadOnlySQLE2ETest.java
@@ -138,7 +138,7 @@ class MySQLReadOnlySQLE2ETest extends 
AbstractMySQLRuntimeE2ETest {
     }
     
     @ParameterizedTest(name = "{0}")
-    @MethodSource("httpTransportCase")
+    @MethodSource("allTransportCases")
     void assertAiNativeDeterministicInteractionLoop(final String name, final 
RuntimeTransport transport) throws IOException, InterruptedException {
         useTransport(transport);
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/PackagedDistributionE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/PackagedDistributionE2ETest.java
index d69b8b50240..7ee136b7b66 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/PackagedDistributionE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/PackagedDistributionE2ETest.java
@@ -17,8 +17,9 @@
 
 package org.apache.shardingsphere.test.e2e.mcp.functionality;
 
+import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolDescriptor;
+import org.apache.shardingsphere.mcp.core.tool.handler.ToolDefinitionRegistry;
 import 
org.apache.shardingsphere.test.e2e.env.runtime.EnvironmentPropertiesLoader;
-import org.apache.shardingsphere.test.e2e.mcp.support.OfficialMCPToolNames;
 import 
org.apache.shardingsphere.test.e2e.mcp.support.distribution.DockerImageHttpRuntime;
 import 
org.apache.shardingsphere.test.e2e.mcp.support.distribution.PackagedDistributionHttpRuntime;
 import 
org.apache.shardingsphere.test.e2e.mcp.support.distribution.PackagedDistributionPluginFixtureSupport;
@@ -75,7 +76,9 @@ class PackagedDistributionE2ETest {
             "database_gateway_execute_query", 
"database_gateway_execute_explain_query", "database_gateway_execute_update",
             "database_gateway_apply_workflow", 
"database_gateway_validate_workflow");
     
-    private static final List<String> REMOVED_FEATURE_TOOL_NAMES = 
OfficialMCPToolNames.getAll().stream().filter(each -> 
!CORE_TOOL_NAMES.contains(each)).toList();
+    private static final List<String> OFFICIAL_TOOL_NAMES = 
ToolDefinitionRegistry.getSupportedToolDescriptors().stream().map(MCPToolDescriptor::getName).toList();
+    
+    private static final List<String> REMOVED_FEATURE_TOOL_NAMES = 
OFFICIAL_TOOL_NAMES.stream().filter(each -> 
!CORE_TOOL_NAMES.contains(each)).toList();
     
     private static final String FIXTURE_RESOURCE_URI = 
"shardingsphere://features/test-fixture/status";
     
@@ -314,7 +317,7 @@ class PackagedDistributionE2ETest {
     }
     
     private void assertOfficialToolNames(final List<String> actualToolNames) {
-        assertThat(actualToolNames, 
containsInAnyOrder(OfficialMCPToolNames.getAll().toArray()));
+        assertThat(actualToolNames, 
containsInAnyOrder(OFFICIAL_TOOL_NAMES.toArray()));
     }
     
     private void assertMySQLMetadata(final MCPInteractionClient 
interactionClient) throws IOException, InterruptedException {
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/LLMHttpE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/LLMHttpE2ETest.java
index de9aa1d9393..b2d9983af22 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/LLMHttpE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/LLMHttpE2ETest.java
@@ -25,11 +25,11 @@ import 
org.apache.shardingsphere.test.e2e.mcp.llm.conversation.LLMConversationRu
 import 
org.apache.shardingsphere.test.e2e.mcp.llm.conversation.artifact.LLMConversationArtifactWriter;
 import 
org.apache.shardingsphere.test.e2e.mcp.llm.conversation.artifact.LLME2EAssertionReport;
 import 
org.apache.shardingsphere.test.e2e.mcp.llm.conversation.client.LLMChatModelClient;
-import 
org.apache.shardingsphere.test.e2e.mcp.llm.fixture.LLMRuntimeFixtureFactory;
-import 
org.apache.shardingsphere.test.e2e.mcp.llm.fixture.LLMRuntimeFixtureFactory.Fixture;
 import org.apache.shardingsphere.test.e2e.mcp.llm.fixture.LLMRuntimeSupport;
 import 
org.apache.shardingsphere.test.e2e.mcp.support.assertion.MCPModelContractAssertions;
 import 
org.apache.shardingsphere.test.e2e.mcp.support.runtime.AbstractConfigBackedRuntimeE2ETest;
+import 
org.apache.shardingsphere.test.e2e.mcp.support.runtime.MySQLRuntimeTestSupport;
+import 
org.apache.shardingsphere.test.e2e.mcp.support.runtime.MySQLRuntimeTestSupport.LLMMySQLRuntimeFixture;
 import org.apache.shardingsphere.test.e2e.mcp.support.runtime.RuntimeTransport;
 import 
org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPInteractionTraceRecord;
 import org.junit.jupiter.api.AfterAll;
@@ -81,15 +81,14 @@ class LLMHttpE2ETest extends 
AbstractConfigBackedRuntimeE2ETest {
             "interaction-trace.json", "mcp-runtime.log", 
"assertion-report.json");
     
     private static final Pattern UNREDACTED_SECRET_PATTERN = Pattern.compile(
-            
"(?i)(\"(?:api[_-]?key|token|password|authorization|secret)\"\\s*:\\s*\")(?!<redacted>\")([^\"]+)(\")|(Bearer\\s+)(?!<redacted>)[A-Za-z0-9._~+/=-]+");
+            
"(?i)(?<![a-z0-9_])\"?(?:api[_-]?key|access[_-]?token|token|authorization|password|passwd|pwd|secret)\"?\\s*[:=]\\s*[\"']?(?!<redacted>)[^\\s,\"'}]+"
+                    + "|(Bearer\\s+)(?!<redacted>)[A-Za-z0-9._~+/=-]+|jdbc:");
     
     private static LLMRuntimeSupport.ModelRuntime llmRuntime;
     
-    private final LLMRuntimeFixtureFactory runtimeFixtureFactory = new 
LLMRuntimeFixtureFactory();
-    
     private final LLMConversationArtifactWriter artifactWriter = new 
LLMConversationArtifactWriter();
     
-    private Fixture runtimeFixture;
+    private LLMMySQLRuntimeFixture runtimeFixture;
     
     @BeforeAll
     static void prepareLLMRuntime() throws InterruptedException {
@@ -162,8 +161,9 @@ class LLMHttpE2ETest extends 
AbstractConfigBackedRuntimeE2ETest {
                 createInteractionClient(),
                 modelRuntime.getConfiguration().getModelName()).run(scenario);
         Path artifactDirectory = 
modelRuntime.getConfiguration().createArtifactDirectory("llm-http/" + 
scenario.id());
-        artifactWriter.write(artifactDirectory, actualResult, 
modelRuntime.getEvidence());
-        assertArtifacts(artifactDirectory);
+        Collection<String> sensitiveValues = getArtifactSensitiveValues();
+        artifactWriter.write(artifactDirectory, actualResult, 
modelRuntime.getEvidence(), sensitiveValues);
+        assertArtifacts(artifactDirectory, sensitiveValues);
         assertTrue(actualResult.assertionReport().isSuccess(), () -> 
createFailureMessage(
                 scenario.id(), actualResult.assertionReport(), 
artifactDirectory));
         assertFalse(actualResult.evidence().interactionTrace().isEmpty(), 
scenario.id() + " must capture MCP evidence.");
@@ -172,7 +172,7 @@ class LLMHttpE2ETest extends 
AbstractConfigBackedRuntimeE2ETest {
     
     private LLME2EAssertionReport evaluateReadOnlyQuery(final String answer, 
final List<MCPInteractionTraceRecord> trace) {
         Optional<Integer> actualCount = findQueryCount(trace, 0);
-        if (actualCount.isEmpty() || getRequiredRuntimeFixture().totalOrders() 
!= actualCount.get()) {
+        if (actualCount.isEmpty() || 
getRequiredRuntimeFixture().getTotalOrders() != actualCount.get()) {
             return LLME2EAssertionReport.failure("query_evidence_mismatch", 
"The MCP query response did not contain the fixture row count.");
         }
         return containsStandaloneNumber(answer, actualCount.get())
@@ -247,7 +247,7 @@ class LLMHttpE2ETest extends 
AbstractConfigBackedRuntimeE2ETest {
             return LLME2EAssertionReport.failure("missing_resource_recovery", 
"The model did not follow the stale response recovery action to a live resource 
containing orders.");
         }
         Optional<Integer> actualCount = findQueryCount(trace, 
trace.get(recoveryResourceIndex).getModelTurn());
-        if (actualCount.isEmpty() || getRequiredRuntimeFixture().totalOrders() 
!= actualCount.get()) {
+        if (actualCount.isEmpty() || 
getRequiredRuntimeFixture().getTotalOrders() != actualCount.get()) {
             return LLME2EAssertionReport.failure("query_evidence_mismatch", 
"The recovered conversation did not obtain the fixture row count from MCP.");
         }
         return containsStandaloneNumber(answer, actualCount.get())
@@ -363,7 +363,23 @@ class LLMHttpE2ETest extends 
AbstractConfigBackedRuntimeE2ETest {
         return Pattern.compile("(?<![a-z0-9_])" + Pattern.quote(identifier) + 
"(?![a-z0-9_])").matcher(answer).find();
     }
     
-    private void assertArtifacts(final Path artifactDirectory) throws 
IOException {
+    private Collection<String> getArtifactSensitiveValues() {
+        Set<String> result = new LinkedHashSet<>();
+        addArtifactSensitiveValue(result, 
getRequiredLLMRuntime().getConfiguration().getApiKey());
+        for (RuntimeDatabaseConfiguration each : 
getRequiredRuntimeFixture().getRuntimeDatabases().values()) {
+            addArtifactSensitiveValue(result, each.getJdbcUrl());
+            addArtifactSensitiveValue(result, each.getPassword());
+        }
+        return result;
+    }
+    
+    private void addArtifactSensitiveValue(final Collection<String> 
sensitiveValues, final String value) {
+        if (8 <= value.length()) {
+            sensitiveValues.add(value);
+        }
+    }
+    
+    private void assertArtifacts(final Path artifactDirectory, final 
Collection<String> sensitiveValues) throws IOException {
         assertTrue(Files.isDirectory(artifactDirectory), () -> "Missing LLM 
artifact directory: " + artifactDirectory);
         for (String each : ARTIFACT_FILES) {
             assertTrue(Files.isRegularFile(artifactDirectory.resolve(each)), 
() -> "Missing LLM artifact: " + artifactDirectory.resolve(each));
@@ -372,7 +388,9 @@ class LLMHttpE2ETest extends 
AbstractConfigBackedRuntimeE2ETest {
             for (Path each : paths.filter(Files::isRegularFile).toList()) {
                 String content = Files.readString(each);
                 assertFalse(UNREDACTED_SECRET_PATTERN.matcher(content).find(), 
() -> "Unredacted secret-like value in LLM artifact: " + each);
-                
assertFalse(content.contains(getRequiredLLMRuntime().getConfiguration().getApiKey()),
 () -> "Known model API key leaked into LLM artifact: " + each);
+                for (String sensitiveValue : sensitiveValues) {
+                    assertFalse(content.contains(sensitiveValue), () -> "Known 
sensitive value leaked into LLM artifact: " + each);
+                }
             }
         }
     }
@@ -389,7 +407,7 @@ class LLMHttpE2ETest extends 
AbstractConfigBackedRuntimeE2ETest {
     }
     
     private String queryOrderStatus() throws SQLException {
-        RuntimeDatabaseConfiguration databaseConfig = 
getRequiredRuntimeFixture().runtimeDatabases().get(DATABASE_NAME);
+        RuntimeDatabaseConfiguration databaseConfig = 
getRequiredRuntimeFixture().getRuntimeDatabases().get(DATABASE_NAME);
         try (
                 Connection connection = 
databaseConfig.openConnection(DATABASE_NAME);
                 PreparedStatement statement = 
connection.prepareStatement("SELECT status FROM orders WHERE order_id = 1");
@@ -427,17 +445,25 @@ class LLMHttpE2ETest extends 
AbstractConfigBackedRuntimeE2ETest {
     
     @Override
     protected Map<String, RuntimeDatabaseConfiguration> getRuntimeDatabases() {
-        return getRequiredRuntimeFixture().runtimeDatabases();
+        return getRequiredRuntimeFixture().getRuntimeDatabases();
     }
     
     @Override
     protected void prepareRuntimeFixture() throws IOException {
-        if (null == runtimeFixture) {
-            runtimeFixture = 
runtimeFixtureFactory.createMySQLFixture(DATABASE_NAME, "Docker is required for 
the MySQL-backed LLM E2E test.");
+        if (null != runtimeFixture) {
+            return;
+        }
+        if (!MySQLRuntimeTestSupport.isDockerAvailable()) {
+            throw new 
IllegalStateException(MySQLRuntimeTestSupport.createDockerRequiredMessage("Docker
 is required for the MySQL-backed LLM E2E test."));
+        }
+        try {
+            runtimeFixture = 
MySQLRuntimeTestSupport.createLLMRuntimeFixture(DATABASE_NAME);
+        } catch (final SQLException ex) {
+            throw new IOException(ex);
         }
     }
     
-    private Fixture getRequiredRuntimeFixture() {
+    private LLMMySQLRuntimeFixture getRequiredRuntimeFixture() {
         if (null == runtimeFixture) {
             throw new IllegalStateException("LLM E2E runtime fixture was not 
initialized.");
         }
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLMConversationArtifactWriter.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLMConversationArtifactWriter.java
index 36df0250b4a..a5c83c0b473 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLMConversationArtifactWriter.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLMConversationArtifactWriter.java
@@ -19,10 +19,12 @@ package 
org.apache.shardingsphere.test.e2e.mcp.llm.conversation.artifact;
 
 import org.apache.shardingsphere.infra.util.json.JsonUtils;
 import 
org.apache.shardingsphere.test.e2e.mcp.llm.conversation.LLMConversationRunner.Result;
+import 
org.apache.shardingsphere.test.e2e.mcp.support.artifact.MCPArtifactUtils;
 
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.Collection;
 import java.util.Map;
 
 /**
@@ -30,8 +32,6 @@ import java.util.Map;
  */
 public final class LLMConversationArtifactWriter {
     
-    private final LLME2EArtifactRedactor redactor = new 
LLME2EArtifactRedactor();
-    
     private final LLME2ERuntimeEvidenceValidator runtimeEvidenceValidator = 
new LLME2ERuntimeEvidenceValidator();
     
     /**
@@ -40,20 +40,22 @@ public final class LLMConversationArtifactWriter {
      * @param artifactDirectory artifact directory
      * @param conversationResult conversation result
      * @param runtimeEvidence runtime evidence
+     * @param sensitiveValues concrete sensitive values
      * @throws IOException IO exception
      */
-    public void write(final Path artifactDirectory, final Result 
conversationResult, final Map<String, Object> runtimeEvidence) throws 
IOException {
+    public void write(final Path artifactDirectory, final Result 
conversationResult, final Map<String, Object> runtimeEvidence,
+                      final Collection<String> sensitiveValues) throws 
IOException {
         runtimeEvidenceValidator.validate(runtimeEvidence);
-        writeContent(artifactDirectory.resolve("run-context.json"), 
JsonUtils.toJsonString(createRunContext(conversationResult, runtimeEvidence)));
-        writeContent(artifactDirectory.resolve("system-prompt.md"), 
conversationResult.systemPrompt());
-        writeContent(artifactDirectory.resolve("question.txt"), 
conversationResult.scenario().question());
-        writeContent(artifactDirectory.resolve("answer.txt"), 
conversationResult.actualAnswer());
+        writeContent(artifactDirectory.resolve("run-context.json"), 
JsonUtils.toJsonString(createRunContext(conversationResult, runtimeEvidence)), 
sensitiveValues);
+        writeContent(artifactDirectory.resolve("system-prompt.md"), 
conversationResult.systemPrompt(), sensitiveValues);
+        writeContent(artifactDirectory.resolve("question.txt"), 
conversationResult.scenario().question(), sensitiveValues);
+        writeContent(artifactDirectory.resolve("answer.txt"), 
conversationResult.actualAnswer(), sensitiveValues);
         writeContent(artifactDirectory.resolve("raw-model-output.txt"),
-                String.join(System.lineSeparator() + System.lineSeparator(), 
conversationResult.evidence().rawModelOutputs()));
-        writeContent(artifactDirectory.resolve("available-tools.json"), 
JsonUtils.toJsonString(conversationResult.evidence().toolDefinitions()));
-        writeContent(artifactDirectory.resolve("interaction-trace.json"), 
JsonUtils.toJsonString(conversationResult.evidence().interactionTrace()));
-        writeContent(artifactDirectory.resolve("mcp-runtime.log"), 
String.join(System.lineSeparator(), 
conversationResult.evidence().runtimeLogLines()));
-        writeContent(artifactDirectory.resolve("assertion-report.json"), 
JsonUtils.toJsonString(conversationResult.assertionReport()));
+                String.join(System.lineSeparator() + System.lineSeparator(), 
conversationResult.evidence().rawModelOutputs()), sensitiveValues);
+        writeContent(artifactDirectory.resolve("available-tools.json"), 
JsonUtils.toJsonString(conversationResult.evidence().toolDefinitions()), 
sensitiveValues);
+        writeContent(artifactDirectory.resolve("interaction-trace.json"), 
JsonUtils.toJsonString(conversationResult.evidence().interactionTrace()), 
sensitiveValues);
+        writeContent(artifactDirectory.resolve("mcp-runtime.log"), 
String.join(System.lineSeparator(), 
conversationResult.evidence().runtimeLogLines()), sensitiveValues);
+        writeContent(artifactDirectory.resolve("assertion-report.json"), 
JsonUtils.toJsonString(conversationResult.assertionReport()), sensitiveValues);
     }
     
     private Map<String, Object> createRunContext(final Result 
conversationResult, final Map<String, Object> runtimeEvidence) {
@@ -65,8 +67,8 @@ public final class LLMConversationArtifactWriter {
                 "failureType", 
conversationResult.assertionReport().getFailureType());
     }
     
-    private void writeContent(final Path file, final String content) throws 
IOException {
+    private void writeContent(final Path file, final String content, final 
Collection<String> sensitiveValues) throws IOException {
         Files.createDirectories(file.getParent());
-        Files.writeString(file, redactor.redact(content));
+        Files.writeString(file, MCPArtifactUtils.redact(content, 
sensitiveValues));
     }
 }
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EArtifactRedactor.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EArtifactRedactor.java
deleted file mode 100644
index 19f7625850d..00000000000
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EArtifactRedactor.java
+++ /dev/null
@@ -1,35 +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.test.e2e.mcp.llm.conversation.artifact;
-
-import java.util.regex.Pattern;
-
-final class LLME2EArtifactRedactor {
-    
-    private static final Pattern JSON_SECRET_FIELD_PATTERN = 
Pattern.compile("(?i)(\"(?:api[_-]?key|token|password|authorization|secret)\"\\s*:\\s*\")([^\"]+)(\")");
-    
-    private static final Pattern BEARER_TOKEN_PATTERN = 
Pattern.compile("(?i)(Bearer\\s+)[A-Za-z0-9._~+/=-]+");
-    
-    private static final Pattern ENV_SECRET_ASSIGNMENT_PATTERN = 
Pattern.compile("(?i)((?:MCP_LLM_API_KEY|HF_TOKEN|HUGGING_FACE_HUB_TOKEN|LLAMA_API_KEY)\\s*=\\s*)\\S+");
-    
-    String redact(final String value) {
-        String result = 
JSON_SECRET_FIELD_PATTERN.matcher(value).replaceAll("$1<redacted>$3");
-        result = 
BEARER_TOKEN_PATTERN.matcher(result).replaceAll("$1<redacted>");
-        return 
ENV_SECRET_ASSIGNMENT_PATTERN.matcher(result).replaceAll("$1<redacted>");
-    }
-}
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeFixtureFactory.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeFixtureFactory.java
deleted file mode 100644
index e2b9caa8c63..00000000000
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeFixtureFactory.java
+++ /dev/null
@@ -1,101 +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.test.e2e.mcp.llm.fixture;
-
-import lombok.AccessLevel;
-import lombok.RequiredArgsConstructor;
-import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
-import 
org.apache.shardingsphere.test.e2e.mcp.support.runtime.MySQLRuntimeTestSupport;
-
-import java.io.IOException;
-import java.sql.SQLException;
-import java.util.Map;
-
-/**
- * Create runtime fixtures for LLM E2E tests.
- */
-public final class LLMRuntimeFixtureFactory {
-    
-    /**
-     * Create one MySQL runtime fixture.
-     *
-     * @param logicalDatabase logical database
-     * @param dockerRequiredMessage failure message when Docker is unavailable
-     * @return runtime fixture
-     * @throws IOException IO exception
-     * @throws IllegalStateException Docker is unavailable
-     */
-    public Fixture createMySQLFixture(final String logicalDatabase, final 
String dockerRequiredMessage) throws IOException {
-        if (!MySQLRuntimeTestSupport.isDockerAvailable()) {
-            throw new 
IllegalStateException(MySQLRuntimeTestSupport.createDockerRequiredMessage(dockerRequiredMessage));
-        }
-        try {
-            MySQLRuntimeTestSupport.LLMMySQLRuntimeFixture actualFixture = 
MySQLRuntimeTestSupport.createLLMRuntimeFixture(logicalDatabase);
-            return new Fixture(actualFixture.getSchemaName(), 
actualFixture.getTotalOrders(), actualFixture.getRuntimeDatabases(), 
actualFixture::close);
-        } catch (final SQLException ex) {
-            throw new IOException(ex);
-        }
-    }
-    
-    /**
-     * Runtime fixture.
-     */
-    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
-    public static final class Fixture implements AutoCloseable {
-        
-        private final String schemaName;
-        
-        private final int totalOrders;
-        
-        private final Map<String, RuntimeDatabaseConfiguration> 
runtimeDatabases;
-        
-        private final Runnable closeAction;
-        
-        /**
-         * Get schema name.
-         *
-         * @return schema name
-         */
-        public String schemaName() {
-            return schemaName;
-        }
-        
-        /**
-         * Get total orders.
-         *
-         * @return total orders
-         */
-        public int totalOrders() {
-            return totalOrders;
-        }
-        
-        /**
-         * Get runtime databases.
-         *
-         * @return runtime databases
-         */
-        public Map<String, RuntimeDatabaseConfiguration> runtimeDatabases() {
-            return runtimeDatabases;
-        }
-        
-        @Override
-        public void close() {
-            closeAction.run();
-        }
-    }
-}
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeFixtureFactoryTest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeFixtureFactoryTest.java
deleted file mode 100644
index 053e03c5a78..00000000000
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeFixtureFactoryTest.java
+++ /dev/null
@@ -1,42 +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.test.e2e.mcp.llm.fixture;
-
-import 
org.apache.shardingsphere.test.e2e.mcp.support.runtime.MySQLRuntimeTestSupport;
-import org.junit.jupiter.api.Test;
-import org.mockito.MockedStatic;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.mockito.Mockito.mockStatic;
-
-class LLMRuntimeFixtureFactoryTest {
-    
-    @Test
-    void assertDockerUnavailableFails() {
-        try (MockedStatic<MySQLRuntimeTestSupport> mocked = 
mockStatic(MySQLRuntimeTestSupport.class)) {
-            
mocked.when(MySQLRuntimeTestSupport::isDockerAvailable).thenReturn(false);
-            mocked.when(() -> 
MySQLRuntimeTestSupport.createDockerRequiredMessage("Docker is required."))
-                    .thenReturn("Docker is required.");
-            IllegalStateException actual = 
assertThrows(IllegalStateException.class,
-                    () -> new 
LLMRuntimeFixtureFactory().createMySQLFixture("logic_db", "Docker is 
required."));
-            assertThat(actual.getMessage(), is("Docker is required."));
-        }
-    }
-}
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/OfficialMCPToolNames.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/OfficialMCPToolNames.java
deleted file mode 100644
index f760c8efdea..00000000000
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/OfficialMCPToolNames.java
+++ /dev/null
@@ -1,43 +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.test.e2e.mcp.support;
-
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
-import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolDescriptor;
-import org.apache.shardingsphere.mcp.core.tool.handler.ToolDefinitionRegistry;
-
-import java.util.List;
-
-/**
- * Official MCP tool names packaged by default.
- */
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-public final class OfficialMCPToolNames {
-    
-    private static final List<String> ALL = 
ToolDefinitionRegistry.getSupportedToolDescriptors().stream().map(MCPToolDescriptor::getName).toList();
-    
-    /**
-     * Get official MCP tool names.
-     *
-     * @return official MCP tool names
-     */
-    public static List<String> getAll() {
-        return ALL;
-    }
-}
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/artifact/MCPArtifactUtils.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/artifact/MCPArtifactUtils.java
new file mode 100644
index 00000000000..db638aeb388
--- /dev/null
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/artifact/MCPArtifactUtils.java
@@ -0,0 +1,121 @@
+/*
+ * 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.artifact;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * MCP E2E artifact utilities.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class MCPArtifactUtils {
+    
+    private static final String ARTIFACT_ROOT_PROPERTY = 
"mcp.e2e.artifact-root";
+    
+    private static final int MAX_RUNTIME_LOG_CHARS = 4096;
+    
+    private static final String TRUNCATION_MARKER = "...<truncated>";
+    
+    private static final Pattern JSON_SECRET_FIELD_PATTERN = Pattern.compile(
+            
"(?i)(\"(?:api[_-]?key|access[_-]?token|token|authorization|password|passwd|pwd|secret)\"\\s*:\\s*\")((?:\\\\.|[^\"\\\\])*)(\")");
+    
+    private static final Pattern SECRET_ASSIGNMENT_PATTERN = Pattern.compile(
+            
"(?i)(?<![a-z0-9_])((?:api[_-]?key|access[_-]?token|token|authorization|password|passwd|pwd|secret)\\s*[:=]\\s*)([^\\r\\n]+)");
+    
+    private static final Pattern BEARER_TOKEN_PATTERN = 
Pattern.compile("(?i)(Bearer\\s+)[A-Za-z0-9._~+/=-]+");
+    
+    private static final Pattern JDBC_URL_PATTERN = 
Pattern.compile("(?i)jdbc:[^\\s\"']+");
+    
+    private static final Pattern URI_USER_INFO_PATTERN = 
Pattern.compile("(?i)([a-z][a-z0-9+.-]*://[^:/\\s\"']+:)[^@\\s\"']+(@)");
+    
+    /**
+     * Redact secret-shaped fields and concrete sensitive values.
+     *
+     * @param value artifact value
+     * @param sensitiveValues concrete sensitive values
+     * @return redacted value
+     */
+    public static String redact(final String value, final Collection<String> 
sensitiveValues) {
+        String result = value;
+        for (String each : sensitiveValues) {
+            if (!each.isBlank()) {
+                result = result.replace(each, "<redacted>");
+            }
+        }
+        result = 
JSON_SECRET_FIELD_PATTERN.matcher(result).replaceAll("$1<redacted>$3");
+        result = 
SECRET_ASSIGNMENT_PATTERN.matcher(result).replaceAll("$1<redacted>");
+        result = 
BEARER_TOKEN_PATTERN.matcher(result).replaceAll("$1<redacted>");
+        result = 
JDBC_URL_PATTERN.matcher(result).replaceAll("<redacted-jdbc-url>");
+        return 
URI_USER_INFO_PATTERN.matcher(result).replaceAll("$1<redacted>$2");
+    }
+    
+    /**
+     * Write one bounded and redacted runtime log when the artifact root is 
configured.
+     *
+     * @param filePrefix artifact file prefix
+     * @param outputMessages runtime output messages
+     */
+    public static void writeRuntimeLogIfConfigured(final String filePrefix, 
final Collection<String> outputMessages) {
+        String artifactRoot = System.getProperty(ARTIFACT_ROOT_PROPERTY, 
"").trim();
+        if (artifactRoot.isEmpty() || outputMessages.isEmpty()) {
+            return;
+        }
+        try {
+            writeArtifact(Path.of(artifactRoot), filePrefix, 
redactAndBoundRuntimeLog(String.join(System.lineSeparator(), outputMessages)));
+        } catch (final IOException ignored) {
+        }
+    }
+    
+    /**
+     * Copy one bounded and redacted runtime log when the artifact root is 
configured.
+     *
+     * @param filePrefix artifact file prefix
+     * @param runtimeLog runtime log
+     */
+    public static void copyRuntimeLogIfConfigured(final String filePrefix, 
final Path runtimeLog) {
+        String artifactRoot = System.getProperty(ARTIFACT_ROOT_PROPERTY, 
"").trim();
+        if (artifactRoot.isEmpty() || !Files.isRegularFile(runtimeLog)) {
+            return;
+        }
+        try {
+            writeArtifact(Path.of(artifactRoot), filePrefix, 
redactAndBoundRuntimeLog(Files.readString(runtimeLog, StandardCharsets.UTF_8)));
+        } catch (final IOException ignored) {
+        }
+    }
+    
+    private static void writeArtifact(final Path artifactDirectory, final 
String filePrefix, final String content) throws IOException {
+        Files.createDirectories(artifactDirectory);
+        Files.writeString(Files.createTempFile(artifactDirectory, filePrefix, 
".log"), content);
+    }
+    
+    private static String redactAndBoundRuntimeLog(final String content) {
+        String redactedContent = redact(content, List.of());
+        return redactedContent.length() <= MAX_RUNTIME_LOG_CHARS
+                ? redactedContent
+                : TRUNCATION_MARKER + 
redactedContent.substring(redactedContent.length() - MAX_RUNTIME_LOG_CHARS);
+    }
+}
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 2acd33151dd..198283acfc3 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
@@ -18,6 +18,7 @@
 package org.apache.shardingsphere.test.e2e.mcp.support.distribution;
 
 import lombok.RequiredArgsConstructor;
+import 
org.apache.shardingsphere.test.e2e.mcp.support.artifact.MCPArtifactUtils;
 import org.apache.shardingsphere.test.e2e.mcp.support.runtime.ReadinessProbe;
 import 
org.apache.shardingsphere.test.e2e.mcp.support.runtime.ReadinessProbe.ReadinessResult;
 import 
org.apache.shardingsphere.test.e2e.mcp.support.transport.client.MCPHttpInteractionClient;
@@ -95,6 +96,7 @@ public final class DockerImageHttpRuntime implements 
AutoCloseable {
         } catch (final InterruptedException ignored) {
             Thread.currentThread().interrupt();
         } finally {
+            MCPArtifactUtils.writeRuntimeLogIfConfigured("docker-image-http-", 
outputMessages);
             process = null;
             outputCollector = null;
             httpPort = 0;
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionProcessSupport.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionProcessSupport.java
index bf8b6986b51..735c01556a1 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionProcessSupport.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionProcessSupport.java
@@ -18,6 +18,7 @@
 package org.apache.shardingsphere.test.e2e.mcp.support.distribution;
 
 import lombok.Getter;
+import 
org.apache.shardingsphere.test.e2e.mcp.support.artifact.MCPArtifactUtils;
 import 
org.apache.shardingsphere.test.e2e.mcp.support.distribution.PackagedDistributionTestSupport.PreparedPackagedDistribution;
 
 import java.io.BufferedReader;
@@ -123,6 +124,8 @@ public final class PackagedDistributionProcessSupport 
implements AutoCloseable {
             } catch (final InterruptedException ignored) {
                 Thread.currentThread().interrupt();
             } finally {
+                
MCPArtifactUtils.writeRuntimeLogIfConfigured(outputCollectorThreadName + 
"-process-", outputMessages);
+                
MCPArtifactUtils.copyRuntimeLogIfConfigured(outputCollectorThreadName + 
"-server-", distributionHome.resolve("logs/mcp.log"));
                 process = null;
                 outputCollector = null;
                 outputMessages.clear();
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowCustomEncryptAlgorithmFixtureTest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowCustomEncryptAlgorithmFixtureTest.java
deleted file mode 100644
index f392cf3d6c0..00000000000
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowCustomEncryptAlgorithmFixtureTest.java
+++ /dev/null
@@ -1,65 +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.test.e2e.mcp.support.fixture;
-
-import org.apache.shardingsphere.encrypt.spi.EncryptAlgorithmMetaData;
-import 
org.apache.shardingsphere.infra.algorithm.core.config.AlgorithmConfiguration;
-import org.junit.jupiter.api.Test;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-class MCPWorkflowCustomEncryptAlgorithmFixtureTest {
-    
-    @Test
-    void assertEncrypt() {
-        assertThat(new 
MCPWorkflowCustomEncryptAlgorithmFixture().encrypt("plain", null), 
is("mcp_custom:plain"));
-    }
-    
-    @Test
-    void assertDecrypt() {
-        assertThat(new 
MCPWorkflowCustomEncryptAlgorithmFixture().decrypt("mcp_custom:plain", null), 
is("plain"));
-    }
-    
-    @Test
-    void assertDecryptWithRawValue() {
-        assertThat(new 
MCPWorkflowCustomEncryptAlgorithmFixture().decrypt("plain", null), is("plain"));
-    }
-    
-    @Test
-    void assertGetMetaData() {
-        EncryptAlgorithmMetaData actual = new 
MCPWorkflowCustomEncryptAlgorithmFixture().getMetaData();
-        assertTrue(actual.isSupportDecrypt());
-        assertFalse(actual.isSupportEquivalentFilter());
-        assertFalse(actual.isSupportLike());
-    }
-    
-    @Test
-    void assertToConfiguration() {
-        AlgorithmConfiguration actual = new 
MCPWorkflowCustomEncryptAlgorithmFixture().toConfiguration();
-        assertThat(actual.getType(), is("MCP_CUSTOM_REVERSIBLE"));
-        assertTrue(actual.getProps().isEmpty());
-    }
-    
-    @Test
-    void assertGetType() {
-        assertThat(new MCPWorkflowCustomEncryptAlgorithmFixture().getType(), 
is("MCP_CUSTOM_REVERSIBLE"));
-    }
-}
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowCustomMaskAlgorithmFixtureTest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowCustomMaskAlgorithmFixtureTest.java
deleted file mode 100644
index 3dcc2341802..00000000000
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowCustomMaskAlgorithmFixtureTest.java
+++ /dev/null
@@ -1,36 +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.test.e2e.mcp.support.fixture;
-
-import org.junit.jupiter.api.Test;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-class MCPWorkflowCustomMaskAlgorithmFixtureTest {
-    
-    @Test
-    void assertMask() {
-        assertThat(new MCPWorkflowCustomMaskAlgorithmFixture().mask("plain"), 
is("mask:plain"));
-    }
-    
-    @Test
-    void assertGetType() {
-        assertThat(new MCPWorkflowCustomMaskAlgorithmFixture().getType(), 
is("MCP_MASK_CUSTOM"));
-    }
-}
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowSecretReferenceFixture.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowSecretReferenceFixture.java
deleted file mode 100644
index 12459d78d7d..00000000000
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowSecretReferenceFixture.java
+++ /dev/null
@@ -1,56 +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.test.e2e.mcp.support.fixture;
-
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
-
-import java.util.Map;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-
-/**
- * Secret reference fixture for MCP workflow E2E tests.
- */
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-public final class MCPWorkflowSecretReferenceFixture {
-    
-    public static final String SECRET_REF = "placeholder://secret-value-1";
-    
-    public static final String INPUT_LABEL = "placeholder-secret-1";
-    
-    /**
-     * Create secret reference input object.
-     *
-     * @return secret reference input object
-     */
-    public static Map<String, Object> createSecretReferenceInput() {
-        return Map.of("secret_ref", SECRET_REF, "label", INPUT_LABEL);
-    }
-    
-    /**
-     * Assert model-facing payload does not expose reference details.
-     *
-     * @param payload model-facing payload
-     */
-    public static void assertSecretReferenceRedacted(final Object payload) {
-        String actual = String.valueOf(payload);
-        assertFalse(actual.contains(SECRET_REF));
-        assertFalse(actual.contains(INPUT_LABEL));
-    }
-}
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixtureHandlerProviderTest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixtureHandlerProviderTest.java
deleted file mode 100644
index b9996681bcf..00000000000
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixtureHandlerProviderTest.java
+++ /dev/null
@@ -1,51 +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.test.e2e.mcp.support.fixture.plugin;
-
-import 
org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceHandler;
-import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolHandler;
-import org.apache.shardingsphere.mcp.api.MCPRequestContext;
-import org.junit.jupiter.api.Test;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-class PluginFixtureHandlerProviderTest {
-    
-    @Test
-    void assertGetToolHandlers() {
-        MCPToolHandler<?> actual = new 
PluginFixtureHandlerProvider().getToolHandlers().iterator().next();
-        assertThat(actual.getClass(), is(PluginFixturePingToolHandler.class));
-        assertThat(actual.getToolName(), is("fixture_ping"));
-        assertThat(actual.getContextType(), is(MCPRequestContext.class));
-    }
-    
-    @Test
-    void assertGetResourceHandlers() {
-        MCPResourceHandler<?> actual = new 
PluginFixtureHandlerProvider().getResourceHandlers().iterator().next();
-        assertThat(actual.getClass(), 
is(PluginFixtureStatusResourceHandler.class));
-        assertThat(actual.getResourceUriTemplate(), 
is("shardingsphere://features/test-fixture/status"));
-        assertThat(actual.getContextType(), is(MCPRequestContext.class));
-    }
-    
-    @Test
-    void assertGetCompletionHandlers() {
-        assertTrue(new 
PluginFixtureHandlerProvider().getCompletionHandlers().isEmpty());
-    }
-}
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixturePingToolHandlerTest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixturePingToolHandlerTest.java
deleted file mode 100644
index c683f127532..00000000000
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixturePingToolHandlerTest.java
+++ /dev/null
@@ -1,34 +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.test.e2e.mcp.support.fixture.plugin;
-
-import org.junit.jupiter.api.Test;
-
-import java.util.Map;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-class PluginFixturePingToolHandlerTest {
-    
-    @Test
-    void assertHandle() {
-        assertThat(new PluginFixturePingToolHandler().handle(null, 
Map.of("message", "hello")).toPayload(),
-                is(Map.of("status", "ready", "echo", "hello")));
-    }
-}
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixtureStatusResourceHandlerTest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixtureStatusResourceHandlerTest.java
deleted file mode 100644
index c9ac7f19d25..00000000000
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixtureStatusResourceHandlerTest.java
+++ /dev/null
@@ -1,36 +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.test.e2e.mcp.support.fixture.plugin;
-
-import 
org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceURIVariables;
-import org.junit.jupiter.api.Test;
-
-import java.util.List;
-import java.util.Map;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-class PluginFixtureStatusResourceHandlerTest {
-    
-    @Test
-    void assertHandle() {
-        assertThat(new PluginFixtureStatusResourceHandler().handle(null, new 
MCPResourceURIVariables(Map.of())).toPayload(),
-                is(Map.of("items", List.of(Map.of("feature", "test-fixture", 
"status", "ready")))));
-    }
-}
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/MCPPayloadAssertionsTest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/MCPPayloadAssertionsTest.java
deleted file mode 100644
index dbf091aca5a..00000000000
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/MCPPayloadAssertionsTest.java
+++ /dev/null
@@ -1,53 +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.test.e2e.mcp.support.transport;
-
-import org.junit.jupiter.api.Test;
-
-import java.util.List;
-import java.util.Map;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-class MCPPayloadAssertionsTest {
-    
-    @Test
-    void assertAssertSingleItemValue() {
-        MCPPayloadAssertions.assertSingleItemValue(Map.of("items", 
List.of(Map.of("name", "orders"))), "name", "orders");
-    }
-    
-    @Test
-    void assertAssertItemValues() {
-        MCPPayloadAssertions.assertItemValues(createPayload(), "name", 
List.of("orders", "users"));
-    }
-    
-    @Test
-    void assertGetSingleItem() {
-        assertThat(MCPPayloadAssertions.getSingleItem(Map.of("items", 
List.of(Map.of("name", "orders")))), is(Map.of("name", "orders")));
-    }
-    
-    @Test
-    void assertFindItem() {
-        assertThat(MCPPayloadAssertions.findItem(createPayload(), "name", 
"users"), is(Map.of("name", "users", "type", "table")));
-    }
-    
-    private Map<String, Object> createPayload() {
-        return Map.of("items", List.of(Map.of("name", "orders", "type", 
"table"), Map.of("name", "users", "type", "table")));
-    }
-}
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/client/AbstractProcessMCPStdioInteractionClient.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/client/AbstractProcessMCPStdioInteractionClient.java
index 0c7087cc417..0e86085c37d 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/client/AbstractProcessMCPStdioInteractionClient.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/client/AbstractProcessMCPStdioInteractionClient.java
@@ -19,6 +19,7 @@ package 
org.apache.shardingsphere.test.e2e.mcp.support.transport.client;
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import 
org.apache.shardingsphere.test.e2e.mcp.support.artifact.MCPArtifactUtils;
 import 
org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPInteractionPayloads;
 import 
org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPInteractionProtocolSupport;
 
@@ -277,6 +278,7 @@ abstract class AbstractProcessMCPStdioInteractionClient 
extends AbstractMCPInter
         writer = null;
         stdErrorCollector = null;
         initializePayload = Map.of();
+        MCPArtifactUtils.writeRuntimeLogIfConfigured(getClientName() + "-", 
stdErrorMessages);
         stdErrorMessages.clear();
     }
     
diff --git a/test/e2e/mcp/src/test/resources/proxy/workflow/cluster/global.yaml 
b/test/e2e/mcp/src/test/resources/proxy/workflow/cluster/global.yaml
index f4a368cc40d..bb470af23e4 100644
--- a/test/e2e/mcp/src/test/resources/proxy/workflow/cluster/global.yaml
+++ b/test/e2e/mcp/src/test/resources/proxy/workflow/cluster/global.yaml
@@ -22,6 +22,8 @@ mode:
     props:
       namespace: mcp_workflow
       server-lists: zk.workflow.host:2181
+      maxRetries: 10
+      operationTimeoutMilliseconds: 5000
 
 authority:
   users:

Reply via email to