Copilot commented on code in PR #7893:
URL: https://github.com/apache/incubator-seata/pull/7893#discussion_r2644559195


##########
console/src/main/java/org/apache/seata/mcp/service/impl/MCPRPCServiceImpl.java:
##########
@@ -0,0 +1,283 @@
+/*
+ * 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.seata.mcp.service.impl;
+
+import org.apache.seata.common.exception.AuthenticationFailedException;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.console.config.WebSecurityConfig;
+import org.apache.seata.console.utils.JwtTokenUtils;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.service.MCPRPCService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class MCPRPCServiceImpl implements MCPRPCService {
+
+    private final JwtTokenUtils jwtTokenUtils;
+
+    public MCPRPCServiceImpl(JwtTokenUtils jwtTokenUtils) {
+        this.jwtTokenUtils = jwtTokenUtils;
+    }
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    private final String NAMING_SPACE_URL = "http://127.0.0.1:%s";;
+
+    private final Logger logger = 
LoggerFactory.getLogger(MCPRPCServiceImpl.class);
+
+    @Value("${server.port:8081}")
+    private String namingSpacePort;
+
+    public String getToken() {
+        Authentication auth = 
SecurityContextHolder.getContext().getAuthentication();
+        if (auth == null || !auth.isAuthenticated()) {
+            throw new AuthenticationFailedException("No right to be 
identified");
+        }
+        String originJwt = (String) auth.getCredentials();
+        if (!jwtTokenUtils.validateToken(originJwt)) {
+            throw new AuthenticationFailedException("Invalid token, please log 
back in to get a new token");
+        }
+        return WebSecurityConfig.TOKEN_PREFIX + originJwt;
+    }
+
+    public void setNamespaceHeaderAndPathParam(
+            NameSpaceDetail nameSpaceDetail, HttpHeaders headers, Map<String, 
String> pathParams) {
+        headers.add("x-seata-namespace", nameSpaceDetail.getNamespace());
+        if (StringUtils.isNotBlank(nameSpaceDetail.getvGroup())) {
+            if (pathParams == null) {
+                pathParams = new HashMap<>();
+            }
+            pathParams.put("vGroup", nameSpaceDetail.getvGroup());

Review Comment:
   The pathParams map is reassigned locally but this assignment has no effect 
outside the method since Java passes object references by value. If pathParams 
is null, the new HashMap created here won't be visible to the caller. Consider 
returning the pathParams map or requiring it to be non-null as a parameter.
   ```suggestion
               if (pathParams != null) {
                   pathParams.put("vGroup", nameSpaceDetail.getvGroup());
               }
   ```



##########
console/src/main/java/org/apache/seata/mcp/core/utils/DateUtils.java:
##########
@@ -0,0 +1,80 @@
+/*
+ * 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.seata.mcp.core.utils;
+
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.regex.Pattern;
+
+public class DateUtils {
+
+    public static final Long ONE_DAY_TIMESTAMP = 86400000L;
+
+    private static final Pattern DATE_PATTERN = 
Pattern.compile("^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$");
+
+    public static boolean isValidDate(String dateStr) {
+        return DATE_PATTERN.matcher(dateStr).matches();
+    }
+
+    public static long convertToTimestampFromDate(String dateStr) {
+        if (!isValidDate(dateStr)) {
+            throw new DateTimeException("The time format does not match 
yyyy-mm-dd");
+        }
+        LocalDate date = LocalDate.parse(dateStr);
+        ZonedDateTime zonedDateTime = 
date.atStartOfDay(ZoneId.systemDefault());
+        return zonedDateTime.toInstant().toEpochMilli();
+    }
+
+    public static long convertToTimeStampFromDateTime(String dateTimeStr) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd 
HH:mm:ss");
+
+        try {
+            LocalDateTime dateTime = LocalDateTime.parse(dateTimeStr, 
formatter);
+            return 
dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+        } catch (Exception e) {

Review Comment:
   The method returns -1 on any exception during date parsing. This can lead to 
confusion as -1 is a valid timestamp (representing a time before the Unix 
epoch). Consider throwing a specific exception or returning an Optional to 
clearly indicate parsing failure rather than returning a potentially misleading 
value.
   ```suggestion
           } catch (DateTimeParseException e) {
   ```



##########
console/src/main/java/org/apache/seata/mcp/service/impl/MCPRPCServiceImpl.java:
##########
@@ -0,0 +1,283 @@
+/*
+ * 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.seata.mcp.service.impl;
+
+import org.apache.seata.common.exception.AuthenticationFailedException;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.console.config.WebSecurityConfig;
+import org.apache.seata.console.utils.JwtTokenUtils;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.service.MCPRPCService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class MCPRPCServiceImpl implements MCPRPCService {
+
+    private final JwtTokenUtils jwtTokenUtils;
+
+    public MCPRPCServiceImpl(JwtTokenUtils jwtTokenUtils) {
+        this.jwtTokenUtils = jwtTokenUtils;
+    }
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    private final String NAMING_SPACE_URL = "http://127.0.0.1:%s";;
+
+    private final Logger logger = 
LoggerFactory.getLogger(MCPRPCServiceImpl.class);
+
+    @Value("${server.port:8081}")
+    private String namingSpacePort;
+
+    public String getToken() {
+        Authentication auth = 
SecurityContextHolder.getContext().getAuthentication();
+        if (auth == null || !auth.isAuthenticated()) {
+            throw new AuthenticationFailedException("No right to be 
identified");
+        }
+        String originJwt = (String) auth.getCredentials();
+        if (!jwtTokenUtils.validateToken(originJwt)) {
+            throw new AuthenticationFailedException("Invalid token, please log 
back in to get a new token");
+        }
+        return WebSecurityConfig.TOKEN_PREFIX + originJwt;
+    }
+
+    public void setNamespaceHeaderAndPathParam(
+            NameSpaceDetail nameSpaceDetail, HttpHeaders headers, Map<String, 
String> pathParams) {
+        headers.add("x-seata-namespace", nameSpaceDetail.getNamespace());
+        if (StringUtils.isNotBlank(nameSpaceDetail.getvGroup())) {
+            if (pathParams == null) {
+                pathParams = new HashMap<>();
+            }
+            pathParams.put("vGroup", nameSpaceDetail.getvGroup());
+            return;
+        }
+        if (nameSpaceDetail.getCluster() != null) {
+            headers.add("x-seata-cluster", nameSpaceDetail.getCluster());
+        }
+    }
+

Review Comment:
   This method overrides [MCPRPCService.getCallNameSpace](1); it is advisable 
to add an Override annotation.
   ```suggestion
   
       @Override
   ```



##########
console/src/main/java/org/apache/seata/mcp/service/impl/ModifyConfirmServiceImpl.java:
##########
@@ -0,0 +1,57 @@
+/*
+ * 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.seata.mcp.service.impl;
+
+import org.apache.seata.mcp.service.ModifyConfirmService;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Service
+public class ModifyConfirmServiceImpl implements ModifyConfirmService {
+
+    private static final Map<String, Long> MODIFY_KEY = new 
ConcurrentHashMap<>();
+
+    private static final long EXPIRE_MS = 60_000; // Key timeout period
+
+    @Override
+    public Map<String, String> confirmAndGetKey() {
+        String key = UUID.randomUUID().toString();
+        MODIFY_KEY.put(key, System.currentTimeMillis());
+        Map<String, String> map = new HashMap<>();
+        map.put("modify_key", key);
+        map.put("expire_time", "60s");

Review Comment:
   The hardcoded string "60s" should be derived from the EXPIRE_MS constant to 
keep them synchronized. If EXPIRE_MS changes, this message will become 
inaccurate. Consider calculating this dynamically (e.g., EXPIRE_MS / 1000 + 
"s") to ensure consistency.
   ```suggestion
           map.put("expire_time", EXPIRE_MS / 1000 + "s");
   ```



##########
console/src/main/java/org/apache/seata/mcp/service/impl/ModifyConfirmServiceImpl.java:
##########
@@ -0,0 +1,57 @@
+/*
+ * 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.seata.mcp.service.impl;
+
+import org.apache.seata.mcp.service.ModifyConfirmService;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Service
+public class ModifyConfirmServiceImpl implements ModifyConfirmService {
+
+    private static final Map<String, Long> MODIFY_KEY = new 
ConcurrentHashMap<>();
+
+    private static final long EXPIRE_MS = 60_000; // Key timeout period

Review Comment:
   The magic number 60_000 for key expiration should be extracted as a named 
constant or made configurable via application properties. This would improve 
maintainability and allow administrators to adjust the timeout without code 
changes.



##########
console/src/main/java/org/apache/seata/mcp/service/impl/MCPRPCServiceImpl.java:
##########
@@ -0,0 +1,283 @@
+/*
+ * 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.seata.mcp.service.impl;
+
+import org.apache.seata.common.exception.AuthenticationFailedException;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.console.config.WebSecurityConfig;
+import org.apache.seata.console.utils.JwtTokenUtils;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.service.MCPRPCService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class MCPRPCServiceImpl implements MCPRPCService {
+
+    private final JwtTokenUtils jwtTokenUtils;
+
+    public MCPRPCServiceImpl(JwtTokenUtils jwtTokenUtils) {
+        this.jwtTokenUtils = jwtTokenUtils;
+    }
+
+    private final RestTemplate restTemplate = new RestTemplate();

Review Comment:
   Creating a new RestTemplate instance as a field can lead to issues in 
production environments. RestTemplate should typically be configured as a 
Spring bean with proper connection pooling, timeouts, and error handling. 
Consider injecting RestTemplate as a dependency or using WebClient for better 
async support.
   ```suggestion
       public MCPRPCServiceImpl(JwtTokenUtils jwtTokenUtils, RestTemplate 
restTemplate) {
           this.jwtTokenUtils = jwtTokenUtils;
           this.restTemplate = restTemplate;
       }
   
       private final RestTemplate restTemplate;
   ```



##########
console/src/main/java/org/apache/seata/mcp/core/utils/DateUtils.java:
##########
@@ -0,0 +1,80 @@
+/*
+ * 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.seata.mcp.core.utils;
+
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.regex.Pattern;
+
+public class DateUtils {
+
+    public static final Long ONE_DAY_TIMESTAMP = 86400000L;
+
+    private static final Pattern DATE_PATTERN = 
Pattern.compile("^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$");
+
+    public static boolean isValidDate(String dateStr) {
+        return DATE_PATTERN.matcher(dateStr).matches();
+    }
+
+    public static long convertToTimestampFromDate(String dateStr) {
+        if (!isValidDate(dateStr)) {
+            throw new DateTimeException("The time format does not match 
yyyy-mm-dd");
+        }
+        LocalDate date = LocalDate.parse(dateStr);
+        ZonedDateTime zonedDateTime = 
date.atStartOfDay(ZoneId.systemDefault());
+        return zonedDateTime.toInstant().toEpochMilli();
+    }
+
+    public static long convertToTimeStampFromDateTime(String dateTimeStr) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd 
HH:mm:ss");
+
+        try {
+            LocalDateTime dateTime = LocalDateTime.parse(dateTimeStr, 
formatter);
+            return 
dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+        } catch (Exception e) {
+            return -1;
+        }
+    }
+
+    public static String convertToDateTimeFromTimestamp(Long timestamp) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd 
HH:mm:ss");
+        LocalDateTime dateTime;
+        try {
+            dateTime = Instant.ofEpochMilli(timestamp)
+                    .atZone(ZoneId.systemDefault())
+                    .toLocalDateTime();
+        } catch (DateTimeParseException e) {

Review Comment:
   The method catches DateTimeParseException but Instant.ofEpochMilli() can 
throw DateTimeException (parent class) or ArithmeticException for invalid 
values. The current catch block will not handle these exceptions properly. 
Consider catching a broader exception type or handling all potential exceptions 
from timestamp conversion.
   ```suggestion
           } catch (DateTimeException | ArithmeticException e) {
   ```



##########
console/src/main/java/org/apache/seata/mcp/core/utils/DateUtils.java:
##########
@@ -0,0 +1,80 @@
+/*
+ * 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.seata.mcp.core.utils;
+
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.regex.Pattern;
+
+public class DateUtils {
+
+    public static final Long ONE_DAY_TIMESTAMP = 86400000L;
+
+    private static final Pattern DATE_PATTERN = 
Pattern.compile("^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$");
+
+    public static boolean isValidDate(String dateStr) {
+        return DATE_PATTERN.matcher(dateStr).matches();
+    }
+
+    public static long convertToTimestampFromDate(String dateStr) {
+        if (!isValidDate(dateStr)) {
+            throw new DateTimeException("The time format does not match 
yyyy-mm-dd");
+        }
+        LocalDate date = LocalDate.parse(dateStr);
+        ZonedDateTime zonedDateTime = 
date.atStartOfDay(ZoneId.systemDefault());
+        return zonedDateTime.toInstant().toEpochMilli();
+    }
+
+    public static long convertToTimeStampFromDateTime(String dateTimeStr) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd 
HH:mm:ss");
+
+        try {
+            LocalDateTime dateTime = LocalDateTime.parse(dateTimeStr, 
formatter);
+            return 
dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+        } catch (Exception e) {
+            return -1;
+        }
+    }
+
+    public static String convertToDateTimeFromTimestamp(Long timestamp) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd 
HH:mm:ss");
+        LocalDateTime dateTime;
+        try {
+            dateTime = Instant.ofEpochMilli(timestamp)
+                    .atZone(ZoneId.systemDefault())
+                    .toLocalDateTime();
+        } catch (DateTimeParseException e) {
+            return "Parse Failed, please check that the timestamp is correct";
+        }
+        return dateTime.format(formatter);
+    }
+
+    public static boolean judgeExceedTimeDuration(Long startTime, Long 
endTime, Long maxDuration) {
+        if (endTime < startTime) return false;

Review Comment:
   The logic returns false when endTime is less than startTime, which means the 
method will indicate the duration is valid (not exceeded) even when the time 
range is inverted. This is likely incorrect behavior. Consider throwing an 
exception or returning true (exceeded) when the time range is invalid to 
prevent silent failures.
   ```suggestion
           if (endTime < startTime) {
               throw new IllegalArgumentException("endTime must not be earlier 
than startTime");
           }
   ```



##########
console/src/main/java/org/apache/seata/mcp/service/impl/MCPRPCServiceImpl.java:
##########
@@ -0,0 +1,283 @@
+/*
+ * 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.seata.mcp.service.impl;
+
+import org.apache.seata.common.exception.AuthenticationFailedException;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.console.config.WebSecurityConfig;
+import org.apache.seata.console.utils.JwtTokenUtils;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.service.MCPRPCService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class MCPRPCServiceImpl implements MCPRPCService {
+
+    private final JwtTokenUtils jwtTokenUtils;
+
+    public MCPRPCServiceImpl(JwtTokenUtils jwtTokenUtils) {
+        this.jwtTokenUtils = jwtTokenUtils;
+    }
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    private final String NAMING_SPACE_URL = "http://127.0.0.1:%s";;
+
+    private final Logger logger = 
LoggerFactory.getLogger(MCPRPCServiceImpl.class);
+
+    @Value("${server.port:8081}")
+    private String namingSpacePort;
+
+    public String getToken() {
+        Authentication auth = 
SecurityContextHolder.getContext().getAuthentication();
+        if (auth == null || !auth.isAuthenticated()) {
+            throw new AuthenticationFailedException("No right to be 
identified");
+        }
+        String originJwt = (String) auth.getCredentials();
+        if (!jwtTokenUtils.validateToken(originJwt)) {
+            throw new AuthenticationFailedException("Invalid token, please log 
back in to get a new token");
+        }
+        return WebSecurityConfig.TOKEN_PREFIX + originJwt;
+    }
+
+    public void setNamespaceHeaderAndPathParam(
+            NameSpaceDetail nameSpaceDetail, HttpHeaders headers, Map<String, 
String> pathParams) {
+        headers.add("x-seata-namespace", nameSpaceDetail.getNamespace());
+        if (StringUtils.isNotBlank(nameSpaceDetail.getvGroup())) {
+            if (pathParams == null) {
+                pathParams = new HashMap<>();
+            }
+            pathParams.put("vGroup", nameSpaceDetail.getvGroup());
+            return;
+        }
+        if (nameSpaceDetail.getCluster() != null) {
+            headers.add("x-seata-cluster", nameSpaceDetail.getCluster());
+        }
+    }
+
+    public String getCallNameSpace(
+            String path, Object queryParams, Map<String, String> pathParams, 
HttpHeaders headers) {
+        if (headers == null) {
+            headers = new HttpHeaders();
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(queryParams);
+        String url = buildUrl(String.format(NAMING_SPACE_URL, 
namingSpacePort), path, pathParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody = null;
+        try {
+            ResponseEntity<String> response = restTemplate.exchange(url, 
HttpMethod.GET, entity, String.class);
+
+            responseBody = response.getBody();
+
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                logger.warn("MCP GET request returned non-success status: {}", 
response.getStatusCode());
+            }
+            return responseBody;
+        } catch (RestClientException e) {
+            logger.error("MCP GET Call NameSpace Failed: {}", e.getMessage());
+            return responseBody;
+        }
+    }
+
+    @Override
+    public String getCallTC(
+            NameSpaceDetail nameSpaceDetail,
+            String path,
+            Object queryParams,
+            Map<String, String> pathParams,
+            HttpHeaders headers) {
+        if (headers == null) {
+            headers = new HttpHeaders();
+        }
+        if (nameSpaceDetail == null || !nameSpaceDetail.isValid()) {
+            return "If you have not specified the namespace of the TC/Server, 
specify the namespace first";
+        } else {
+            setNamespaceHeaderAndPathParam(nameSpaceDetail, headers, 
pathParams);
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(queryParams);
+        String url = buildUrl(String.format(NAMING_SPACE_URL, 
namingSpacePort), path, pathParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody = null;
+        try {
+            ResponseEntity<String> response = restTemplate.exchange(url, 
HttpMethod.GET, entity, String.class);
+
+            responseBody = response.getBody();
+
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                logger.warn("MCP GET request returned non-success status: {}", 
response.getStatusCode());
+            }
+            return responseBody;
+        } catch (RestClientException e) {
+            logger.error("MCP GET Call TC Failed: {}", e.getMessage());
+            return responseBody;
+        }
+    }
+
+    @Override
+    public String deleteCallTC(
+            NameSpaceDetail nameSpaceDetail,
+            String path,
+            Object queryParams,
+            Map<String, String> pathParams,
+            HttpHeaders headers) {
+        if (headers == null) {
+            headers = new HttpHeaders();
+        }
+        if (nameSpaceDetail == null || !nameSpaceDetail.isValid()) {
+            return "If you have not specified the namespace of the TC/Server, 
specify the namespace first";
+        } else {
+            setNamespaceHeaderAndPathParam(nameSpaceDetail, headers, 
pathParams);
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(queryParams);
+        String url = buildUrl(String.format(NAMING_SPACE_URL, 
namingSpacePort), path, pathParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody = null;
+        try {
+            ResponseEntity<String> response = restTemplate.exchange(url, 
HttpMethod.DELETE, entity, String.class);
+
+            responseBody = response.getBody();
+
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                logger.warn("MCP DELETE request returned non-success status: 
{}", response.getStatusCode());
+            }
+            return responseBody;
+        } catch (RestClientException e) {
+            logger.error("MCP DELETE Call TC Failed: {}", e.getMessage());
+            return responseBody;

Review Comment:
   When a RestClientException is caught, the method returns responseBody which 
will always be null at this point since the exception occurred before or during 
the exchange. This results in returning null instead of providing meaningful 
error information. Consider returning an error message or throwing a custom 
exception to properly communicate the failure to the caller.



##########
console/src/main/java/org/apache/seata/mcp/tools/GlobalSessionTools.java:
##########
@@ -0,0 +1,228 @@
+/*
+ * 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.seata.mcp.tools;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.seata.common.result.PageResult;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.core.model.GlobalStatus;
+import org.apache.seata.mcp.core.constant.RPCConstant;
+import org.apache.seata.mcp.core.props.MCPProperties;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.core.utils.DateUtils;
+import org.apache.seata.mcp.entity.dto.GlobalSessionParamDto;
+import org.apache.seata.mcp.entity.param.GlobalAbnormalSessionParam;
+import org.apache.seata.mcp.entity.param.GlobalSessionParam;
+import org.apache.seata.mcp.entity.vo.GlobalSessionVO;
+import org.apache.seata.mcp.service.MCPRPCService;
+import org.apache.seata.mcp.service.ModifyConfirmService;
+import org.springaicommunity.mcp.annotation.McpTool;
+import org.springaicommunity.mcp.annotation.McpToolParam;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class GlobalSessionTools {
+
+    private final MCPRPCService mcpRPCService;
+
+    private final MCPProperties mcpProperties;
+
+    private final ObjectMapper objectMapper;
+
+    private final ModifyConfirmService modifyConfirmService;
+
+    public GlobalSessionTools(
+            MCPRPCService mcpRPCService,
+            MCPProperties mcpProperties,
+            ObjectMapper objectMapper,
+            ModifyConfirmService modifyConfirmService) {
+        this.mcpRPCService = mcpRPCService;
+        this.mcpProperties = mcpProperties;
+        this.objectMapper = objectMapper;
+        this.modifyConfirmService = modifyConfirmService;
+    }
+
+    private final List<Integer> exceptionStatus = new ArrayList<>();
+
+    @McpTool(description = "Query global transactions")
+    public PageResult<GlobalSessionVO> queryGlobalSession(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Query parameter objects") 
GlobalSessionParamDto paramDto) {
+        GlobalSessionParam param = 
GlobalSessionParam.covertFromDtoParam(paramDto);
+        if (param.getTimeStart() != null) {
+            if (param.getTimeEnd() != null) {
+                if (DateUtils.judgeExceedTimeDuration(
+                        param.getTimeStart(), param.getTimeEnd(), 
mcpProperties.getQueryDuration())) {
+                    throw new IllegalArgumentException(
+                            "The query time span is not allowed to exceed the 
max query duration : "
+                                    + 
DateUtils.convertToHourFromTimeStamp(mcpProperties.getQueryDuration()) + " 
hour");
+                }
+            } else {
+                param.setTimeEnd(param.getTimeStart() + 
DateUtils.ONE_DAY_TIMESTAMP);
+            }
+        } else {
+            param.setTimeEnd(null);
+            param.setTimeStart(null);
+        }
+        PageResult<GlobalSessionVO> pageResult;
+        String result = mcpRPCService.getCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/query", param, null, null);
+        try {
+            pageResult = objectMapper.readValue(result, new 
TypeReference<PageResult<GlobalSessionVO>>() {});
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+        if (pageResult == null) {
+            return PageResult.failure("", "query global session failed");
+        } else {
+            return pageResult;
+        }
+    }
+
+    @McpTool(description = "Delete the global session, Get the modify key 
before you delete")
+    public String deleteGlobalSession(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global transaction id") String xid,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        Map<String, String> pathParams = new HashMap<>();
+        pathParams.put("xid", xid);
+        String result = mcpRPCService.deleteCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/deleteGlobalSession", null, pathParams, null);
+        if (StringUtils.isBlank(result)) {
+            return String.format("delete global session failed, xid: %s", xid);
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(description = "Stop the global session retry, Get the modify key 
before you stop")
+    public String stopGlobalSession(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global transaction id") String xid,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        Map<String, String> pathParams = new HashMap<>();
+        pathParams.put("xid", xid);
+        String result = mcpRPCService.putCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/stopGlobalSession", null, pathParams, null);
+        if (StringUtils.isBlank(result)) {
+            return String.format("stop global session retry failed, xid: %s", 
xid);
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(description = "Start the global session retry, Get the modify key 
before you start")
+    public String startGlobalSession(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global transaction id") String xid,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        Map<String, String> pathParams = new HashMap<>();
+        pathParams.put("xid", xid);
+        String result = mcpRPCService.putCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/startGlobalSession", null, pathParams, null);
+        if (StringUtils.isBlank(result)) {
+            return String.format("start the global session retry failed, xid: 
%s", xid);
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(description = "Send global session to commit or rollback to rm, 
Get the modify key before you send")
+    public String sendCommitOrRollback(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global transaction id") String xid,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        Map<String, String> pathParams = new HashMap<>();
+        pathParams.put("xid", xid);
+        String result = mcpRPCService.putCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/sendCommitOrRollback", null, pathParams, null);
+        if (StringUtils.isBlank(result)) {
+            return String.format("send global session to commit or rollback to 
rm failed, xid: %s", xid);
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(
+            description =
+                    "Change the global session status, Used to change 
transactions that are in a failed commit or rollback failed state to a retry 
state, Get the modify key before you change")
+    public String changeGlobalStatus(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global transaction id") String xid,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        Map<String, String> pathParams = new HashMap<>();
+        pathParams.put("xid", xid);
+        String result = mcpRPCService.putCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/changeGlobalStatus", null, pathParams, null);
+        if (StringUtils.isBlank(result)) {
+            return String.format("change the global session status failed, 
xid: %s", xid);
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(description = "Check out the abnormal transaction information,You 
can specify the time")
+    public List<GlobalSessionVO> getAbnormalSessions(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Query Param") 
GlobalAbnormalSessionParam abnormalSessionParam) {
+        List<GlobalSessionVO> result = new ArrayList<>();
+        GlobalSessionParamDto param = 
GlobalSessionParamDto.covertFromAbnormalParam(abnormalSessionParam);
+        param.setPageNum(1);
+        param.setPageSize(100);
+        if (exceptionStatus.isEmpty()) {
+            exceptionStatus.add(GlobalStatus.CommitFailed.getCode());
+            exceptionStatus.add(GlobalStatus.TimeoutRollbackFailed.getCode());
+            exceptionStatus.add(GlobalStatus.RollbackFailed.getCode());
+        }
+        for (Integer status : exceptionStatus) {
+            param.setStatus(status);
+            List<GlobalSessionVO> datas =
+                    queryGlobalSession(nameSpaceDetail, param).getData();
+            if (datas != null && !datas.isEmpty()) {
+                for (GlobalSessionVO vo : datas) {
+                    if (result.size() >= 200) {

Review Comment:
   The magic number 200 is used as a hard-coded limit for the result size. This 
value should be extracted as a named constant (e.g., 
MAX_ABNORMAL_SESSIONS_RESULT_SIZE) at the class level to improve code 
maintainability and make it easier to adjust this limit in the future.



##########
console/src/main/java/org/apache/seata/mcp/tools/GlobalSessionTools.java:
##########
@@ -0,0 +1,228 @@
+/*
+ * 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.seata.mcp.tools;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.seata.common.result.PageResult;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.core.model.GlobalStatus;
+import org.apache.seata.mcp.core.constant.RPCConstant;
+import org.apache.seata.mcp.core.props.MCPProperties;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.core.utils.DateUtils;
+import org.apache.seata.mcp.entity.dto.GlobalSessionParamDto;
+import org.apache.seata.mcp.entity.param.GlobalAbnormalSessionParam;
+import org.apache.seata.mcp.entity.param.GlobalSessionParam;
+import org.apache.seata.mcp.entity.vo.GlobalSessionVO;
+import org.apache.seata.mcp.service.MCPRPCService;
+import org.apache.seata.mcp.service.ModifyConfirmService;
+import org.springaicommunity.mcp.annotation.McpTool;
+import org.springaicommunity.mcp.annotation.McpToolParam;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class GlobalSessionTools {
+
+    private final MCPRPCService mcpRPCService;
+
+    private final MCPProperties mcpProperties;
+
+    private final ObjectMapper objectMapper;
+
+    private final ModifyConfirmService modifyConfirmService;
+
+    public GlobalSessionTools(
+            MCPRPCService mcpRPCService,
+            MCPProperties mcpProperties,
+            ObjectMapper objectMapper,
+            ModifyConfirmService modifyConfirmService) {
+        this.mcpRPCService = mcpRPCService;
+        this.mcpProperties = mcpProperties;
+        this.objectMapper = objectMapper;
+        this.modifyConfirmService = modifyConfirmService;
+    }
+
+    private final List<Integer> exceptionStatus = new ArrayList<>();
+
+    @McpTool(description = "Query global transactions")
+    public PageResult<GlobalSessionVO> queryGlobalSession(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Query parameter objects") 
GlobalSessionParamDto paramDto) {
+        GlobalSessionParam param = 
GlobalSessionParam.covertFromDtoParam(paramDto);
+        if (param.getTimeStart() != null) {
+            if (param.getTimeEnd() != null) {
+                if (DateUtils.judgeExceedTimeDuration(
+                        param.getTimeStart(), param.getTimeEnd(), 
mcpProperties.getQueryDuration())) {
+                    throw new IllegalArgumentException(
+                            "The query time span is not allowed to exceed the 
max query duration : "
+                                    + 
DateUtils.convertToHourFromTimeStamp(mcpProperties.getQueryDuration()) + " 
hour");
+                }
+            } else {
+                param.setTimeEnd(param.getTimeStart() + 
DateUtils.ONE_DAY_TIMESTAMP);
+            }
+        } else {
+            param.setTimeEnd(null);
+            param.setTimeStart(null);
+        }
+        PageResult<GlobalSessionVO> pageResult;
+        String result = mcpRPCService.getCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/query", param, null, null);
+        try {
+            pageResult = objectMapper.readValue(result, new 
TypeReference<PageResult<GlobalSessionVO>>() {});
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+        if (pageResult == null) {
+            return PageResult.failure("", "query global session failed");
+        } else {
+            return pageResult;
+        }
+    }
+
+    @McpTool(description = "Delete the global session, Get the modify key 
before you delete")
+    public String deleteGlobalSession(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global transaction id") String xid,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        Map<String, String> pathParams = new HashMap<>();
+        pathParams.put("xid", xid);
+        String result = mcpRPCService.deleteCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/deleteGlobalSession", null, pathParams, null);
+        if (StringUtils.isBlank(result)) {
+            return String.format("delete global session failed, xid: %s", xid);
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(description = "Stop the global session retry, Get the modify key 
before you stop")
+    public String stopGlobalSession(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global transaction id") String xid,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        Map<String, String> pathParams = new HashMap<>();
+        pathParams.put("xid", xid);
+        String result = mcpRPCService.putCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/stopGlobalSession", null, pathParams, null);
+        if (StringUtils.isBlank(result)) {
+            return String.format("stop global session retry failed, xid: %s", 
xid);
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(description = "Start the global session retry, Get the modify key 
before you start")
+    public String startGlobalSession(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global transaction id") String xid,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        Map<String, String> pathParams = new HashMap<>();
+        pathParams.put("xid", xid);
+        String result = mcpRPCService.putCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/startGlobalSession", null, pathParams, null);
+        if (StringUtils.isBlank(result)) {
+            return String.format("start the global session retry failed, xid: 
%s", xid);
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(description = "Send global session to commit or rollback to rm, 
Get the modify key before you send")
+    public String sendCommitOrRollback(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global transaction id") String xid,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        Map<String, String> pathParams = new HashMap<>();
+        pathParams.put("xid", xid);
+        String result = mcpRPCService.putCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/sendCommitOrRollback", null, pathParams, null);
+        if (StringUtils.isBlank(result)) {
+            return String.format("send global session to commit or rollback to 
rm failed, xid: %s", xid);
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(
+            description =
+                    "Change the global session status, Used to change 
transactions that are in a failed commit or rollback failed state to a retry 
state, Get the modify key before you change")
+    public String changeGlobalStatus(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global transaction id") String xid,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        Map<String, String> pathParams = new HashMap<>();
+        pathParams.put("xid", xid);
+        String result = mcpRPCService.putCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/changeGlobalStatus", null, pathParams, null);
+        if (StringUtils.isBlank(result)) {
+            return String.format("change the global session status failed, 
xid: %s", xid);
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(description = "Check out the abnormal transaction information,You 
can specify the time")
+    public List<GlobalSessionVO> getAbnormalSessions(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Query Param") 
GlobalAbnormalSessionParam abnormalSessionParam) {
+        List<GlobalSessionVO> result = new ArrayList<>();
+        GlobalSessionParamDto param = 
GlobalSessionParamDto.covertFromAbnormalParam(abnormalSessionParam);
+        param.setPageNum(1);
+        param.setPageSize(100);
+        if (exceptionStatus.isEmpty()) {
+            exceptionStatus.add(GlobalStatus.CommitFailed.getCode());
+            exceptionStatus.add(GlobalStatus.TimeoutRollbackFailed.getCode());
+            exceptionStatus.add(GlobalStatus.RollbackFailed.getCode());
+        }

Review Comment:
   The exceptionStatus list is initialized in the getAbnormalSessions method on 
every call. This is inefficient as the list contains constant status codes that 
should be initialized once. Consider moving this initialization to a 
constructor or using a static initializer block to initialize the list only 
once when the class is loaded.



##########
console/src/main/java/org/apache/seata/mcp/core/props/NameSpaceDetail.java:
##########
@@ -0,0 +1,56 @@
+/*
+ * 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.seata.mcp.core.props;
+
+import org.apache.seata.common.util.StringUtils;
+
+public class NameSpaceDetail {
+    private String namespace;
+    private String cluster;
+    private String vGroup;
+
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public void setNamespace(String namespace) {
+        this.namespace = namespace;
+    }
+
+    public String getCluster() {
+        return cluster;
+    }
+
+    public void setCluster(String cluster) {
+        this.cluster = cluster;
+    }
+
+    public String getvGroup() {
+        return vGroup;
+    }
+
+    public void setvGroup(String vGroup) {

Review Comment:
   The method name getvGroup uses non-standard Java naming convention for 
getters. According to JavaBeans conventions, getter methods should follow 
camelCase pattern. This should be renamed to getVGroup (with capital V) to 
follow standard naming conventions and maintain consistency.
   ```suggestion
       public String getVGroup() {
           return vGroup;
       }
   
       public void setVGroup(String vGroup) {
   ```



##########
console/src/main/java/org/apache/seata/mcp/tools/ModifyConfirmTools.java:
##########
@@ -0,0 +1,59 @@
+/*
+ * 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.seata.mcp.tools;
+
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.mcp.service.ModifyConfirmService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springaicommunity.mcp.annotation.McpTool;
+import org.springaicommunity.mcp.annotation.McpToolParam;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+
+@Service
+public class ModifyConfirmTools {
+
+    private final ModifyConfirmService modifyConfirmService;
+
+    public ModifyConfirmTools(ModifyConfirmService modifyConfirmService) {
+        this.modifyConfirmService = modifyConfirmService;
+    }
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(ModifyConfirmTools.class);
+

Review Comment:
   The logger is declared after the constructor, which violates the typical 
Java style convention of declaring static final fields at the top of the class. 
Consider moving this logger declaration before the constructor to improve code 
readability and consistency with standard Java conventions.
   ```suggestion
       private static final Logger LOGGER = 
LoggerFactory.getLogger(ModifyConfirmTools.class);
   
       private final ModifyConfirmService modifyConfirmService;
   
       public ModifyConfirmTools(ModifyConfirmService modifyConfirmService) {
           this.modifyConfirmService = modifyConfirmService;
       }
   ```



##########
console/src/main/java/org/apache/seata/mcp/service/impl/ModifyConfirmServiceImpl.java:
##########
@@ -0,0 +1,57 @@
+/*
+ * 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.seata.mcp.service.impl;
+
+import org.apache.seata.mcp.service.ModifyConfirmService;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Service
+public class ModifyConfirmServiceImpl implements ModifyConfirmService {
+
+    private static final Map<String, Long> MODIFY_KEY = new 
ConcurrentHashMap<>();

Review Comment:
   The MODIFY_KEY map can grow indefinitely if keys expire but are not removed. 
While isValidKey() removes expired keys when accessed, keys that are never 
accessed again will remain in memory forever. Consider implementing a 
background cleanup task or using a cache with TTL capabilities (like Caffeine 
or Guava Cache) to automatically evict expired entries.



##########
console/src/main/java/org/apache/seata/mcp/core/props/NameSpaceDetail.java:
##########
@@ -0,0 +1,56 @@
+/*
+ * 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.seata.mcp.core.props;
+
+import org.apache.seata.common.util.StringUtils;
+
+public class NameSpaceDetail {
+    private String namespace;
+    private String cluster;
+    private String vGroup;
+
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public void setNamespace(String namespace) {
+        this.namespace = namespace;
+    }
+
+    public String getCluster() {
+        return cluster;
+    }
+
+    public void setCluster(String cluster) {
+        this.cluster = cluster;
+    }
+
+    public String getvGroup() {
+        return vGroup;
+    }
+
+    public void setvGroup(String vGroup) {

Review Comment:
   The method name setvGroup uses non-standard Java naming convention for 
setters. According to JavaBeans conventions, setter methods should follow 
camelCase pattern. This should be renamed to setVGroup (with capital V) to 
follow standard naming conventions and maintain consistency.
   ```suggestion
       public String getVGroup() {
           return vGroup;
       }
   
       public void setVGroup(String vGroup) {
   ```



##########
console/src/main/java/org/apache/seata/mcp/service/impl/MCPRPCServiceImpl.java:
##########
@@ -0,0 +1,283 @@
+/*
+ * 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.seata.mcp.service.impl;
+
+import org.apache.seata.common.exception.AuthenticationFailedException;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.console.config.WebSecurityConfig;
+import org.apache.seata.console.utils.JwtTokenUtils;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.service.MCPRPCService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class MCPRPCServiceImpl implements MCPRPCService {
+
+    private final JwtTokenUtils jwtTokenUtils;
+
+    public MCPRPCServiceImpl(JwtTokenUtils jwtTokenUtils) {
+        this.jwtTokenUtils = jwtTokenUtils;
+    }
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    private final String NAMING_SPACE_URL = "http://127.0.0.1:%s";;
+
+    private final Logger logger = 
LoggerFactory.getLogger(MCPRPCServiceImpl.class);
+
+    @Value("${server.port:8081}")
+    private String namingSpacePort;
+
+    public String getToken() {
+        Authentication auth = 
SecurityContextHolder.getContext().getAuthentication();
+        if (auth == null || !auth.isAuthenticated()) {
+            throw new AuthenticationFailedException("No right to be 
identified");
+        }
+        String originJwt = (String) auth.getCredentials();
+        if (!jwtTokenUtils.validateToken(originJwt)) {
+            throw new AuthenticationFailedException("Invalid token, please log 
back in to get a new token");
+        }
+        return WebSecurityConfig.TOKEN_PREFIX + originJwt;
+    }
+
+    public void setNamespaceHeaderAndPathParam(
+            NameSpaceDetail nameSpaceDetail, HttpHeaders headers, Map<String, 
String> pathParams) {
+        headers.add("x-seata-namespace", nameSpaceDetail.getNamespace());
+        if (StringUtils.isNotBlank(nameSpaceDetail.getvGroup())) {
+            if (pathParams == null) {
+                pathParams = new HashMap<>();
+            }
+            pathParams.put("vGroup", nameSpaceDetail.getvGroup());
+            return;
+        }
+        if (nameSpaceDetail.getCluster() != null) {
+            headers.add("x-seata-cluster", nameSpaceDetail.getCluster());
+        }
+    }
+
+    public String getCallNameSpace(
+            String path, Object queryParams, Map<String, String> pathParams, 
HttpHeaders headers) {
+        if (headers == null) {
+            headers = new HttpHeaders();
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(queryParams);
+        String url = buildUrl(String.format(NAMING_SPACE_URL, 
namingSpacePort), path, pathParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody = null;
+        try {
+            ResponseEntity<String> response = restTemplate.exchange(url, 
HttpMethod.GET, entity, String.class);
+
+            responseBody = response.getBody();
+
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                logger.warn("MCP GET request returned non-success status: {}", 
response.getStatusCode());
+            }
+            return responseBody;
+        } catch (RestClientException e) {
+            logger.error("MCP GET Call NameSpace Failed: {}", e.getMessage());
+            return responseBody;

Review Comment:
   When a RestClientException is caught, the method returns responseBody which 
will always be null at this point since the exception occurred before or during 
the exchange. This results in returning null instead of providing meaningful 
error information. Consider returning an error message or throwing a custom 
exception to properly communicate the failure to the caller.



##########
console/src/main/java/org/apache/seata/mcp/service/impl/MCPRPCServiceImpl.java:
##########
@@ -0,0 +1,283 @@
+/*
+ * 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.seata.mcp.service.impl;
+
+import org.apache.seata.common.exception.AuthenticationFailedException;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.console.config.WebSecurityConfig;
+import org.apache.seata.console.utils.JwtTokenUtils;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.service.MCPRPCService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class MCPRPCServiceImpl implements MCPRPCService {
+
+    private final JwtTokenUtils jwtTokenUtils;
+
+    public MCPRPCServiceImpl(JwtTokenUtils jwtTokenUtils) {
+        this.jwtTokenUtils = jwtTokenUtils;
+    }
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    private final String NAMING_SPACE_URL = "http://127.0.0.1:%s";;
+
+    private final Logger logger = 
LoggerFactory.getLogger(MCPRPCServiceImpl.class);
+
+    @Value("${server.port:8081}")
+    private String namingSpacePort;
+
+    public String getToken() {
+        Authentication auth = 
SecurityContextHolder.getContext().getAuthentication();
+        if (auth == null || !auth.isAuthenticated()) {
+            throw new AuthenticationFailedException("No right to be 
identified");
+        }
+        String originJwt = (String) auth.getCredentials();
+        if (!jwtTokenUtils.validateToken(originJwt)) {
+            throw new AuthenticationFailedException("Invalid token, please log 
back in to get a new token");
+        }
+        return WebSecurityConfig.TOKEN_PREFIX + originJwt;
+    }
+
+    public void setNamespaceHeaderAndPathParam(
+            NameSpaceDetail nameSpaceDetail, HttpHeaders headers, Map<String, 
String> pathParams) {
+        headers.add("x-seata-namespace", nameSpaceDetail.getNamespace());
+        if (StringUtils.isNotBlank(nameSpaceDetail.getvGroup())) {
+            if (pathParams == null) {
+                pathParams = new HashMap<>();
+            }
+            pathParams.put("vGroup", nameSpaceDetail.getvGroup());
+            return;
+        }
+        if (nameSpaceDetail.getCluster() != null) {
+            headers.add("x-seata-cluster", nameSpaceDetail.getCluster());
+        }
+    }
+
+    public String getCallNameSpace(
+            String path, Object queryParams, Map<String, String> pathParams, 
HttpHeaders headers) {
+        if (headers == null) {
+            headers = new HttpHeaders();
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(queryParams);
+        String url = buildUrl(String.format(NAMING_SPACE_URL, 
namingSpacePort), path, pathParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody = null;
+        try {
+            ResponseEntity<String> response = restTemplate.exchange(url, 
HttpMethod.GET, entity, String.class);
+
+            responseBody = response.getBody();
+
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                logger.warn("MCP GET request returned non-success status: {}", 
response.getStatusCode());
+            }
+            return responseBody;
+        } catch (RestClientException e) {
+            logger.error("MCP GET Call NameSpace Failed: {}", e.getMessage());
+            return responseBody;
+        }
+    }
+
+    @Override
+    public String getCallTC(
+            NameSpaceDetail nameSpaceDetail,
+            String path,
+            Object queryParams,
+            Map<String, String> pathParams,
+            HttpHeaders headers) {
+        if (headers == null) {
+            headers = new HttpHeaders();
+        }
+        if (nameSpaceDetail == null || !nameSpaceDetail.isValid()) {
+            return "If you have not specified the namespace of the TC/Server, 
specify the namespace first";
+        } else {
+            setNamespaceHeaderAndPathParam(nameSpaceDetail, headers, 
pathParams);
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(queryParams);
+        String url = buildUrl(String.format(NAMING_SPACE_URL, 
namingSpacePort), path, pathParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody = null;
+        try {
+            ResponseEntity<String> response = restTemplate.exchange(url, 
HttpMethod.GET, entity, String.class);
+
+            responseBody = response.getBody();
+
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                logger.warn("MCP GET request returned non-success status: {}", 
response.getStatusCode());
+            }
+            return responseBody;
+        } catch (RestClientException e) {
+            logger.error("MCP GET Call TC Failed: {}", e.getMessage());
+            return responseBody;
+        }
+    }
+
+    @Override
+    public String deleteCallTC(
+            NameSpaceDetail nameSpaceDetail,
+            String path,
+            Object queryParams,
+            Map<String, String> pathParams,
+            HttpHeaders headers) {
+        if (headers == null) {
+            headers = new HttpHeaders();
+        }
+        if (nameSpaceDetail == null || !nameSpaceDetail.isValid()) {
+            return "If you have not specified the namespace of the TC/Server, 
specify the namespace first";
+        } else {
+            setNamespaceHeaderAndPathParam(nameSpaceDetail, headers, 
pathParams);
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(queryParams);
+        String url = buildUrl(String.format(NAMING_SPACE_URL, 
namingSpacePort), path, pathParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody = null;
+        try {
+            ResponseEntity<String> response = restTemplate.exchange(url, 
HttpMethod.DELETE, entity, String.class);
+
+            responseBody = response.getBody();
+
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                logger.warn("MCP DELETE request returned non-success status: 
{}", response.getStatusCode());
+            }
+            return responseBody;
+        } catch (RestClientException e) {
+            logger.error("MCP DELETE Call TC Failed: {}", e.getMessage());
+            return responseBody;
+        }
+    }
+
+    @Override
+    public String putCallTC(
+            NameSpaceDetail nameSpaceDetail,
+            String path,
+            Object queryParams,
+            Map<String, String> pathParams,
+            HttpHeaders headers) {
+        if (headers == null) {
+            headers = new HttpHeaders();
+        }
+        if (nameSpaceDetail == null || !nameSpaceDetail.isValid()) {
+            return "If you have not specified the namespace of the TC/Server, 
specify the namespace first";
+        } else {
+            setNamespaceHeaderAndPathParam(nameSpaceDetail, headers, 
pathParams);
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(queryParams);
+        String url = buildUrl(String.format(NAMING_SPACE_URL, 
namingSpacePort), path, pathParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody = null;
+        try {
+            ResponseEntity<String> response = restTemplate.exchange(url, 
HttpMethod.PUT, entity, String.class);
+
+            responseBody = response.getBody();
+
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                logger.warn("MCP PUT request returned non-success status: {}", 
response.getStatusCode());
+            }
+            return responseBody;
+        } catch (RestClientException e) {
+            logger.error("MCP Put Call TC Failed: {}", e.getMessage());
+            return responseBody;

Review Comment:
   When a RestClientException is caught, the method returns responseBody which 
will always be null at this point since the exception occurred before or during 
the exchange. This results in returning null instead of providing meaningful 
error information. Consider returning an error message or throwing a custom 
exception to properly communicate the failure to the caller.
   ```suggestion
               logger.error("MCP Put Call TC Failed", e);
               return "MCP Put Call TC Failed: " + e.getMessage();
   ```



##########
console/src/main/java/org/apache/seata/mcp/tools/GlobalSessionTools.java:
##########
@@ -0,0 +1,228 @@
+/*
+ * 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.seata.mcp.tools;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.seata.common.result.PageResult;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.core.model.GlobalStatus;
+import org.apache.seata.mcp.core.constant.RPCConstant;
+import org.apache.seata.mcp.core.props.MCPProperties;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.core.utils.DateUtils;
+import org.apache.seata.mcp.entity.dto.GlobalSessionParamDto;
+import org.apache.seata.mcp.entity.param.GlobalAbnormalSessionParam;
+import org.apache.seata.mcp.entity.param.GlobalSessionParam;
+import org.apache.seata.mcp.entity.vo.GlobalSessionVO;
+import org.apache.seata.mcp.service.MCPRPCService;
+import org.apache.seata.mcp.service.ModifyConfirmService;
+import org.springaicommunity.mcp.annotation.McpTool;
+import org.springaicommunity.mcp.annotation.McpToolParam;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class GlobalSessionTools {
+
+    private final MCPRPCService mcpRPCService;
+
+    private final MCPProperties mcpProperties;
+
+    private final ObjectMapper objectMapper;
+
+    private final ModifyConfirmService modifyConfirmService;
+
+    public GlobalSessionTools(
+            MCPRPCService mcpRPCService,
+            MCPProperties mcpProperties,
+            ObjectMapper objectMapper,
+            ModifyConfirmService modifyConfirmService) {
+        this.mcpRPCService = mcpRPCService;
+        this.mcpProperties = mcpProperties;
+        this.objectMapper = objectMapper;
+        this.modifyConfirmService = modifyConfirmService;
+    }
+
+    private final List<Integer> exceptionStatus = new ArrayList<>();
+
+    @McpTool(description = "Query global transactions")
+    public PageResult<GlobalSessionVO> queryGlobalSession(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Query parameter objects") 
GlobalSessionParamDto paramDto) {
+        GlobalSessionParam param = 
GlobalSessionParam.covertFromDtoParam(paramDto);
+        if (param.getTimeStart() != null) {
+            if (param.getTimeEnd() != null) {
+                if (DateUtils.judgeExceedTimeDuration(
+                        param.getTimeStart(), param.getTimeEnd(), 
mcpProperties.getQueryDuration())) {
+                    throw new IllegalArgumentException(
+                            "The query time span is not allowed to exceed the 
max query duration : "
+                                    + 
DateUtils.convertToHourFromTimeStamp(mcpProperties.getQueryDuration()) + " 
hour");
+                }
+            } else {
+                param.setTimeEnd(param.getTimeStart() + 
DateUtils.ONE_DAY_TIMESTAMP);
+            }
+        } else {
+            param.setTimeEnd(null);
+            param.setTimeStart(null);
+        }
+        PageResult<GlobalSessionVO> pageResult;
+        String result = mcpRPCService.getCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/query", param, null, null);
+        try {
+            pageResult = objectMapper.readValue(result, new 
TypeReference<PageResult<GlobalSessionVO>>() {});
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+        if (pageResult == null) {
+            return PageResult.failure("", "query global session failed");
+        } else {
+            return pageResult;
+        }
+    }
+
+    @McpTool(description = "Delete the global session, Get the modify key 
before you delete")
+    public String deleteGlobalSession(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global transaction id") String xid,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        Map<String, String> pathParams = new HashMap<>();
+        pathParams.put("xid", xid);
+        String result = mcpRPCService.deleteCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/deleteGlobalSession", null, pathParams, null);
+        if (StringUtils.isBlank(result)) {
+            return String.format("delete global session failed, xid: %s", xid);
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(description = "Stop the global session retry, Get the modify key 
before you stop")
+    public String stopGlobalSession(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global transaction id") String xid,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        Map<String, String> pathParams = new HashMap<>();
+        pathParams.put("xid", xid);
+        String result = mcpRPCService.putCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/stopGlobalSession", null, pathParams, null);
+        if (StringUtils.isBlank(result)) {
+            return String.format("stop global session retry failed, xid: %s", 
xid);
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(description = "Start the global session retry, Get the modify key 
before you start")
+    public String startGlobalSession(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global transaction id") String xid,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        Map<String, String> pathParams = new HashMap<>();
+        pathParams.put("xid", xid);
+        String result = mcpRPCService.putCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/startGlobalSession", null, pathParams, null);
+        if (StringUtils.isBlank(result)) {
+            return String.format("start the global session retry failed, xid: 
%s", xid);
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(description = "Send global session to commit or rollback to rm, 
Get the modify key before you send")
+    public String sendCommitOrRollback(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global transaction id") String xid,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        Map<String, String> pathParams = new HashMap<>();
+        pathParams.put("xid", xid);
+        String result = mcpRPCService.putCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/sendCommitOrRollback", null, pathParams, null);
+        if (StringUtils.isBlank(result)) {
+            return String.format("send global session to commit or rollback to 
rm failed, xid: %s", xid);
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(
+            description =
+                    "Change the global session status, Used to change 
transactions that are in a failed commit or rollback failed state to a retry 
state, Get the modify key before you change")
+    public String changeGlobalStatus(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global transaction id") String xid,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        Map<String, String> pathParams = new HashMap<>();
+        pathParams.put("xid", xid);
+        String result = mcpRPCService.putCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/changeGlobalStatus", null, pathParams, null);
+        if (StringUtils.isBlank(result)) {
+            return String.format("change the global session status failed, 
xid: %s", xid);
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(description = "Check out the abnormal transaction information,You 
can specify the time")
+    public List<GlobalSessionVO> getAbnormalSessions(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Query Param") 
GlobalAbnormalSessionParam abnormalSessionParam) {
+        List<GlobalSessionVO> result = new ArrayList<>();
+        GlobalSessionParamDto param = 
GlobalSessionParamDto.covertFromAbnormalParam(abnormalSessionParam);
+        param.setPageNum(1);
+        param.setPageSize(100);

Review Comment:
   The magic number 100 used for pageSize should be extracted as a named 
constant (e.g., DEFAULT_ABNORMAL_SESSION_PAGE_SIZE) at the class level. This 
improves code maintainability and makes the value's purpose clearer.



##########
console/src/main/java/org/apache/seata/mcp/service/impl/MCPRPCServiceImpl.java:
##########
@@ -0,0 +1,283 @@
+/*
+ * 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.seata.mcp.service.impl;
+
+import org.apache.seata.common.exception.AuthenticationFailedException;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.console.config.WebSecurityConfig;
+import org.apache.seata.console.utils.JwtTokenUtils;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.service.MCPRPCService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class MCPRPCServiceImpl implements MCPRPCService {
+
+    private final JwtTokenUtils jwtTokenUtils;
+
+    public MCPRPCServiceImpl(JwtTokenUtils jwtTokenUtils) {
+        this.jwtTokenUtils = jwtTokenUtils;
+    }
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    private final String NAMING_SPACE_URL = "http://127.0.0.1:%s";;

Review Comment:
   The hardcoded URL "http://127.0.0.1:%s"; restricts the service to only 
communicate with localhost. This should be made configurable through 
application properties to support distributed deployments where TC nodes may be 
on different hosts. Consider injecting this as a configuration property.



##########
console/src/main/java/org/apache/seata/mcp/service/impl/MCPRPCServiceImpl.java:
##########
@@ -0,0 +1,283 @@
+/*
+ * 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.seata.mcp.service.impl;
+
+import org.apache.seata.common.exception.AuthenticationFailedException;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.console.config.WebSecurityConfig;
+import org.apache.seata.console.utils.JwtTokenUtils;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.service.MCPRPCService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class MCPRPCServiceImpl implements MCPRPCService {
+
+    private final JwtTokenUtils jwtTokenUtils;
+
+    public MCPRPCServiceImpl(JwtTokenUtils jwtTokenUtils) {
+        this.jwtTokenUtils = jwtTokenUtils;
+    }
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    private final String NAMING_SPACE_URL = "http://127.0.0.1:%s";;
+
+    private final Logger logger = 
LoggerFactory.getLogger(MCPRPCServiceImpl.class);
+
+    @Value("${server.port:8081}")
+    private String namingSpacePort;
+
+    public String getToken() {
+        Authentication auth = 
SecurityContextHolder.getContext().getAuthentication();
+        if (auth == null || !auth.isAuthenticated()) {
+            throw new AuthenticationFailedException("No right to be 
identified");
+        }
+        String originJwt = (String) auth.getCredentials();
+        if (!jwtTokenUtils.validateToken(originJwt)) {
+            throw new AuthenticationFailedException("Invalid token, please log 
back in to get a new token");
+        }
+        return WebSecurityConfig.TOKEN_PREFIX + originJwt;
+    }
+
+    public void setNamespaceHeaderAndPathParam(
+            NameSpaceDetail nameSpaceDetail, HttpHeaders headers, Map<String, 
String> pathParams) {
+        headers.add("x-seata-namespace", nameSpaceDetail.getNamespace());
+        if (StringUtils.isNotBlank(nameSpaceDetail.getvGroup())) {
+            if (pathParams == null) {
+                pathParams = new HashMap<>();
+            }
+            pathParams.put("vGroup", nameSpaceDetail.getvGroup());
+            return;
+        }
+        if (nameSpaceDetail.getCluster() != null) {
+            headers.add("x-seata-cluster", nameSpaceDetail.getCluster());
+        }
+    }
+
+    public String getCallNameSpace(
+            String path, Object queryParams, Map<String, String> pathParams, 
HttpHeaders headers) {
+        if (headers == null) {
+            headers = new HttpHeaders();
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(queryParams);
+        String url = buildUrl(String.format(NAMING_SPACE_URL, 
namingSpacePort), path, pathParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody = null;
+        try {
+            ResponseEntity<String> response = restTemplate.exchange(url, 
HttpMethod.GET, entity, String.class);
+
+            responseBody = response.getBody();
+
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                logger.warn("MCP GET request returned non-success status: {}", 
response.getStatusCode());
+            }
+            return responseBody;
+        } catch (RestClientException e) {
+            logger.error("MCP GET Call NameSpace Failed: {}", e.getMessage());
+            return responseBody;
+        }
+    }
+
+    @Override
+    public String getCallTC(
+            NameSpaceDetail nameSpaceDetail,
+            String path,
+            Object queryParams,
+            Map<String, String> pathParams,
+            HttpHeaders headers) {
+        if (headers == null) {
+            headers = new HttpHeaders();
+        }
+        if (nameSpaceDetail == null || !nameSpaceDetail.isValid()) {
+            return "If you have not specified the namespace of the TC/Server, 
specify the namespace first";
+        } else {
+            setNamespaceHeaderAndPathParam(nameSpaceDetail, headers, 
pathParams);
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(queryParams);
+        String url = buildUrl(String.format(NAMING_SPACE_URL, 
namingSpacePort), path, pathParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody = null;
+        try {
+            ResponseEntity<String> response = restTemplate.exchange(url, 
HttpMethod.GET, entity, String.class);
+
+            responseBody = response.getBody();
+
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                logger.warn("MCP GET request returned non-success status: {}", 
response.getStatusCode());
+            }
+            return responseBody;
+        } catch (RestClientException e) {
+            logger.error("MCP GET Call TC Failed: {}", e.getMessage());
+            return responseBody;

Review Comment:
   When a RestClientException is caught, the method returns responseBody which 
will always be null at this point since the exception occurred before or during 
the exchange. This results in returning null instead of providing meaningful 
error information. Consider returning an error message or throwing a custom 
exception to properly communicate the failure to the caller.



##########
console/src/main/java/org/apache/seata/mcp/service/impl/MCPRPCServiceImpl.java:
##########
@@ -0,0 +1,283 @@
+/*
+ * 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.seata.mcp.service.impl;
+
+import org.apache.seata.common.exception.AuthenticationFailedException;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.console.config.WebSecurityConfig;
+import org.apache.seata.console.utils.JwtTokenUtils;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.service.MCPRPCService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class MCPRPCServiceImpl implements MCPRPCService {
+
+    private final JwtTokenUtils jwtTokenUtils;
+
+    public MCPRPCServiceImpl(JwtTokenUtils jwtTokenUtils) {
+        this.jwtTokenUtils = jwtTokenUtils;
+    }
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    private final String NAMING_SPACE_URL = "http://127.0.0.1:%s";;
+
+    private final Logger logger = 
LoggerFactory.getLogger(MCPRPCServiceImpl.class);
+
+    @Value("${server.port:8081}")
+    private String namingSpacePort;
+
+    public String getToken() {
+        Authentication auth = 
SecurityContextHolder.getContext().getAuthentication();
+        if (auth == null || !auth.isAuthenticated()) {
+            throw new AuthenticationFailedException("No right to be 
identified");
+        }
+        String originJwt = (String) auth.getCredentials();
+        if (!jwtTokenUtils.validateToken(originJwt)) {
+            throw new AuthenticationFailedException("Invalid token, please log 
back in to get a new token");
+        }
+        return WebSecurityConfig.TOKEN_PREFIX + originJwt;
+    }
+
+    public void setNamespaceHeaderAndPathParam(
+            NameSpaceDetail nameSpaceDetail, HttpHeaders headers, Map<String, 
String> pathParams) {
+        headers.add("x-seata-namespace", nameSpaceDetail.getNamespace());
+        if (StringUtils.isNotBlank(nameSpaceDetail.getvGroup())) {
+            if (pathParams == null) {
+                pathParams = new HashMap<>();
+            }
+            pathParams.put("vGroup", nameSpaceDetail.getvGroup());
+            return;
+        }
+        if (nameSpaceDetail.getCluster() != null) {
+            headers.add("x-seata-cluster", nameSpaceDetail.getCluster());
+        }
+    }
+
+    public String getCallNameSpace(
+            String path, Object queryParams, Map<String, String> pathParams, 
HttpHeaders headers) {
+        if (headers == null) {
+            headers = new HttpHeaders();
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(queryParams);
+        String url = buildUrl(String.format(NAMING_SPACE_URL, 
namingSpacePort), path, pathParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody = null;
+        try {
+            ResponseEntity<String> response = restTemplate.exchange(url, 
HttpMethod.GET, entity, String.class);
+
+            responseBody = response.getBody();
+
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                logger.warn("MCP GET request returned non-success status: {}", 
response.getStatusCode());
+            }
+            return responseBody;
+        } catch (RestClientException e) {
+            logger.error("MCP GET Call NameSpace Failed: {}", e.getMessage());
+            return responseBody;
+        }
+    }
+
+    @Override
+    public String getCallTC(
+            NameSpaceDetail nameSpaceDetail,
+            String path,
+            Object queryParams,
+            Map<String, String> pathParams,
+            HttpHeaders headers) {
+        if (headers == null) {
+            headers = new HttpHeaders();
+        }
+        if (nameSpaceDetail == null || !nameSpaceDetail.isValid()) {
+            return "If you have not specified the namespace of the TC/Server, 
specify the namespace first";
+        } else {
+            setNamespaceHeaderAndPathParam(nameSpaceDetail, headers, 
pathParams);
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(queryParams);
+        String url = buildUrl(String.format(NAMING_SPACE_URL, 
namingSpacePort), path, pathParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody = null;
+        try {
+            ResponseEntity<String> response = restTemplate.exchange(url, 
HttpMethod.GET, entity, String.class);
+
+            responseBody = response.getBody();
+
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                logger.warn("MCP GET request returned non-success status: {}", 
response.getStatusCode());
+            }
+            return responseBody;
+        } catch (RestClientException e) {
+            logger.error("MCP GET Call TC Failed: {}", e.getMessage());
+            return responseBody;
+        }
+    }
+
+    @Override
+    public String deleteCallTC(
+            NameSpaceDetail nameSpaceDetail,
+            String path,
+            Object queryParams,
+            Map<String, String> pathParams,
+            HttpHeaders headers) {
+        if (headers == null) {
+            headers = new HttpHeaders();
+        }
+        if (nameSpaceDetail == null || !nameSpaceDetail.isValid()) {
+            return "If you have not specified the namespace of the TC/Server, 
specify the namespace first";
+        } else {
+            setNamespaceHeaderAndPathParam(nameSpaceDetail, headers, 
pathParams);
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(queryParams);
+        String url = buildUrl(String.format(NAMING_SPACE_URL, 
namingSpacePort), path, pathParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody = null;
+        try {
+            ResponseEntity<String> response = restTemplate.exchange(url, 
HttpMethod.DELETE, entity, String.class);
+
+            responseBody = response.getBody();
+
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                logger.warn("MCP DELETE request returned non-success status: 
{}", response.getStatusCode());
+            }
+            return responseBody;
+        } catch (RestClientException e) {
+            logger.error("MCP DELETE Call TC Failed: {}", e.getMessage());
+            return responseBody;
+        }
+    }
+
+    @Override
+    public String putCallTC(
+            NameSpaceDetail nameSpaceDetail,
+            String path,
+            Object queryParams,
+            Map<String, String> pathParams,
+            HttpHeaders headers) {
+        if (headers == null) {
+            headers = new HttpHeaders();
+        }
+        if (nameSpaceDetail == null || !nameSpaceDetail.isValid()) {
+            return "If you have not specified the namespace of the TC/Server, 
specify the namespace first";
+        } else {
+            setNamespaceHeaderAndPathParam(nameSpaceDetail, headers, 
pathParams);
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(queryParams);
+        String url = buildUrl(String.format(NAMING_SPACE_URL, 
namingSpacePort), path, pathParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody = null;
+        try {
+            ResponseEntity<String> response = restTemplate.exchange(url, 
HttpMethod.PUT, entity, String.class);
+
+            responseBody = response.getBody();
+
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                logger.warn("MCP PUT request returned non-success status: {}", 
response.getStatusCode());
+            }
+            return responseBody;
+        } catch (RestClientException e) {
+            logger.error("MCP Put Call TC Failed: {}", e.getMessage());
+            return responseBody;
+        }
+    }
+
+    private Map<String, Object> objectToQueryParamMap(Object obj) {
+        if (obj == null) {
+            return Collections.emptyMap();
+        }
+
+        Map<String, Object> paramMap = new HashMap<>();
+
+        if (obj instanceof Map) {
+            ((Map<?, ?>) obj).forEach((key, value) -> {
+                if (key != null && value != null) {
+                    paramMap.put(key.toString(), value);
+                }
+            });
+            return paramMap;
+        }
+
+        Class<?> clazz = obj.getClass();
+        for (Field field : clazz.getDeclaredFields()) {
+            try {
+                field.setAccessible(true);

Review Comment:
   Using reflection with setAccessible(true) can cause security issues in 
environments with a SecurityManager and may fail with Java modules. 
Additionally, this approach bypasses encapsulation. Consider using a library 
like Jackson's ObjectMapper.convertValue() or defining a proper 
interface/contract for objects that need to be converted to query parameters.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to