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 ecb7c87c9da Run MCP E2E tests by wildcard (#38791)
ecb7c87c9da is described below

commit ecb7c87c9da4e20986e993a3eb303176b5fd4bcd
Author: Liang Zhang <[email protected]>
AuthorDate: Thu Jun 4 10:48:59 2026 +0800

    Run MCP E2E tests by wildcard (#38791)
    
    - Replace hardcoded MCP E2E test class list with *E2ETest
    - Remove MCP-specific enable flags and llm-e2e profile
    - Use e2e-env.properties with system property overrides for MCP E2E config
    - Build MCP distribution image before workflow E2E execution
    - Stabilize LLM resource discovery and MySQL readiness in full Docker E2E
---
 .github/workflows/e2e-mcp.yml                      | 32 ++++++--------
 .github/workflows/release-mcp.yml                  | 15 +++----
 .../content/test-manual/mcp-e2e-test/_index.cn.md  | 31 ++++++++------
 .../content/test-manual/mcp-e2e-test/_index.en.md  | 31 ++++++++------
 test/e2e/mcp/pom.xml                               | 29 -------------
 .../test/e2e/mcp/env/MCPE2ECondition.java          | 40 ++----------------
 .../test/e2e/mcp/env/MCPE2ETestConfiguration.java  | 37 ++--------------
 .../e2e/mcp/env/MCPE2ETestConfigurationTest.java   | 45 ++++++--------------
 .../e2e/mcp/llm/config/LLME2EConfiguration.java    | 40 +++++++++---------
 .../LLMMCPModelFacingToolResponseFormatter.java    | 13 ++++++
 ...LLMMCPModelFacingToolResponseFormatterTest.java | 49 ++++++++++++++++++++++
 .../e2e/mcp/llm/suite/smoke/LLMSmokeE2ETest.java   |  2 +-
 .../suite/usability/LLMUsabilitySuiteE2ETest.java  |  2 +-
 .../AbstractProductionMySQLRuntimeE2ETest.java     | 11 +----
 .../HttpProductionProxyEncryptWorkflowE2ETest.java | 30 ++++++-------
 .../HttpProductionProxyMaskWorkflowE2ETest.java    | 19 +++++----
 .../production/PackagedDistributionE2ETest.java    | 15 +++----
 .../ExecuteQueryTransactionE2ETest.java            |  2 +-
 .../HttpTransportApprovalSafetyE2ETest.java        |  2 +-
 .../HttpTransportBaselineContractE2ETest.java      |  2 +-
 .../HttpTransportCompletionE2ETest.java            |  2 +-
 .../programmatic/HttpTransportContractE2ETest.java |  2 +-
 .../HttpTransportProtocolContractE2ETest.java      |  2 +-
 .../programmatic/HttpTransportRecoveryE2ETest.java |  2 +-
 .../programmatic/HttpTransportSecurityE2ETest.java |  2 +-
 .../HttpTransportSessionLifecycleE2ETest.java      |  2 +-
 .../programmatic/MetadataDiscoveryE2ETest.java     |  2 +-
 .../PackagedDistributionTestSupport.java           |  6 ++-
 .../PackagedDistributionTestSupportTest.java       |  2 +-
 .../support/runtime/MySQLRuntimeTestSupport.java   | 27 +++++++++---
 .../mcp/src/test/resources/env/e2e-env.properties  | 25 ++++++++---
 31 files changed, 254 insertions(+), 267 deletions(-)

diff --git a/.github/workflows/e2e-mcp.yml b/.github/workflows/e2e-mcp.yml
index d5c9a094ac1..0580713a9e4 100644
--- a/.github/workflows/e2e-mcp.yml
+++ b/.github/workflows/e2e-mcp.yml
@@ -44,11 +44,8 @@ permissions:
 
 env:
   MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false 
-Dmaven.wagon.http.retryHandler.class=standard 
-Dmaven.wagon.http.retryHandler.count=3 -Dspotless.apply.skip=true
-  MCP_LLM_READY_TIMEOUT_SECONDS: '900'
-  MCP_LLM_REQUEST_TIMEOUT_SECONDS: '300'
-  MCP_LLM_ARTIFACT_ROOT: test/e2e/mcp/target/llm-e2e
   MCP_LLM_SERVER_IMAGE: apache/shardingsphere-mcp-llm-runtime:local
-  MCP_LLM_BASE_SERVER_IMAGE_DIGEST: 
sha256:988d2695631987e28a29d98970aaf0e979e23b843a26824abb790ac4245d1d57
+  MCP_DISTRIBUTION_IMAGE: apache/shardingsphere-mcp-e2e:local
 
 jobs:
   e2e-mcp-mysql-runtime:
@@ -56,8 +53,6 @@ jobs:
     if: github.repository == 'apache/shardingsphere'
     runs-on: ubuntu-latest
     timeout-minutes: 60
-    env:
-      MCP_LLM_RUN_ID: gha-${{ github.run_id }}-${{ github.run_attempt }}
     steps:
       - uses: actions/[email protected]
       - uses: ./.github/workflows/resources/actions/setup-build-environment
@@ -79,30 +74,29 @@ jobs:
         with:
           context: test/e2e/mcp/src/test/resources/docker/llm-runtime
           file: test/e2e/mcp/src/test/resources/docker/llm-runtime/Dockerfile
-          build-args: |
-            BASE_IMAGE=ghcr.io/ggml-org/llama.cpp@${{ 
env.MCP_LLM_BASE_SERVER_IMAGE_DIGEST }}
           tags: ${{ env.MCP_LLM_SERVER_IMAGE }}
           load: true
           cache-from: type=gha,scope=mcp-llm-runtime
           cache-to: type=gha,mode=max,scope=mcp-llm-runtime,ignore-error=true
       - name: Build MCP E2E Test Dependencies
         run: ./mvnw -pl test/e2e/mcp -am install -DskipTests -DskipITs 
-Dspotless.skip=true -B -ntp
-      - name: Run MCP MySQL Runtime E2E
+      - name: Package MCP Distribution
+        run: ./mvnw -pl distribution/mcp -am -DskipTests package -B -ntp
+      - name: Build MCP Distribution Image
+        run: docker build -f distribution/mcp/Dockerfile -t "${{ 
env.MCP_DISTRIBUTION_IMAGE }}" distribution/mcp/target
+      - name: Run MCP E2E
         shell: bash
         run: |
           set -euo pipefail
-          
MCP_E2E_TESTS=HttpTransportContractE2ETest,HttpTransportProtocolContractE2ETest,HttpTransportBaselineContractE2ETest
-          MCP_E2E_TESTS="${MCP_E2E_TESTS},ProductionMySQLRuntimeE2ETest"
-          
MCP_E2E_TESTS="${MCP_E2E_TESTS},HttpProductionProxyEncryptWorkflowE2ETest"
-          
MCP_E2E_TESTS="${MCP_E2E_TESTS},HttpProductionProxyMaskWorkflowE2ETest,LLMUsabilitySuiteE2ETest"
           ./mvnw -pl test/e2e/mcp test -DskipITs -Dspotless.skip=true \
-            -Dtest="${MCP_E2E_TESTS}" \
+            -Dtest='*E2ETest' \
             -Dsurefire.failIfNoSpecifiedTests=true \
-            -Dmcp.e2e.contract.enabled=true \
-            -Dmcp.e2e.production.mysql.enabled=true \
-            -Dmcp.e2e.production.stdio.enabled=true \
-            -Dmcp.e2e.llm.enabled=true \
-            -Dmcp.e2e.llm.excludedGroups= \
+            -De2e.run.type=DOCKER \
+            -Dmcp.e2e.container.image="${{ env.MCP_DISTRIBUTION_IMAGE }}" \
+            -Dmcp.llm.ready-timeout-seconds=900 \
+            -Dmcp.llm.request-timeout-seconds=300 \
+            -Dmcp.llm.artifact-root="${{ github.workspace 
}}/test/e2e/mcp/target/llm-e2e" \
+            -Dmcp.llm.run-id=gha-${{ github.run_id }}-${{ github.run_attempt 
}} \
             -B -ntp
       - name: Upload MCP E2E Artifacts
         if: always()
diff --git a/.github/workflows/release-mcp.yml 
b/.github/workflows/release-mcp.yml
index 0ae027ee54a..dd20ef53332 100644
--- a/.github/workflows/release-mcp.yml
+++ b/.github/workflows/release-mcp.yml
@@ -37,8 +37,6 @@ permissions:
 env:
   MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false 
-Dmaven.wagon.http.retryHandler.class=standard 
-Dmaven.wagon.http.retryHandler.count=3 -Dspotless.apply.skip=true
   MCP_IMAGE: ghcr.io/apache/shardingsphere-mcp
-  MCP_PUBLISHER_LINUX_AMD64_SHA256: 
ab128162b0616090b47cf245afe0a23f3ef08936fdce19074f5ba0a4469281ac
-  MCP_PUBLISHER_LINUX_ARM64_SHA256: 
04f5199b3deef8e6fc4d6ed98c56a74f799def53edca3fe6d4862ecd4397c172
   MCP_PUBLISHER_VERSION: v1.7.9
   MCP_SERVER_NAME: io.github.apache/shardingsphere-mcp
 
@@ -104,7 +102,7 @@ jobs:
           ./mvnw -pl test/e2e/mcp test -DskipITs -Dspotless.skip=true \
             -Dtest=PackagedDistributionE2ETest \
             -Dsurefire.failIfNoSpecifiedTests=true \
-            -Dmcp.e2e.distribution.enabled=true \
+            -De2e.run.type=DOCKER \
             -Dmcp.e2e.container.image=shardingsphere-mcp-release:local \
             -Dmcp.distribution.home="${DISTRIBUTION_HOME}" \
             -B -ntp
@@ -162,7 +160,7 @@ jobs:
           ./mvnw -pl test/e2e/mcp test -DskipITs -Dspotless.skip=true \
             
-Dtest=PackagedDistributionE2ETest#assertLaunchContainerOverHttp+assertLaunchContainerOverStdio
 \
             -Dsurefire.failIfNoSpecifiedTests=true \
-            -Dmcp.e2e.distribution.enabled=true \
+            -De2e.run.type=DOCKER \
             -Dmcp.e2e.container.image="${{ env.MCP_IMAGE }}@${{ 
steps.push-image.outputs.digest }}" \
             -B -ntp
       - name: Install MCP Publisher
@@ -172,20 +170,23 @@ jobs:
           case "$(uname -m)" in
             x86_64 | amd64)
               MCP_PUBLISHER_ARCHIVE="mcp-publisher_linux_amd64.tar.gz"
-              MCP_PUBLISHER_SHA256="${MCP_PUBLISHER_LINUX_AMD64_SHA256}"
               ;;
             aarch64 | arm64)
               MCP_PUBLISHER_ARCHIVE="mcp-publisher_linux_arm64.tar.gz"
-              MCP_PUBLISHER_SHA256="${MCP_PUBLISHER_LINUX_ARM64_SHA256}"
               ;;
             *)
               echo "Unsupported MCP Publisher architecture: $(uname -m)" 1>&2
               exit 1
               ;;
           esac
+          
MCP_PUBLISHER_CHECKSUMS="registry_${MCP_PUBLISHER_VERSION#v}_checksums.txt"
           curl -fsSL --retry 3 --output "${MCP_PUBLISHER_ARCHIVE}" \
             
"https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}/${MCP_PUBLISHER_ARCHIVE}";
-          printf '%s  %s\n' "${MCP_PUBLISHER_SHA256}" 
"${MCP_PUBLISHER_ARCHIVE}" | sha256sum -c -
+          curl -fsSL --retry 3 --output "${MCP_PUBLISHER_CHECKSUMS}" \
+            
"https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}/${MCP_PUBLISHER_CHECKSUMS}";
+          awk -v archive="${MCP_PUBLISHER_ARCHIVE}" '$2 == archive {print; 
found = 1} END {exit found ? 0 : 1}' \
+            "${MCP_PUBLISHER_CHECKSUMS}" > "${MCP_PUBLISHER_ARCHIVE}.sha256"
+          sha256sum -c "${MCP_PUBLISHER_ARCHIVE}.sha256"
           tar xzf "${MCP_PUBLISHER_ARCHIVE}" mcp-publisher
       - name: Authenticate to MCP Registry
         working-directory: mcp
diff --git a/docs/document/content/test-manual/mcp-e2e-test/_index.cn.md 
b/docs/document/content/test-manual/mcp-e2e-test/_index.cn.md
index 0031d7fa1fd..b4e099eb7d4 100644
--- a/docs/document/content/test-manual/mcp-e2e-test/_index.cn.md
+++ b/docs/document/content/test-manual/mcp-e2e-test/_index.cn.md
@@ -27,6 +27,13 @@ chapter = true
 ./mvnw -pl test/e2e/mcp -am install -DskipTests -DskipITs -Dspotless.skip=true 
-B -ntp
 ```
 
+打包 MCP distribution 并构建本地 distribution image:
+
+```bash
+./mvnw -pl distribution/mcp -am -DskipTests package -B -ntp
+docker build -f distribution/mcp/Dockerfile -t 
apache/shardingsphere-mcp-e2e:local distribution/mcp/target
+```
+
 ## LLM Runtime
 
 MCP LLM lane 默认使用本地 Docker image 承载 OpenAI-compatible endpoint。
@@ -50,27 +57,24 @@ sh 
test/e2e/mcp/src/test/resources/docker/llm-runtime/build-local.sh
 
 ## 运行 MCP Runtime E2E
 
+MCP E2E 运行配置集中在 `test/e2e/mcp/src/test/resources/env/e2e-env.properties`。
+本地运行时可以直接修改该文件,也可以使用同名 `-D` 系统参数覆盖。
+
 ```bash
-MCP_E2E_TESTS=HttpTransportContractE2ETest,HttpTransportProtocolContractE2ETest,HttpTransportBaselineContractE2ETest
-MCP_E2E_TESTS="${MCP_E2E_TESTS},ProductionMySQLRuntimeE2ETest"
-MCP_E2E_TESTS="${MCP_E2E_TESTS},HttpProductionProxyEncryptWorkflowE2ETest"
-MCP_E2E_TESTS="${MCP_E2E_TESTS},HttpProductionProxyMaskWorkflowE2ETest,LLMUsabilitySuiteE2ETest"
 ./mvnw -pl test/e2e/mcp test -DskipITs -Dspotless.skip=true \
-  -Dtest="${MCP_E2E_TESTS}" \
+  -Dtest='*E2ETest' \
   -Dsurefire.failIfNoSpecifiedTests=true \
-  -Dmcp.e2e.contract.enabled=true \
-  -Dmcp.e2e.production.mysql.enabled=true \
-  -Dmcp.e2e.production.stdio.enabled=true \
-  -Dmcp.e2e.llm.enabled=true \
-  -Dmcp.e2e.llm.excludedGroups=
+  -De2e.run.type=DOCKER \
+  -Dmcp.e2e.container.image=apache/shardingsphere-mcp-e2e:local
 ```
 
 ## 运行 LLM Usability Suite
 
 ```bash
-./mvnw -pl test/e2e/mcp -Pllm-e2e test -DskipITs -Dspotless.skip=true \
+./mvnw -pl test/e2e/mcp test -DskipITs -Dspotless.skip=true \
   -Dtest=LLMUsabilitySuiteE2ETest \
-  -Dsurefire.failIfNoSpecifiedTests=true
+  -Dsurefire.failIfNoSpecifiedTests=true \
+  -De2e.run.type=DOCKER
 ```
 
 ## External Debug
@@ -78,8 +82,9 @@ 
MCP_E2E_TESTS="${MCP_E2E_TESTS},HttpProductionProxyMaskWorkflowE2ETest,LLMUsabil
 仅本地调试时,可以连接已经运行的 OpenAI-compatible endpoint:
 
 ```bash
-./mvnw -pl test/e2e/mcp -Pllm-e2e test -DskipITs -Dspotless.skip=true \
+./mvnw -pl test/e2e/mcp test -DskipITs -Dspotless.skip=true \
   -Dtest=LLMUsabilitySuiteE2ETest \
+  -De2e.run.type=DOCKER \
   -Dmcp.llm.runtime-mode=external-debug \
   -Dmcp.llm.base-url=http://127.0.0.1:8080/v1 \
   -Dsurefire.failIfNoSpecifiedTests=true
diff --git a/docs/document/content/test-manual/mcp-e2e-test/_index.en.md 
b/docs/document/content/test-manual/mcp-e2e-test/_index.en.md
index b9afe93aff5..c7661e40b2c 100644
--- a/docs/document/content/test-manual/mcp-e2e-test/_index.en.md
+++ b/docs/document/content/test-manual/mcp-e2e-test/_index.en.md
@@ -27,6 +27,13 @@ Install MCP E2E dependency modules into the local repository 
first:
 ./mvnw -pl test/e2e/mcp -am install -DskipTests -DskipITs -Dspotless.skip=true 
-B -ntp
 ```
 
+Package the MCP distribution and build the local distribution image:
+
+```bash
+./mvnw -pl distribution/mcp -am -DskipTests package -B -ntp
+docker build -f distribution/mcp/Dockerfile -t 
apache/shardingsphere-mcp-e2e:local distribution/mcp/target
+```
+
 ## LLM Runtime
 
 The MCP LLM lane uses a local Docker image to host an OpenAI-compatible 
endpoint.
@@ -50,27 +57,24 @@ sh 
test/e2e/mcp/src/test/resources/docker/llm-runtime/build-local.sh
 
 ## Run MCP Runtime E2E
 
+MCP E2E runtime configuration is centralized in 
`test/e2e/mcp/src/test/resources/env/e2e-env.properties`.
+For local runs, edit that file or override the same keys with `-D` system 
properties.
+
 ```bash
-MCP_E2E_TESTS=HttpTransportContractE2ETest,HttpTransportProtocolContractE2ETest,HttpTransportBaselineContractE2ETest
-MCP_E2E_TESTS="${MCP_E2E_TESTS},ProductionMySQLRuntimeE2ETest"
-MCP_E2E_TESTS="${MCP_E2E_TESTS},HttpProductionProxyEncryptWorkflowE2ETest"
-MCP_E2E_TESTS="${MCP_E2E_TESTS},HttpProductionProxyMaskWorkflowE2ETest,LLMUsabilitySuiteE2ETest"
 ./mvnw -pl test/e2e/mcp test -DskipITs -Dspotless.skip=true \
-  -Dtest="${MCP_E2E_TESTS}" \
+  -Dtest='*E2ETest' \
   -Dsurefire.failIfNoSpecifiedTests=true \
-  -Dmcp.e2e.contract.enabled=true \
-  -Dmcp.e2e.production.mysql.enabled=true \
-  -Dmcp.e2e.production.stdio.enabled=true \
-  -Dmcp.e2e.llm.enabled=true \
-  -Dmcp.e2e.llm.excludedGroups=
+  -De2e.run.type=DOCKER \
+  -Dmcp.e2e.container.image=apache/shardingsphere-mcp-e2e:local
 ```
 
 ## Run LLM Usability Suite
 
 ```bash
-./mvnw -pl test/e2e/mcp -Pllm-e2e test -DskipITs -Dspotless.skip=true \
+./mvnw -pl test/e2e/mcp test -DskipITs -Dspotless.skip=true \
   -Dtest=LLMUsabilitySuiteE2ETest \
-  -Dsurefire.failIfNoSpecifiedTests=true
+  -Dsurefire.failIfNoSpecifiedTests=true \
+  -De2e.run.type=DOCKER
 ```
 
 ## External Debug
@@ -78,8 +82,9 @@ 
MCP_E2E_TESTS="${MCP_E2E_TESTS},HttpProductionProxyMaskWorkflowE2ETest,LLMUsabil
 For local debugging only, connect to an already running OpenAI-compatible 
endpoint:
 
 ```bash
-./mvnw -pl test/e2e/mcp -Pllm-e2e test -DskipITs -Dspotless.skip=true \
+./mvnw -pl test/e2e/mcp test -DskipITs -Dspotless.skip=true \
   -Dtest=LLMUsabilitySuiteE2ETest \
+  -De2e.run.type=DOCKER \
   -Dmcp.llm.runtime-mode=external-debug \
   -Dmcp.llm.base-url=http://127.0.0.1:8080/v1 \
   -Dsurefire.failIfNoSpecifiedTests=true
diff --git a/test/e2e/mcp/pom.xml b/test/e2e/mcp/pom.xml
index 3269956432a..84f4129ebaf 100644
--- a/test/e2e/mcp/pom.xml
+++ b/test/e2e/mcp/pom.xml
@@ -32,9 +32,6 @@
         <maven.compiler.target>${java.version}</maven.compiler.target>
         <maven.compiler.release>${java.version}</maven.compiler.release>
         <maven.deploy.skip>true</maven.deploy.skip>
-        <mcp.e2e.llm.groups />
-        <mcp.e2e.llm.excludedGroups>llm-e2e</mcp.e2e.llm.excludedGroups>
-        <mcp.e2e.llm.enabled>false</mcp.e2e.llm.enabled>
     </properties>
     
     <dependencies>
@@ -80,30 +77,4 @@
             <scope>test</scope>
         </dependency>
     </dependencies>
-    
-    <build>
-        <plugins>
-            <plugin>
-                <artifactId>maven-surefire-plugin</artifactId>
-                <configuration>
-                    <groups>${mcp.e2e.llm.groups}</groups>
-                    
<excludedGroups>${mcp.e2e.llm.excludedGroups}</excludedGroups>
-                    <systemPropertyVariables>
-                        
<mcp.e2e.llm.enabled>${mcp.e2e.llm.enabled}</mcp.e2e.llm.enabled>
-                    </systemPropertyVariables>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-    
-    <profiles>
-        <profile>
-            <id>llm-e2e</id>
-            <properties>
-                <mcp.e2e.llm.groups>llm-e2e</mcp.e2e.llm.groups>
-                <mcp.e2e.llm.excludedGroups />
-                <mcp.e2e.llm.enabled>true</mcp.e2e.llm.enabled>
-            </properties>
-        </profile>
-    </profiles>
 </project>
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/env/MCPE2ECondition.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/env/MCPE2ECondition.java
index 76c2341f1b0..2653f7ec1f9 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/env/MCPE2ECondition.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/env/MCPE2ECondition.java
@@ -23,43 +23,11 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public final class MCPE2ECondition {
     
-    public static boolean isContractEnabled() {
-        return isContractEnabled(MCPE2ETestConfiguration.getInstance());
+    public static boolean isDockerEnabled() {
+        return isDockerEnabled(MCPE2ETestConfiguration.getInstance());
     }
     
-    static boolean isContractEnabled(final MCPE2ETestConfiguration config) {
-        return config.isContractEnabled();
-    }
-    
-    public static boolean isProductionMySQLEnabled() {
-        return isProductionMySQLEnabled(MCPE2ETestConfiguration.getInstance());
-    }
-    
-    static boolean isProductionMySQLEnabled(final MCPE2ETestConfiguration 
config) {
-        return config.isProductionMySQLEnabled();
-    }
-    
-    public static boolean isProductionMySQLStdioEnabled() {
-        return 
isProductionMySQLStdioEnabled(MCPE2ETestConfiguration.getInstance());
-    }
-    
-    static boolean isProductionMySQLStdioEnabled(final MCPE2ETestConfiguration 
config) {
-        return config.isProductionMySQLEnabled() && 
config.isProductionStdioEnabled();
-    }
-    
-    public static boolean isDistributionEnabled() {
-        return isDistributionEnabled(MCPE2ETestConfiguration.getInstance());
-    }
-    
-    static boolean isDistributionEnabled(final MCPE2ETestConfiguration config) 
{
-        return config.isDistributionEnabled();
-    }
-    
-    public static boolean isLLMEnabled() {
-        return isLLMEnabled(MCPE2ETestConfiguration.getInstance());
-    }
-    
-    static boolean isLLMEnabled(final MCPE2ETestConfiguration config) {
-        return config.isLLMEnabled();
+    static boolean isDockerEnabled(final MCPE2ETestConfiguration config) {
+        return config.isDockerRunType();
     }
 }
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/env/MCPE2ETestConfiguration.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/env/MCPE2ETestConfiguration.java
index d1763458402..88a99274b96 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/env/MCPE2ETestConfiguration.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/env/MCPE2ETestConfiguration.java
@@ -27,7 +27,7 @@ import java.util.Properties;
 @RequiredArgsConstructor
 final class MCPE2ETestConfiguration {
     
-    private static final MCPE2ETestConfiguration INSTANCE = new 
MCPE2ETestConfiguration(EnvironmentPropertiesLoader.loadProperties("env/e2e-env.properties"));
+    private static final MCPE2ETestConfiguration INSTANCE = new 
MCPE2ETestConfiguration(EnvironmentPropertiesLoader.loadProperties());
     
     private final Properties props;
     
@@ -35,38 +35,7 @@ final class MCPE2ETestConfiguration {
         return INSTANCE;
     }
     
-    boolean isContractEnabled() {
-        return getBoolean("mcp.e2e.contract.enabled", false);
-    }
-    
-    boolean isProductionMySQLEnabled() {
-        return getBoolean("mcp.e2e.production.mysql.enabled", false);
-    }
-    
-    boolean isProductionStdioEnabled() {
-        return getBoolean("mcp.e2e.production.stdio.enabled", false);
-    }
-    
-    boolean isDistributionEnabled() {
-        return getBoolean("mcp.e2e.distribution.enabled", false);
-    }
-    
-    boolean isLLMEnabled() {
-        return getBoolean("mcp.e2e.llm.enabled", false);
-    }
-    
-    private boolean getBoolean(final String key, final boolean defaultValue) {
-        String value = props.getProperty(key);
-        if (null == value) {
-            return defaultValue;
-        }
-        String trimmedValue = value.trim().toLowerCase(Locale.ENGLISH);
-        if ("true".equals(trimmedValue)) {
-            return true;
-        }
-        if ("false".equals(trimmedValue)) {
-            return false;
-        }
-        return defaultValue;
+    boolean isDockerRunType() {
+        return EnvironmentPropertiesLoader.getListValue(props, 
"e2e.run.type").stream().map(each -> 
each.toUpperCase(Locale.ENGLISH)).anyMatch("DOCKER"::equals);
     }
 }
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/env/MCPE2ETestConfigurationTest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/env/MCPE2ETestConfigurationTest.java
index b160b80e392..d00f5f61cf7 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/env/MCPE2ETestConfigurationTest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/env/MCPE2ETestConfigurationTest.java
@@ -27,52 +27,33 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 class MCPE2ETestConfigurationTest {
     
     @Test
-    void assertDefaultLaneValues() {
+    void assertDefaultRunType() {
         MCPE2ETestConfiguration config = new MCPE2ETestConfiguration(new 
Properties());
-        assertFalse(config.isContractEnabled());
-        assertFalse(config.isProductionMySQLEnabled());
-        assertFalse(config.isProductionStdioEnabled());
-        assertFalse(config.isDistributionEnabled());
-        assertFalse(config.isLLMEnabled());
+        assertFalse(config.isDockerRunType());
     }
     
     @Test
-    void assertExplicitLaneOverrides() {
+    void assertDockerRunType() {
         Properties props = new Properties();
-        props.setProperty("mcp.e2e.contract.enabled", "true");
-        props.setProperty("mcp.e2e.production.mysql.enabled", "true");
-        props.setProperty("mcp.e2e.production.stdio.enabled", "true");
-        props.setProperty("mcp.e2e.distribution.enabled", "true");
-        props.setProperty("mcp.e2e.llm.enabled", "true");
+        props.setProperty("e2e.run.type", "DOCKER");
         MCPE2ETestConfiguration config = new MCPE2ETestConfiguration(props);
-        assertTrue(config.isContractEnabled());
-        assertTrue(config.isProductionMySQLEnabled());
-        assertTrue(config.isProductionStdioEnabled());
-        assertTrue(config.isDistributionEnabled());
-        assertTrue(config.isLLMEnabled());
+        assertTrue(config.isDockerRunType());
     }
     
     @Test
-    void assertInvalidBooleanFallsBackToDefault() {
+    void assertCommaSeparatedRunTypes() {
         Properties props = new Properties();
-        props.setProperty("mcp.e2e.contract.enabled", "invalid");
-        props.setProperty("mcp.e2e.production.mysql.enabled", "invalid");
+        props.setProperty("e2e.run.type", "NATIVE, docker");
         MCPE2ETestConfiguration config = new MCPE2ETestConfiguration(props);
-        assertFalse(config.isContractEnabled());
-        assertFalse(config.isProductionMySQLEnabled());
+        assertTrue(config.isDockerRunType());
     }
     
     @Test
-    void assertCompositeMySQLStdioCondition() {
-        Properties mysqlOnlyProps = new Properties();
-        mysqlOnlyProps.setProperty("mcp.e2e.production.mysql.enabled", "true");
-        MCPE2ETestConfiguration mysqlOnlyConfig = new 
MCPE2ETestConfiguration(mysqlOnlyProps);
-        Properties bothProps = new Properties();
-        bothProps.setProperty("mcp.e2e.production.mysql.enabled", "true");
-        bothProps.setProperty("mcp.e2e.production.stdio.enabled", "true");
-        MCPE2ETestConfiguration bothConfig = new 
MCPE2ETestConfiguration(bothProps);
-        
assertFalse(MCPE2ECondition.isProductionMySQLStdioEnabled(mysqlOnlyConfig));
-        assertTrue(MCPE2ECondition.isProductionMySQLStdioEnabled(bothConfig));
+    void assertNativeRunType() {
+        Properties props = new Properties();
+        props.setProperty("e2e.run.type", "NATIVE");
+        MCPE2ETestConfiguration config = new MCPE2ETestConfiguration(props);
+        assertFalse(config.isDockerRunType());
     }
     
 }
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/config/LLME2EConfiguration.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/config/LLME2EConfiguration.java
index 5afacce9659..314d7d2559b 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/config/LLME2EConfiguration.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/config/LLME2EConfiguration.java
@@ -17,6 +17,8 @@
 
 package org.apache.shardingsphere.test.e2e.mcp.llm.config;
 
+import 
org.apache.shardingsphere.test.e2e.env.runtime.EnvironmentPropertiesLoader;
+
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 
@@ -27,6 +29,7 @@ import java.nio.file.Paths;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.Locale;
+import java.util.Properties;
 import java.util.UUID;
 
 /**
@@ -80,20 +83,21 @@ public final class LLME2EConfiguration {
      * @return LLM E2E configuration
      */
     public static LLME2EConfiguration load() {
-        RuntimeMode runtimeMode = 
RuntimeMode.from(readString("mcp.llm.runtime-mode", "MCP_LLM_RUNTIME_MODE", 
RuntimeMode.DOCKER.getValue()));
+        Properties props = EnvironmentPropertiesLoader.loadProperties();
+        RuntimeMode runtimeMode = RuntimeMode.from(readString(props, 
"mcp.llm.runtime-mode", RuntimeMode.DOCKER.getValue()));
         return new LLME2EConfiguration(
-                normalizeBaseUrl(readString("mcp.llm.base-url", 
"MCP_LLM_BASE_URL", DEFAULT_BASE_URL)),
-                readString("mcp.llm.provider", "MCP_LLM_PROVIDER", 
"openai-compatible"),
-                readString("mcp.llm.model", "MCP_LLM_MODEL", 
DEFAULT_MODEL_NAME),
-                readString("mcp.llm.api-key", "MCP_LLM_API_KEY", 
DEFAULT_API_KEY),
-                readInteger("mcp.llm.ready-timeout-seconds", 
"MCP_LLM_READY_TIMEOUT_SECONDS", 600),
-                readInteger("mcp.llm.request-timeout-seconds", 
"MCP_LLM_REQUEST_TIMEOUT_SECONDS", 240),
-                readInteger("mcp.llm.max-turns", "MCP_LLM_MAX_TURNS", 10),
-                Paths.get(readString("mcp.llm.artifact-root", 
"MCP_LLM_ARTIFACT_ROOT", "target/llm-e2e")),
-                readString("mcp.llm.run-id", "MCP_LLM_RUN_ID", 
createDefaultRunId()),
+                normalizeBaseUrl(readString(props, "mcp.llm.base-url", 
DEFAULT_BASE_URL)),
+                readString(props, "mcp.llm.provider", "openai-compatible"),
+                readString(props, "mcp.llm.model", DEFAULT_MODEL_NAME),
+                readString(props, "mcp.llm.api-key", DEFAULT_API_KEY),
+                readInteger(props, "mcp.llm.ready-timeout-seconds", 600),
+                readInteger(props, "mcp.llm.request-timeout-seconds", 240),
+                readInteger(props, "mcp.llm.max-turns", 10),
+                Paths.get(readString(props, "mcp.llm.artifact-root", 
"target/llm-e2e")),
+                readString(props, "mcp.llm.run-id", createDefaultRunId()),
                 runtimeMode,
-                readString("mcp.llm.server-image", "MCP_LLM_SERVER_IMAGE", 
DEFAULT_SERVER_IMAGE),
-                readString("mcp.llm.base-server-image-digest", 
"MCP_LLM_BASE_SERVER_IMAGE_DIGEST", 
getDefaultBaseServerImageDigest(runtimeMode)));
+                readString(props, "mcp.llm.server-image", 
DEFAULT_SERVER_IMAGE),
+                readString(props, "mcp.llm.base-server-image-digest", 
getDefaultBaseServerImageDigest(runtimeMode)));
     }
     
     /**
@@ -162,17 +166,13 @@ public final class LLME2EConfiguration {
         return baseUrl + "/models";
     }
     
-    private static String readString(final String propertyName, final String 
environmentName, final String defaultValue) {
-        String result = System.getProperty(propertyName);
-        if (null != result && !result.trim().isEmpty()) {
-            return result.trim();
-        }
-        result = System.getenv(environmentName);
+    private static String readString(final Properties props, final String 
propertyName, final String defaultValue) {
+        String result = props.getProperty(propertyName);
         return null == result || result.trim().isEmpty() ? defaultValue : 
result.trim();
     }
     
-    private static int readInteger(final String propertyName, final String 
environmentName, final int defaultValue) {
-        String result = readString(propertyName, environmentName, 
String.valueOf(defaultValue));
+    private static int readInteger(final Properties props, final String 
propertyName, final int defaultValue) {
+        String result = readString(props, propertyName, 
String.valueOf(defaultValue));
         try {
             return Integer.parseInt(result);
         } catch (final NumberFormatException ignored) {
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPModelFacingToolResponseFormatter.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPModelFacingToolResponseFormatter.java
index 2f38a301c8e..abe2c6c9782 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPModelFacingToolResponseFormatter.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPModelFacingToolResponseFormatter.java
@@ -57,6 +57,19 @@ final class LLMMCPModelFacingToolResponseFormatter {
         copyIfPresent(response, result, "total_match_count");
         copyIfPresent(response, result, "search_context");
         copyIfPresent(response, result, "ambiguity_state");
+        List<Map<String, Object>> resources = 
LLMMCPJsonValues.castToList(response.get("resources"));
+        if (!resources.isEmpty()) {
+            List<Map<String, Object>> compactResources = new LinkedList<>();
+            for (Map<String, Object> each : resources) {
+                Map<String, Object> compactResource = new LinkedHashMap<>(8, 
1F);
+                copyIfPresent(each, compactResource, "uri");
+                copyIfPresent(each, compactResource, "name");
+                copyIfPresent(each, compactResource, "title");
+                copyIfPresent(each, compactResource, "mimeType");
+                compactResources.add(compactResource.isEmpty() ? each : 
compactResource);
+            }
+            result.put("resources", compactResources);
+        }
         copyPromptList(response, result);
         copyPromptMessages(response, result);
         copyCompactItems(response, result);
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPModelFacingToolResponseFormatterTest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPModelFacingToolResponseFormatterTest.java
new file mode 100644
index 00000000000..6581919ea48
--- /dev/null
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPModelFacingToolResponseFormatterTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.apache.shardingsphere.infra.util.json.JsonUtils;
+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 LLMMCPModelFacingToolResponseFormatterTest {
+    
+    @Test
+    void assertFormat() {
+        Map<String, Object> actual = 
JsonUtils.fromJsonString(LLMMCPModelFacingToolResponseFormatter.format(Map.of("resources",
 List.of(Map.of(
+                "uri", "shardingsphere://databases",
+                "name", "logical-databases",
+                "title", "Logical Databases",
+                "description", "Long model-facing description.",
+                "mimeType", "application/json",
+                "_meta", Map.of("org.apache.shardingsphere/resource-kind", 
"list"))))), new TypeReference<>() {
+                });
+        Map<String, Object> expected = Map.of("resources", List.of(Map.of(
+                "uri", "shardingsphere://databases",
+                "name", "logical-databases",
+                "title", "Logical Databases",
+                "mimeType", "application/json")));
+        assertThat(actual, is(expected));
+    }
+}
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/smoke/LLMSmokeE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/smoke/LLMSmokeE2ETest.java
index 62ad0ab4894..e16872f461e 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/smoke/LLMSmokeE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/smoke/LLMSmokeE2ETest.java
@@ -96,7 +96,7 @@ class LLMSmokeE2ETest extends 
AbstractConfigBackedRuntimeE2ETest {
     }
     
     private static boolean isEnabled() {
-        return MCPE2ECondition.isLLMEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     static Stream<Arguments> getTestCases() {
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteE2ETest.java
index 98b3da31b59..f550e2f9018 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteE2ETest.java
@@ -89,7 +89,7 @@ class LLMUsabilitySuiteE2ETest extends 
AbstractConfigBackedRuntimeE2ETest {
     }
     
     private static boolean isEnabled() {
-        return MCPE2ECondition.isLLMEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     static Stream<Arguments> getTestCases() {
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionMySQLRuntimeE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionMySQLRuntimeE2ETest.java
index 498e927094d..24904c31217 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionMySQLRuntimeE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionMySQLRuntimeE2ETest.java
@@ -123,7 +123,7 @@ abstract class AbstractProductionMySQLRuntimeE2ETest 
extends AbstractTransportPa
     }
     
     protected static boolean isEnabled() {
-        return MCPE2ECondition.isProductionMySQLEnabled() || 
MCPE2ECondition.isProductionMySQLStdioEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     protected static Stream<Arguments> transports() {
@@ -155,14 +155,7 @@ abstract class AbstractProductionMySQLRuntimeE2ETest 
extends AbstractTransportPa
     }
     
     protected static Stream<RuntimeTransport> runtimeTransports() {
-        Stream.Builder<RuntimeTransport> result = Stream.builder();
-        if (MCPE2ECondition.isProductionMySQLEnabled()) {
-            result.add(RuntimeTransport.HTTP);
-        }
-        if (MCPE2ECondition.isProductionMySQLStdioEnabled()) {
-            result.add(RuntimeTransport.STDIO);
-        }
-        return result.build();
+        return Stream.of(RuntimeTransport.HTTP, RuntimeTransport.STDIO);
     }
     
     protected static String getTransportName(final RuntimeTransport transport) 
{
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyEncryptWorkflowE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyEncryptWorkflowE2ETest.java
index a5fbcc08c45..1fece035a64 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyEncryptWorkflowE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyEncryptWorkflowE2ETest.java
@@ -24,6 +24,8 @@ import 
org.apache.shardingsphere.test.e2e.mcp.support.transport.client.MCPIntera
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledIf;
 
+import java.io.IOException;
+import java.sql.SQLException;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -53,11 +55,11 @@ class HttpProductionProxyEncryptWorkflowE2ETest extends 
AbstractProductionProxyW
     private static final String TABLE_RULES_RESOURCE_URI = 
"shardingsphere://features/encrypt/databases/%s/tables/%s/rules";
     
     private static boolean isEnabled() {
-        return MCPE2ECondition.isProductionMySQLEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     @Test
-    void assertCompleteEncryptAlgorithmThroughProxy() throws Exception {
+    void assertCompleteEncryptAlgorithmThroughProxy() throws IOException, 
InterruptedException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             Map<String, Object> actual = 
interactionClient.complete(Map.of("type", "ref/prompt", "name", 
PLAN_PROMPT_NAME), "algorithm_type", "AE", Map.of());
             
assertThat(getStringList(getMap(actual.get("completion")).get("values")), 
hasItem("AES"));
@@ -65,7 +67,7 @@ class HttpProductionProxyEncryptWorkflowE2ETest extends 
AbstractProductionProxyW
     }
     
     @Test
-    void assertPlanApplyAndValidateEncryptWorkflowThroughProxy() throws 
Exception {
+    void assertPlanApplyAndValidateEncryptWorkflowThroughProxy() throws 
IOException, InterruptedException, SQLException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             Map<String, Object> clarifyingResponse = 
interactionClient.call(PLAN_TOOL_NAME,
                     Map.of("table", "orders", "column", "status", 
"natural_language_intent", "encrypt status with reversible encryption, no 
equality, no like"));
@@ -116,7 +118,7 @@ class HttpProductionProxyEncryptWorkflowE2ETest extends 
AbstractProductionProxyW
     }
     
     @Test
-    void assertPlanRecommendsAssistedQueryEncryptWorkflowThroughProxy() throws 
Exception {
+    void assertPlanRecommendsAssistedQueryEncryptWorkflowThroughProxy() throws 
IOException, InterruptedException, SQLException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             Map<String, Object> actualClarifyingResponse = 
interactionClient.call(PLAN_TOOL_NAME,
                     Map.of("database", getLogicalDatabaseName(), "table", 
"orders", "column", "status",
@@ -168,7 +170,7 @@ class HttpProductionProxyEncryptWorkflowE2ETest extends 
AbstractProductionProxyW
     }
     
     @Test
-    void assertPlanReportsLikeQueryCapabilityConflictThroughProxy() throws 
Exception {
+    void assertPlanReportsLikeQueryCapabilityConflictThroughProxy() throws 
IOException, InterruptedException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             Map<String, Object> actualPlanResponse = 
interactionClient.call(PLAN_TOOL_NAME,
                     Map.of("database", getLogicalDatabaseName(), "table", 
"orders", "column", "status",
@@ -184,7 +186,7 @@ class HttpProductionProxyEncryptWorkflowE2ETest extends 
AbstractProductionProxyW
     }
     
     @Test
-    void 
assertPlanAutoRenamesDerivedColumnWhenCipherColumnConflictsThroughProxy() 
throws Exception {
+    void 
assertPlanAutoRenamesDerivedColumnWhenCipherColumnConflictsThroughProxy() 
throws IOException, InterruptedException, SQLException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             interactionClient.call("database_gateway_execute_update",
                     Map.of("database", getLogicalDatabaseName(), "schema", 
"public",
@@ -210,7 +212,7 @@ class HttpProductionProxyEncryptWorkflowE2ETest extends 
AbstractProductionProxyW
     }
     
     @Test
-    void assertPlanRejectsUnsupportedSecondEncryptColumnThroughProxy() throws 
Exception {
+    void assertPlanRejectsUnsupportedSecondEncryptColumnThroughProxy() throws 
IOException, InterruptedException, SQLException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             Map<String, Object> actualFirstPlanResponse = 
interactionClient.call(PLAN_TOOL_NAME,
                     Map.of("database", getLogicalDatabaseName(), "table", 
"orders", "column", "status",
@@ -239,7 +241,7 @@ class HttpProductionProxyEncryptWorkflowE2ETest extends 
AbstractProductionProxyW
     }
     
     @Test
-    void assertApplySupportsManualOnlyExecutionModeThroughProxy() throws 
Exception {
+    void assertApplySupportsManualOnlyExecutionModeThroughProxy() throws 
IOException, InterruptedException, SQLException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             Map<String, Object> actualPlannedResponse = 
interactionClient.call(PLAN_TOOL_NAME,
                     Map.of("database", getLogicalDatabaseName(), "table", 
"orders", "column", "status",
@@ -263,7 +265,7 @@ class HttpProductionProxyEncryptWorkflowE2ETest extends 
AbstractProductionProxyW
     }
     
     @Test
-    void assertApplySupportsApprovedStepsThroughProxy() throws Exception {
+    void assertApplySupportsApprovedStepsThroughProxy() throws IOException, 
InterruptedException, SQLException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             Map<String, Object> actualPlannedResponse = 
interactionClient.call(PLAN_TOOL_NAME,
                     Map.of("database", getLogicalDatabaseName(), "table", 
"orders", "column", "status",
@@ -292,7 +294,7 @@ class HttpProductionProxyEncryptWorkflowE2ETest extends 
AbstractProductionProxyW
     }
     
     @Test
-    void assertPlanRejectsUnsupportedEncryptAlterExpansionThroughProxy() 
throws Exception {
+    void assertPlanRejectsUnsupportedEncryptAlterExpansionThroughProxy() 
throws IOException, InterruptedException, SQLException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             createEncryptRuleWithoutEquality(interactionClient);
             Map<String, Object> actualAlterPlanResponse = 
interactionClient.call(PLAN_TOOL_NAME,
@@ -310,7 +312,7 @@ class HttpProductionProxyEncryptWorkflowE2ETest extends 
AbstractProductionProxyW
     }
     
     @Test
-    void assertPlanApplyAndValidateEncryptDropWorkflowThroughProxy() throws 
Exception {
+    void assertPlanApplyAndValidateEncryptDropWorkflowThroughProxy() throws 
IOException, InterruptedException, SQLException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             createEncryptRuleWithoutEquality(interactionClient);
             Map<String, Object> actualDropPlanResponse = 
interactionClient.call(PLAN_TOOL_NAME,
@@ -335,7 +337,7 @@ class HttpProductionProxyEncryptWorkflowE2ETest extends 
AbstractProductionProxyW
     }
     
     @Test
-    void 
assertPlanApplyValidateAndReadEncryptResourcesWithCustomAlgorithmThroughProxy() 
throws Exception {
+    void 
assertPlanApplyValidateAndReadEncryptResourcesWithCustomAlgorithmThroughProxy() 
throws IOException, InterruptedException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             Map<String, Object> actualPlanResponse = 
interactionClient.call(PLAN_TOOL_NAME,
                     Map.of("database", getLogicalDatabaseName(), "table", 
"orders", "column", "status",
@@ -357,11 +359,11 @@ class HttpProductionProxyEncryptWorkflowE2ETest extends 
AbstractProductionProxyW
         }
     }
     
-    private void createEncryptRuleWithoutEquality(final MCPInteractionClient 
interactionClient) throws Exception {
+    private void createEncryptRuleWithoutEquality(final MCPInteractionClient 
interactionClient) throws IOException, InterruptedException {
         createEncryptRuleWithoutEquality(interactionClient, "status", 
"base-secret");
     }
     
-    private void createEncryptRuleWithoutEquality(final MCPInteractionClient 
interactionClient, final String columnName, final String secret) throws 
Exception {
+    private void createEncryptRuleWithoutEquality(final MCPInteractionClient 
interactionClient, final String columnName, final String secret) throws 
IOException, InterruptedException {
         Map<String, Object> actualCreatePlanResponse = 
interactionClient.call(PLAN_TOOL_NAME,
                 Map.of("database", getLogicalDatabaseName(), "table", 
"orders", "column", columnName,
                         "natural_language_intent", String.format("encrypt %s 
with reversible encryption, no equality, no like", columnName), 
"algorithm_type", "AES",
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyMaskWorkflowE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyMaskWorkflowE2ETest.java
index 8ef5e7f394d..78a6689491a 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyMaskWorkflowE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyMaskWorkflowE2ETest.java
@@ -24,6 +24,7 @@ import 
org.apache.shardingsphere.test.e2e.mcp.support.transport.client.MCPIntera
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledIf;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -52,11 +53,11 @@ class HttpProductionProxyMaskWorkflowE2ETest extends 
AbstractProductionProxyWork
     private static final String TABLE_RULES_RESOURCE_URI = 
"shardingsphere://features/mask/databases/%s/tables/%s/rules";
     
     private static boolean isEnabled() {
-        return MCPE2ECondition.isProductionMySQLEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     @Test
-    void assertCompleteMaskAlgorithmThroughProxy() throws Exception {
+    void assertCompleteMaskAlgorithmThroughProxy() throws IOException, 
InterruptedException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             Map<String, Object> actual = 
interactionClient.complete(Map.of("type", "ref/prompt", "name", 
PLAN_PROMPT_NAME), "algorithm_type", "KEEP", Map.of());
             
assertThat(getStringList(getMap(actual.get("completion")).get("values")), 
hasItem("KEEP_FIRST_N_LAST_M"));
@@ -64,7 +65,7 @@ class HttpProductionProxyMaskWorkflowE2ETest extends 
AbstractProductionProxyWork
     }
     
     @Test
-    void assertPlanApplyAndValidateMaskCreateAlterWorkflowThroughProxy() 
throws Exception {
+    void assertPlanApplyAndValidateMaskCreateAlterWorkflowThroughProxy() 
throws IOException, InterruptedException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             Map<String, Object> actualCreatePlanResponse = 
interactionClient.call(PLAN_TOOL_NAME,
                     Map.of("database", getLogicalDatabaseName(), "table", 
"orders", "column", "status",
@@ -94,7 +95,7 @@ class HttpProductionProxyMaskWorkflowE2ETest extends 
AbstractProductionProxyWork
     }
     
     @Test
-    void assertPlanApplyAndValidateMaskDropWorkflowThroughProxy() throws 
Exception {
+    void assertPlanApplyAndValidateMaskDropWorkflowThroughProxy() throws 
IOException, InterruptedException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             createMaskRule(interactionClient);
             Map<String, Object> actualDropPlanResponse = 
interactionClient.call(PLAN_TOOL_NAME,
@@ -110,7 +111,7 @@ class HttpProductionProxyMaskWorkflowE2ETest extends 
AbstractProductionProxyWork
     }
     
     @Test
-    void assertPlanRejectsUnsupportedSecondMaskColumnThroughProxy() throws 
Exception {
+    void assertPlanRejectsUnsupportedSecondMaskColumnThroughProxy() throws 
IOException, InterruptedException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             createMaskRule(interactionClient);
             Map<String, Object> actualSecondCreatePlanResponse = 
interactionClient.call(PLAN_TOOL_NAME,
@@ -129,7 +130,7 @@ class HttpProductionProxyMaskWorkflowE2ETest extends 
AbstractProductionProxyWork
     }
     
     @Test
-    void 
assertPlanRecommendApplyAndValidateMaskWorkflowFromNaturalLanguageThroughProxy()
 throws Exception {
+    void 
assertPlanRecommendApplyAndValidateMaskWorkflowFromNaturalLanguageThroughProxy()
 throws IOException, InterruptedException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             Map<String, Object> actualClarifyingResponse = 
interactionClient.call(PLAN_TOOL_NAME,
                     Map.of("database", getLogicalDatabaseName(), "table", 
"orders", "column", "status",
@@ -153,7 +154,7 @@ class HttpProductionProxyMaskWorkflowE2ETest extends 
AbstractProductionProxyWork
     }
     
     @Test
-    void assertApplySupportsApprovedStepsThroughProxy() throws Exception {
+    void assertApplySupportsApprovedStepsThroughProxy() throws IOException, 
InterruptedException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             Map<String, Object> actualPlanResponse = 
interactionClient.call(PLAN_TOOL_NAME,
                     Map.of("database", getLogicalDatabaseName(), "table", 
"orders", "column", "status",
@@ -178,7 +179,7 @@ class HttpProductionProxyMaskWorkflowE2ETest extends 
AbstractProductionProxyWork
     }
     
     @Test
-    void 
assertPlanApplyValidateAndReadMaskResourcesWithCustomAlgorithmThroughProxy() 
throws Exception {
+    void 
assertPlanApplyValidateAndReadMaskResourcesWithCustomAlgorithmThroughProxy() 
throws IOException, InterruptedException {
         try (MCPInteractionClient interactionClient = 
createOpenedInteractionClient()) {
             Map<String, Object> actualPlanResponse = 
interactionClient.call(PLAN_TOOL_NAME,
                     Map.of("database", getLogicalDatabaseName(), "table", 
"orders", "column", "status",
@@ -200,7 +201,7 @@ class HttpProductionProxyMaskWorkflowE2ETest extends 
AbstractProductionProxyWork
         }
     }
     
-    private void createMaskRule(final MCPInteractionClient interactionClient) 
throws Exception {
+    private void createMaskRule(final MCPInteractionClient interactionClient) 
throws IOException, InterruptedException {
         Map<String, Object> actualCreatePlanResponse = 
interactionClient.call(PLAN_TOOL_NAME,
                 Map.of("database", getLogicalDatabaseName(), "table", 
"orders", "column", "status",
                         "operation_type", "create", "algorithm_type", 
"KEEP_FIRST_N_LAST_M",
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/PackagedDistributionE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/PackagedDistributionE2ETest.java
index a0dae6017a6..9185a4b7dfa 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/PackagedDistributionE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/PackagedDistributionE2ETest.java
@@ -21,6 +21,7 @@ import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.hasItems;
 import static org.hamcrest.Matchers.is;
 
+import 
org.apache.shardingsphere.test.e2e.env.runtime.EnvironmentPropertiesLoader;
 import org.apache.shardingsphere.test.e2e.mcp.env.MCPE2ECondition;
 import org.apache.shardingsphere.test.e2e.mcp.support.OfficialMCPToolNames;
 import 
org.apache.shardingsphere.test.e2e.mcp.support.distribution.DockerImageHttpRuntime;
@@ -91,7 +92,7 @@ class PackagedDistributionE2ETest {
     }
     
     private static boolean isEnabled() {
-        return MCPE2ECondition.isDistributionEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     @Test
@@ -138,10 +139,9 @@ class PackagedDistributionE2ETest {
     
     @Test
     void assertLaunchContainerOverHttp() throws IOException, 
InterruptedException, SQLException {
-        assumeContainerImageConfigured();
         Path configFile = createDockerConfigurationFile(RuntimeTransport.HTTP);
         try (
-                DockerImageHttpRuntime runtime = new 
DockerImageHttpRuntime(System.getProperty(IMAGE_PROPERTY), configFile);
+                DockerImageHttpRuntime runtime = new 
DockerImageHttpRuntime(getConfiguredContainerImage(), configFile);
                 MCPInteractionClient interactionClient = 
runtime.openInteractionClient()) {
             assertContainerRuntime(RuntimeTransport.HTTP, interactionClient);
         }
@@ -149,9 +149,8 @@ class PackagedDistributionE2ETest {
     
     @Test
     void assertLaunchContainerOverStdio() throws IOException, 
InterruptedException, SQLException {
-        assumeContainerImageConfigured();
         Path configFile = 
createDockerConfigurationFile(RuntimeTransport.STDIO);
-        try (MCPInteractionClient interactionClient = new 
DockerImageStdioInteractionClient(System.getProperty(IMAGE_PROPERTY), 
configFile)) {
+        try (MCPInteractionClient interactionClient = new 
DockerImageStdioInteractionClient(getConfiguredContainerImage(), configFile)) {
             interactionClient.open();
             assertContainerRuntime(RuntimeTransport.STDIO, interactionClient);
         }
@@ -180,8 +179,10 @@ class PackagedDistributionE2ETest {
         MySQLRuntimeTestSupport.initializeDatabase(mysqlContainer);
     }
     
-    private void assumeContainerImageConfigured() {
-        Assumptions.assumeFalse(System.getProperty(IMAGE_PROPERTY, 
"").isBlank(), "Set -D" + IMAGE_PROPERTY + " to run MCP container distribution 
E2E.");
+    private String getConfiguredContainerImage() {
+        String result = 
EnvironmentPropertiesLoader.loadProperties().getProperty(IMAGE_PROPERTY, 
"").trim();
+        Assumptions.assumeFalse(result.isBlank(), "Set " + IMAGE_PROPERTY + " 
in env/e2e-env.properties or pass -D" + IMAGE_PROPERTY + " to run MCP container 
distribution E2E.");
+        return result;
     }
     
     private void assertOfficialRuntime(final Path distributionHome, final 
RuntimeTransport transport,
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/ExecuteQueryTransactionE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/ExecuteQueryTransactionE2ETest.java
index 7bb5808d988..d3504efa8a8 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/ExecuteQueryTransactionE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/ExecuteQueryTransactionE2ETest.java
@@ -39,7 +39,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 class ExecuteQueryTransactionE2ETest extends 
AbstractHttpProgrammaticRuntimeE2ETest {
     
     private static boolean isEnabled() {
-        return MCPE2ECondition.isContractEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     @Test
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java
index a1e3a5127cc..6abc482af19 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java
@@ -38,7 +38,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 class HttpTransportApprovalSafetyE2ETest extends 
AbstractHttpProgrammaticRuntimeE2ETest {
     
     private static boolean isEnabled() {
-        return MCPE2ECondition.isContractEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     @Test
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportBaselineContractE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportBaselineContractE2ETest.java
index fc050735ad3..fef006781dd 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportBaselineContractE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportBaselineContractE2ETest.java
@@ -40,7 +40,7 @@ class HttpTransportBaselineContractE2ETest extends 
AbstractHttpProgrammaticRunti
     private static final String BASELINE_RESOURCE_PATH = 
"baseline-contract/model-contract/";
     
     private static boolean isEnabled() {
-        return MCPE2ECondition.isContractEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     @Test
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java
index dde9dea638a..cc2fc4e0b29 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java
@@ -40,7 +40,7 @@ class HttpTransportCompletionE2ETest extends 
AbstractHttpProgrammaticRuntimeE2ET
     private static final String PLAN_MASK_PROMPT_NAME = "plan_mask_rule";
     
     private static boolean isEnabled() {
-        return MCPE2ECondition.isContractEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     @Test
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportContractE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportContractE2ETest.java
index 341197afbb2..12c045fba7e 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportContractE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportContractE2ETest.java
@@ -50,7 +50,7 @@ class HttpTransportContractE2ETest extends 
AbstractHttpProgrammaticRuntimeE2ETes
     private static final String PLAN_MASK_TOOL_NAME = 
"database_gateway_plan_mask_rule";
     
     private static boolean isEnabled() {
-        return MCPE2ECondition.isContractEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     @Test
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportProtocolContractE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportProtocolContractE2ETest.java
index 57b68ce3070..ff3276a4b33 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportProtocolContractE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportProtocolContractE2ETest.java
@@ -38,7 +38,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 class HttpTransportProtocolContractE2ETest extends 
AbstractHttpProgrammaticRuntimeE2ETest {
     
     private static boolean isEnabled() {
-        return MCPE2ECondition.isContractEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     @Test
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportRecoveryE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportRecoveryE2ETest.java
index e6c4e3d11dd..e4aa767fd19 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportRecoveryE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportRecoveryE2ETest.java
@@ -43,7 +43,7 @@ class HttpTransportRecoveryE2ETest extends 
AbstractHttpProgrammaticRuntimeE2ETes
     private static final String RECOVERY_SECRET = "recovery-secret-value";
     
     private static boolean isEnabled() {
-        return MCPE2ECondition.isContractEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     @Test
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSecurityE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSecurityE2ETest.java
index 91d260b5ce7..8ae4eee531c 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSecurityE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSecurityE2ETest.java
@@ -39,7 +39,7 @@ class HttpTransportSecurityE2ETest extends 
AbstractHttpProgrammaticRuntimeE2ETes
     private boolean remoteBinding;
     
     private static boolean isEnabled() {
-        return MCPE2ECondition.isContractEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     @Override
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSessionLifecycleE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSessionLifecycleE2ETest.java
index b345322088f..ff41ec73476 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSessionLifecycleE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSessionLifecycleE2ETest.java
@@ -33,7 +33,7 @@ import static org.hamcrest.Matchers.is;
 class HttpTransportSessionLifecycleE2ETest extends 
AbstractHttpProgrammaticRuntimeE2ETest {
     
     private static boolean isEnabled() {
-        return MCPE2ECondition.isContractEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     @Test
diff --git 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/MetadataDiscoveryE2ETest.java
 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/MetadataDiscoveryE2ETest.java
index d12c72ff4d2..7e8fbae144d 100644
--- 
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/MetadataDiscoveryE2ETest.java
+++ 
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/MetadataDiscoveryE2ETest.java
@@ -41,7 +41,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 class MetadataDiscoveryE2ETest extends AbstractHttpProgrammaticRuntimeE2ETest {
     
     private static boolean isEnabled() {
-        return MCPE2ECondition.isContractEnabled();
+        return MCPE2ECondition.isDockerEnabled();
     }
     
     @Test
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 e3e1ad22084..0b00365e19a 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
@@ -24,6 +24,7 @@ import 
org.apache.shardingsphere.mcp.bootstrap.config.MCPTransportType;
 import 
org.apache.shardingsphere.mcp.bootstrap.config.loader.MCPConfigurationLoader;
 import 
org.apache.shardingsphere.mcp.bootstrap.config.yaml.swapper.YamlMCPLaunchConfigurationSwapper;
 import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
+import 
org.apache.shardingsphere.test.e2e.env.runtime.EnvironmentPropertiesLoader;
 import org.apache.shardingsphere.test.e2e.mcp.support.runtime.RuntimeTransport;
 
 import java.io.IOException;
@@ -136,11 +137,12 @@ public final class PackagedDistributionTestSupport {
     private static IllegalStateException 
createMissingDistributionHomeException() {
         Path expectedTargetDirectory = 
findRepositoryRoot().resolve("distribution/mcp/target");
         return new IllegalStateException("Packaged MCP distribution was not 
found. Run `" + DISTRIBUTION_PACKAGE_COMMAND
-                + "` first or set 
`-Dmcp.distribution.home=/path/to/apache-shardingsphere-mcp-*`. Checked `" + 
expectedTargetDirectory + "`.");
+                + "` first or set `mcp.distribution.home` in 
env/e2e-env.properties or pass "
+                + 
"`-Dmcp.distribution.home=/path/to/apache-shardingsphere-mcp-*`. Checked `" + 
expectedTargetDirectory + "`.");
     }
     
     private static Optional<Path> resolveConfiguredDistributionHome() {
-        String configuredHome = System.getProperty("mcp.distribution.home", 
"").trim();
+        String configuredHome = 
EnvironmentPropertiesLoader.loadProperties().getProperty("mcp.distribution.home",
 "").trim();
         if (configuredHome.isEmpty()) {
             return Optional.empty();
         }
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 c08bdca004b..3d64d8b19e9 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
@@ -124,7 +124,7 @@ class PackagedDistributionTestSupportTest {
             IllegalStateException actual = 
assertThrows(IllegalStateException.class,
                     () -> 
PackagedDistributionTestSupport.prepare(tempDir.resolve("missing-distribution"),
 RuntimeTransport.HTTP));
             assertThat(actual.getMessage(), is("Packaged MCP distribution was 
not found. Run `./mvnw -pl distribution/mcp -am -DskipTests package` first"
-                    + " or set 
`-Dmcp.distribution.home=/path/to/apache-shardingsphere-mcp-*`. Checked `"
+                    + " or set `mcp.distribution.home` in 
env/e2e-env.properties or pass 
`-Dmcp.distribution.home=/path/to/apache-shardingsphere-mcp-*`. Checked `"
                     + 
repositoryRoot.resolve("distribution/mcp/target").toAbsolutePath().normalize() 
+ "`."));
         } finally {
             restoreDistributionHome(actualOriginalHome);
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 42e7ff6235b..089d27cadae 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
@@ -22,6 +22,7 @@ import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import 
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
+import 
org.apache.shardingsphere.test.e2e.env.runtime.EnvironmentPropertiesLoader;
 import org.testcontainers.DockerClientFactory;
 import org.testcontainers.containers.GenericContainer;
 import org.testcontainers.containers.wait.strategy.Wait;
@@ -45,7 +46,7 @@ import java.util.Optional;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public final class MySQLRuntimeTestSupport {
     
-    private static final Duration JDBC_READY_TIMEOUT = Duration.ofSeconds(30);
+    private static final Duration DEFAULT_JDBC_READY_TIMEOUT = 
Duration.ofSeconds(90);
     
     private static final String MYSQL_READY_LOG_PATTERN = ".*ready for 
connections.*port: 3306.*\\n";
     
@@ -315,9 +316,25 @@ public final class MySQLRuntimeTestSupport {
     
     private static Connection getConnection(final GenericContainer<?> 
container, final String databaseName) throws SQLException {
         String jdbcUrl = createJdbcUrl(container.getHost(), 
container.getMappedPort(3306), databaseName);
+        String configuredJdbcReadyTimeoutSeconds = 
EnvironmentPropertiesLoader.loadProperties().getProperty("mcp.e2e.mysql.ready-timeout-seconds",
 "").trim();
+        long jdbcReadyTimeoutMillis;
         try {
-            return new ReadinessProbe(JDBC_READY_TIMEOUT.toMillis(), 
JDBC_READY_INITIAL_INTERVAL_MILLIS, JDBC_READY_MAX_INTERVAL_MILLIS)
-                    .waitUntilReady(() -> getConnectionIfReady(jdbcUrl), 
MySQLRuntimeTestSupport::createJdbcReadyException);
+            if (configuredJdbcReadyTimeoutSeconds.isEmpty()) {
+                jdbcReadyTimeoutMillis = DEFAULT_JDBC_READY_TIMEOUT.toMillis();
+            } else {
+                int parsedJdbcReadyTimeoutSeconds = 
Integer.parseInt(configuredJdbcReadyTimeoutSeconds);
+                if (0 >= parsedJdbcReadyTimeoutSeconds) {
+                    throw new IllegalArgumentException("MCP E2E MySQL JDBC 
readiness timeout must be positive.");
+                }
+                jdbcReadyTimeoutMillis = 
Duration.ofSeconds(parsedJdbcReadyTimeoutSeconds).toMillis();
+            }
+        } catch (final NumberFormatException ex) {
+            throw new IllegalArgumentException("MCP E2E MySQL JDBC readiness 
timeout must be an integer.", ex);
+        }
+        try {
+            return new ReadinessProbe(jdbcReadyTimeoutMillis, 
JDBC_READY_INITIAL_INTERVAL_MILLIS, JDBC_READY_MAX_INTERVAL_MILLIS)
+                    .waitUntilReady(() -> getConnectionIfReady(jdbcUrl),
+                            (cause, attemptCount, elapsedMillis) -> 
createJdbcReadyException(cause, attemptCount, elapsedMillis, 
jdbcReadyTimeoutMillis));
         } catch (final InterruptedException ex) {
             Thread.currentThread().interrupt();
             throw new SQLException("Interrupted while waiting for MySQL JDBC 
readiness.", ex);
@@ -332,9 +349,9 @@ public final class MySQLRuntimeTestSupport {
         }
     }
     
-    private static SQLException createJdbcReadyException(final Exception 
cause, final int attemptCount, final long elapsedMillis) {
+    private static SQLException createJdbcReadyException(final Exception 
cause, final int attemptCount, final long elapsedMillis, final long 
jdbcReadyTimeoutMillis) {
         String result = String.format("MySQL JDBC connection did not become 
ready after %d attempt(s), elapsedMillis=%d, timeoutMillis=%d.",
-                attemptCount, elapsedMillis, JDBC_READY_TIMEOUT.toMillis());
+                attemptCount, elapsedMillis, jdbcReadyTimeoutMillis);
         return null == cause || null == cause.getMessage() || 
cause.getMessage().isBlank()
                 ? new SQLException(result)
                 : new SQLException(result + " Last readiness failure: " + 
cause.getMessage(), cause);
diff --git a/test/e2e/mcp/src/test/resources/env/e2e-env.properties 
b/test/e2e/mcp/src/test/resources/env/e2e-env.properties
index 0875f59dc26..0e769a0ea69 100644
--- a/test/e2e/mcp/src/test/resources/env/e2e-env.properties
+++ b/test/e2e/mcp/src/test/resources/env/e2e-env.properties
@@ -16,8 +16,23 @@
 #
 
 e2e.timezone=UTC
-mcp.e2e.contract.enabled=false
-mcp.e2e.production.mysql.enabled=false
-mcp.e2e.production.stdio.enabled=false
-mcp.e2e.distribution.enabled=false
-mcp.e2e.llm.enabled=false
+
+#e2e.run.type=DOCKER
+e2e.run.type=
+
+mcp.e2e.container.image=
+mcp.e2e.mysql.ready-timeout-seconds=90
+mcp.distribution.home=
+
+mcp.llm.runtime-mode=docker
+mcp.llm.base-url=http://127.0.0.1:8080/v1
+mcp.llm.provider=openai-compatible
+mcp.llm.model=ggml-org/Qwen3-1.7B-GGUF:Q4_K_M
+mcp.llm.api-key=mcp-llm-score
+mcp.llm.ready-timeout-seconds=600
+mcp.llm.request-timeout-seconds=240
+mcp.llm.max-turns=10
+mcp.llm.artifact-root=target/llm-e2e
+mcp.llm.run-id=
+mcp.llm.server-image=apache/shardingsphere-mcp-llm-runtime:local
+mcp.llm.base-server-image-digest=

Reply via email to