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


##########
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");

Review Comment:
   The exception message contains a grammatical error. "The time format does 
not match yyyy-mm-dd" should use uppercase "MM" and "DD" to be consistent with 
the actual pattern and with the error message at line 55 which correctly uses 
"yyyy-MM-dd HH:mm:ss".
   ```suggestion
               throw new DateTimeException("The time format does not match 
yyyy-MM-dd");
   ```



##########
console/src/main/java/org/apache/seata/mcp/entity/vo/McpGlobalLockVO.java:
##########
@@ -0,0 +1,136 @@
+/*
+ * 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.entity.vo;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.apache.seata.mcp.core.config.TimestampToStringDeserializer;
+
+public class McpGlobalLockVO {
+
+    private String xid;
+
+    private String transactionId;
+
+    private String branchId;
+
+    private String resourceId;
+
+    private String tableName;
+
+    private String pk;
+
+    private String rowKey;
+
+    /**
+     * the vgroup
+     */
+    private String vgroup;
+
+    @JsonDeserialize(using = TimestampToStringDeserializer.class)
+    private String gmtCreate;
+
+    @JsonDeserialize(using = TimestampToStringDeserializer.class)
+    private String gmtModified;
+
+    public String getXid() {
+        return xid;
+    }
+
+    public void setXid(String xid) {
+        this.xid = xid;
+    }
+
+    public String getTransactionId() {
+        return transactionId;
+    }
+
+    public void setTransactionId(Long transactionId) {
+        this.transactionId = String.valueOf(transactionId);
+    }
+
+    public String getBranchId() {
+        return branchId;
+    }
+
+    public void setBranchId(Long branchId) {
+        this.branchId = String.valueOf(branchId);
+    }

Review Comment:
   Duplicate setter methods detected. The class has two setters for both 
`transactionId` and `branchId`:
   1. Lines 61-63 and 121-123 for `transactionId`
   2. Lines 69-71 and 125-127 for `branchId`
   
   The duplicate setters at lines 121-127 accept String parameters while the 
earlier ones at lines 61-71 accept Long parameters. This creates ambiguity and 
potential runtime issues. You should remove one set of setters or rename them 
to clarify their purpose (e.g., `setTransactionIdFromLong` vs 
`setTransactionIdFromString`).



##########
console/src/main/java/org/apache/seata/mcp/service/impl/ConsoleRemoteServiceImpl.java:
##########
@@ -0,0 +1,244 @@
+/*
+ * 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 com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.seata.common.NamingServerLocalMarker;
+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.ConsoleApiService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import 
org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+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 java.util.Map;
+
+import static org.apache.seata.mcp.core.utils.UrlUtils.buildUrl;
+import static org.apache.seata.mcp.core.utils.UrlUtils.objectToQueryParamMap;
+
+@ConditionalOnMissingBean(NamingServerLocalMarker.class)
+@Service
+public class ConsoleRemoteServiceImpl implements ConsoleApiService {
+
+    private final JwtTokenUtils jwtTokenUtils;
+
+    private final RestTemplate restTemplate;
+
+    private final ObjectMapper objectMapper;
+
+    private final AuthenticationManager authenticationManager;
+
+    public ConsoleRemoteServiceImpl(
+            JwtTokenUtils jwtTokenUtils,
+            RestTemplate restTemplate,
+            ObjectMapper objectMapper,
+            AuthenticationManager authenticationManager) {
+        this.jwtTokenUtils = jwtTokenUtils;
+        this.restTemplate = restTemplate;
+        this.objectMapper = objectMapper;
+        this.authenticationManager = authenticationManager;
+    }
+
+    @Value("${seata.console.naming-space-url:http://127.0.0.1:%s}";)
+    private String namingSpaceUrl;
+
+    private final Logger logger = 
LoggerFactory.getLogger(ConsoleRemoteServiceImpl.class);
+
+    @Value("${server.port:8081}")
+    private String namingSpacePort;
+
+    @Value("${seata.mcp.auth.enabled}")
+    private String enabledAuth;
+
+    public String getToken() {
+        if (Boolean.parseBoolean(enabledAuth)) {
+            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;
+        } else {
+            logger.warn(
+                    "Authentication is disabled 
(seata.mcp.auth.enabled=false); "
+                            + "generating token using internal fallback user. 
This configuration should not be used in production.");

Review Comment:
   Sensitive warning message in production. The warning at lines 94-96 reveals 
internal security configuration details and explicitly states that 
authentication is disabled. This information could be useful to attackers. 
Consider:
   1. Reducing the verbosity of this log message in production
   2. Using a different log level (DEBUG instead of WARN)
   3. Not mentioning specific configuration keys in production logs
   ```suggestion
               logger.debug(
                       "Authentication is disabled; this configuration is not 
recommended for production environments.");
   ```



##########
console/src/main/java/org/apache/seata/mcp/service/impl/ConsoleRemoteServiceImpl.java:
##########
@@ -0,0 +1,244 @@
+/*
+ * 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 com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.seata.common.NamingServerLocalMarker;
+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.ConsoleApiService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import 
org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+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 java.util.Map;
+
+import static org.apache.seata.mcp.core.utils.UrlUtils.buildUrl;
+import static org.apache.seata.mcp.core.utils.UrlUtils.objectToQueryParamMap;
+
+@ConditionalOnMissingBean(NamingServerLocalMarker.class)
+@Service
+public class ConsoleRemoteServiceImpl implements ConsoleApiService {
+
+    private final JwtTokenUtils jwtTokenUtils;
+
+    private final RestTemplate restTemplate;
+
+    private final ObjectMapper objectMapper;
+
+    private final AuthenticationManager authenticationManager;
+
+    public ConsoleRemoteServiceImpl(
+            JwtTokenUtils jwtTokenUtils,
+            RestTemplate restTemplate,
+            ObjectMapper objectMapper,
+            AuthenticationManager authenticationManager) {
+        this.jwtTokenUtils = jwtTokenUtils;
+        this.restTemplate = restTemplate;
+        this.objectMapper = objectMapper;
+        this.authenticationManager = authenticationManager;
+    }
+
+    @Value("${seata.console.naming-space-url:http://127.0.0.1:%s}";)
+    private String namingSpaceUrl;
+
+    private final Logger logger = 
LoggerFactory.getLogger(ConsoleRemoteServiceImpl.class);
+
+    @Value("${server.port:8081}")
+    private String namingSpacePort;
+
+    @Value("${seata.mcp.auth.enabled}")
+    private String enabledAuth;
+
+    public String getToken() {
+        if (Boolean.parseBoolean(enabledAuth)) {
+            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;
+        } else {
+            logger.warn(
+                    "Authentication is disabled 
(seata.mcp.auth.enabled=false); "
+                            + "generating token using internal fallback user. 
This configuration should not be used in production.");
+            UsernamePasswordAuthenticationToken authenticationToken =
+                    new UsernamePasswordAuthenticationToken("seata", "");
+            Authentication authentication = 
authenticationManager.authenticate(authenticationToken);
+            return WebSecurityConfig.TOKEN_PREFIX + 
jwtTokenUtils.createToken(authentication);
+        }
+    }
+
+    public void setNamespaceHeaderAndQueryParam(
+            NameSpaceDetail nameSpaceDetail, HttpHeaders headers, Map<String, 
String> queryParams) {
+        headers.add("x-seata-namespace", nameSpaceDetail.getNamespace());
+        if (StringUtils.isNotBlank(nameSpaceDetail.getvGroup())) {
+            if (queryParams != null) {
+                queryParams.put("vGroup", nameSpaceDetail.getvGroup());
+            }
+            return;
+        }
+        if (nameSpaceDetail.getCluster() != null) {
+            headers.add("x-seata-cluster", nameSpaceDetail.getCluster());
+        }
+    }
+
+    @Override
+    public String getCallNameSpace(String path) {
+        HttpHeaders headers = new HttpHeaders();
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        String url = buildUrl(String.format(namingSpaceUrl, namingSpacePort), 
path, null, null);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody;
+        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);
+            return "Connect TC Failed";
+        }
+    }
+
+    @Override
+    public String getCallTC(
+            NameSpaceDetail nameSpaceDetail,
+            String path,
+            Object objectQueryParams,
+            Map<String, String> queryParams,
+            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 {
+            setNamespaceHeaderAndQueryParam(nameSpaceDetail, headers, 
queryParams);
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(objectQueryParams, objectMapper);
+        String url = buildUrl(String.format(namingSpaceUrl, namingSpacePort), 
path, queryParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody;
+        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);
+            return "Connect TC Failed";
+        }
+    }
+
+    @Override
+    public String deleteCallTC(
+            NameSpaceDetail nameSpaceDetail,
+            String path,
+            Object objectQueryParams,
+            Map<String, String> queryParams,
+            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 {
+            setNamespaceHeaderAndQueryParam(nameSpaceDetail, headers, 
queryParams);
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(objectQueryParams, objectMapper);
+        String url = buildUrl(String.format(namingSpaceUrl, namingSpacePort), 
path, queryParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody;
+        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);
+            return "Connect TC Failed";
+        }
+    }
+
+    @Override
+    public String putCallTC(
+            NameSpaceDetail nameSpaceDetail,
+            String path,
+            Object objectQueryParams,
+            Map<String, String> queryParams,
+            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 {
+            setNamespaceHeaderAndQueryParam(nameSpaceDetail, headers, 
queryParams);
+        }
+        headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+        Map<String, Object> queryParamsMap = 
objectToQueryParamMap(objectQueryParams, objectMapper);
+        String url = buildUrl(String.format(namingSpaceUrl, namingSpacePort), 
path, queryParams, queryParamsMap);
+        HttpEntity<String> entity = new HttpEntity<>(headers);
+        String responseBody;
+        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);
+            return "Connect TC Failed";
+        }

Review Comment:
   Error handling loses exception context. The caught RestClientException 
instances at lines 134, 169, 204, and 239 are logged, but then simple error 
strings are returned instead of proper error responses. This makes debugging 
difficult because:
   1. The calling code receives only generic error messages
   2. Stack traces and root causes are lost
   3. No way to distinguish between different types of failures
   
   Consider returning structured error responses or throwing exceptions that 
preserve the original cause.



##########
console/src/main/java/org/apache/seata/mcp/tools/GlobalLockTools.java:
##########
@@ -0,0 +1,137 @@
+/*
+ * 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.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.McpGlobalLockParamDto;
+import org.apache.seata.mcp.entity.param.McpGlobalLockDeleteParam;
+import org.apache.seata.mcp.entity.param.McpGlobalLockParam;
+import org.apache.seata.mcp.entity.vo.McpGlobalLockVO;
+import org.apache.seata.mcp.service.ConsoleApiService;
+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.HashMap;
+import java.util.Map;
+
+@Service
+public class GlobalLockTools {
+
+    private final Logger logger = 
LoggerFactory.getLogger(GlobalLockTools.class);
+
+    private final ConsoleApiService mcpRPCService;
+
+    private final MCPProperties mcpProperties;
+
+    private final ModifyConfirmService modifyConfirmService;
+
+    private final ObjectMapper objectMapper;
+
+    public GlobalLockTools(
+            ConsoleApiService mcpRPCService,
+            MCPProperties mcpProperties,
+            ModifyConfirmService modifyConfirmService,
+            ObjectMapper objectMapper) {
+        this.mcpRPCService = mcpRPCService;
+        this.mcpProperties = mcpProperties;
+        this.modifyConfirmService = modifyConfirmService;
+        this.objectMapper = objectMapper;
+    }
+
+    @McpTool(description = "Query the global lock information")
+    public PageResult<McpGlobalLockVO> queryGlobalLock(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global lock parameters") 
McpGlobalLockParamDto paramDto) {
+        McpGlobalLockParam param = 
McpGlobalLockParam.convertFromParamDto(paramDto);
+        if (param.getTimeStart() != null) {
+            if (param.getTimeEnd() != null) {
+                if (DateUtils.judgeExceedTimeDuration(
+                        param.getTimeStart(), param.getTimeEnd(), 
mcpProperties.getQueryDuration())) {
+                    return PageResult.failure(
+                            "",
+                            String.format(
+                                    "The query time span is not allowed to 
exceed the max query duration: %s hour",
+                                    
DateUtils.convertToHourFromTimeStamp(mcpProperties.getQueryDuration())));
+                }
+            } else {
+                param.setTimeEnd(param.getTimeStart() + 
DateUtils.ONE_DAY_TIMESTAMP);
+            }
+        } else {
+            param.setTimeEnd(null);
+            param.setTimeStart(null);
+        }
+        PageResult<McpGlobalLockVO> result = null;
+        String response = mcpRPCService.getCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_LOCK_BASE_URL + "/query", 
param, null, null);
+        try {
+            result = objectMapper.readValue(response, new 
TypeReference<PageResult<McpGlobalLockVO>>() {});
+        } catch (JsonProcessingException e) {
+            logger.error(e.getMessage());
+        }
+        if (result == null) {
+            return PageResult.failure("", "query global lock failed");
+        } else {
+            return result;
+        }
+    }
+
+    @McpTool(description = "Delete the global lock, Get the modify key before 
you delete")
+    public String deleteGlobalLock(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global lock delete parameters") 
McpGlobalLockDeleteParam param,
+            @McpToolParam(description = "Modify key") String modifyKey) {
+        if (!modifyConfirmService.isValidKey(modifyKey)) {
+            return "The modify key is not available";
+        }
+        String result = mcpRPCService.deleteCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_LOCK_BASE_URL + "/delete", 
param, null, null);
+        if (StringUtils.isBlank(result)) {
+            return "delete global lock failed";

Review Comment:
   The error message template is inconsistent. This message does not include 
the xid/branchId parameters in the error message format, but similar error 
messages in the same file (lines 58, 79, 100) do include them. For consistency 
and better debugging, the error message should include the actual xid and 
branchId values that failed.



##########
console/src/main/java/org/apache/seata/mcp/service/impl/ConsoleRemoteServiceImpl.java:
##########
@@ -0,0 +1,244 @@
+/*
+ * 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 com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.seata.common.NamingServerLocalMarker;
+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.ConsoleApiService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import 
org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+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 java.util.Map;
+
+import static org.apache.seata.mcp.core.utils.UrlUtils.buildUrl;
+import static org.apache.seata.mcp.core.utils.UrlUtils.objectToQueryParamMap;
+
+@ConditionalOnMissingBean(NamingServerLocalMarker.class)
+@Service
+public class ConsoleRemoteServiceImpl implements ConsoleApiService {
+
+    private final JwtTokenUtils jwtTokenUtils;
+
+    private final RestTemplate restTemplate;
+
+    private final ObjectMapper objectMapper;
+
+    private final AuthenticationManager authenticationManager;
+
+    public ConsoleRemoteServiceImpl(
+            JwtTokenUtils jwtTokenUtils,
+            RestTemplate restTemplate,
+            ObjectMapper objectMapper,
+            AuthenticationManager authenticationManager) {
+        this.jwtTokenUtils = jwtTokenUtils;
+        this.restTemplate = restTemplate;
+        this.objectMapper = objectMapper;
+        this.authenticationManager = authenticationManager;
+    }
+
+    @Value("${seata.console.naming-space-url:http://127.0.0.1:%s}";)
+    private String namingSpaceUrl;
+
+    private final Logger logger = 
LoggerFactory.getLogger(ConsoleRemoteServiceImpl.class);
+
+    @Value("${server.port:8081}")
+    private String namingSpacePort;
+
+    @Value("${seata.mcp.auth.enabled}")
+    private String enabledAuth;
+
+    public String getToken() {
+        if (Boolean.parseBoolean(enabledAuth)) {
+            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;
+        } else {
+            logger.warn(
+                    "Authentication is disabled 
(seata.mcp.auth.enabled=false); "
+                            + "generating token using internal fallback user. 
This configuration should not be used in production.");
+            UsernamePasswordAuthenticationToken authenticationToken =
+                    new UsernamePasswordAuthenticationToken("seata", "");
+            Authentication authentication = 
authenticationManager.authenticate(authenticationToken);
+            return WebSecurityConfig.TOKEN_PREFIX + 
jwtTokenUtils.createToken(authentication);

Review Comment:
   Hardcoded fallback credentials are a security risk. Line 98 uses hardcoded 
credentials `new UsernamePasswordAuthenticationToken("seata", "")` when 
authentication is disabled. Even though this is only used when auth is 
disabled, hardcoded credentials in source code are a security anti-pattern and 
could be exploited if:
   1. The configuration is accidentally disabled in production
   2. The code is copied and reused elsewhere
   3. An attacker gains access to disable the auth configuration
   
   Consider using a configuration-based approach for fallback credentials or 
requiring explicit admin approval for disabling authentication.



##########
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.McpGlobalSessionParamDto;
+import org.apache.seata.mcp.entity.param.McpGlobalAbnormalSessionParam;
+import org.apache.seata.mcp.entity.param.McpGlobalSessionParam;
+import org.apache.seata.mcp.entity.vo.McpGlobalSessionVO;
+import org.apache.seata.mcp.service.ConsoleApiService;
+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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class GlobalSessionTools {
+
+    private final Logger logger = 
LoggerFactory.getLogger(GlobalSessionTools.class);
+
+    private final ConsoleApiService mcpRPCService;
+
+    private final MCPProperties mcpProperties;
+
+    private final ObjectMapper objectMapper;
+
+    private final ModifyConfirmService modifyConfirmService;
+
+    private final List<Integer> exceptionStatus = new ArrayList<>();
+
+    public static final int ABNORMAL_SESSION_PAGE_SIZE = 30;
+
+    public GlobalSessionTools(
+            ConsoleApiService mcpRPCService,
+            MCPProperties mcpProperties,
+            ObjectMapper objectMapper,
+            ModifyConfirmService modifyConfirmService) {
+        this.mcpRPCService = mcpRPCService;
+        this.mcpProperties = mcpProperties;
+        this.objectMapper = objectMapper;
+        this.modifyConfirmService = modifyConfirmService;
+        exceptionStatus.add(GlobalStatus.CommitFailed.getCode());
+        exceptionStatus.add(GlobalStatus.TimeoutRollbackFailed.getCode());
+        exceptionStatus.add(GlobalStatus.RollbackFailed.getCode());
+    }
+
+    @McpTool(description = "Query global transactions")
+    public PageResult<McpGlobalSessionVO> queryGlobalSession(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Query parameter objects") 
McpGlobalSessionParamDto paramDto) {
+        McpGlobalSessionParam param = 
McpGlobalSessionParam.convertFromDtoParam(paramDto);
+        if (param.getTimeStart() != null) {
+            if (param.getTimeEnd() != null) {
+                if (DateUtils.judgeExceedTimeDuration(
+                        param.getTimeStart(), param.getTimeEnd(), 
mcpProperties.getQueryDuration())) {
+                    return PageResult.failure(
+                            "",
+                            String.format(
+                                    "The query time span is not allowed to 
exceed the max query duration: %s hours",
+                                    
DateUtils.convertToHourFromTimeStamp(mcpProperties.getQueryDuration())));
+                }
+            } else {
+                param.setTimeEnd(param.getTimeStart() + 
DateUtils.ONE_DAY_TIMESTAMP);
+            }
+        } else {
+            param.setTimeEnd(null);
+            param.setTimeStart(null);
+        }
+        PageResult<McpGlobalSessionVO> pageResult = null;
+        String result = mcpRPCService.getCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/query", param, null, null);
+        try {
+            pageResult = objectMapper.readValue(result, new 
TypeReference<PageResult<McpGlobalSessionVO>>() {});
+        } catch (JsonProcessingException e) {
+            logger.error(e.getMessage());
+        }
+        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<McpGlobalSessionVO> getAbnormalSessions(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Query Param") 
McpGlobalAbnormalSessionParam abnormalSessionParam) {
+        List<McpGlobalSessionVO> result = new ArrayList<>();
+        McpGlobalSessionParamDto param = 
McpGlobalSessionParamDto.convertFromAbnormalParam(abnormalSessionParam);
+        param.setPageSize(ABNORMAL_SESSION_PAGE_SIZE);
+        for (Integer status : exceptionStatus) {
+            param.setStatus(status);
+            List<McpGlobalSessionVO> datas =
+                    queryGlobalSession(nameSpaceDetail, param).getData();

Review Comment:
   The variable name `datas` is incorrect English. The plural of "data" is 
"data", not "datas". The variable should be renamed to `data` or `sessionData`.



##########
namingserver/src/main/java/org/apache/seata/namingserver/service/ConsoleLocalServiceImpl.java:
##########
@@ -0,0 +1,163 @@
+/*
+ * 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.namingserver.service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.seata.common.metadata.ClusterRole;
+import org.apache.seata.common.metadata.Node;
+import org.apache.seata.common.metadata.namingserver.NamingServerNode;
+import org.apache.seata.common.util.CollectionUtils;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.service.ConsoleApiService;
+import org.apache.seata.mcp.service.impl.ConsoleRemoteServiceImpl;
+import org.apache.seata.namingserver.manager.NamingManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static org.apache.seata.common.Constants.RAFT_GROUP_HEADER;
+import static org.apache.seata.mcp.core.utils.UrlUtils.buildUrl;
+import static org.apache.seata.mcp.core.utils.UrlUtils.objectToQueryParamMap;
+
+@ConditionalOnBean(ConsoleRemoteServiceImpl.class)
+@Primary
+@Service
+public class ConsoleLocalServiceImpl implements ConsoleApiService {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private final NamingManager namingManager;
+
+    private final RestTemplate restTemplate;
+
+    private final ObjectMapper objectMapper;
+
+    public ConsoleLocalServiceImpl(NamingManager namingManager, RestTemplate 
restTemplate, ObjectMapper objectMapper) {
+        this.namingManager = namingManager;
+        this.restTemplate = restTemplate;
+        this.objectMapper = objectMapper;
+    }
+
+    public String getResult(
+            NameSpaceDetail nameSpaceDetail,
+            HttpMethod httpMethod,
+            String path,
+            Object objectQueryParams,
+            Map<String, String> queryParams,
+            HttpHeaders headers) {
+        String namespace = nameSpaceDetail.getNamespace();
+        String cluster = nameSpaceDetail.getCluster();
+        String vgroup = nameSpaceDetail.getvGroup();
+        if (StringUtils.isNotBlank(namespace) && 
(StringUtils.isNotBlank(cluster) || StringUtils.isNotBlank(vgroup))) {
+            List<NamingServerNode> list = null;
+            if (StringUtils.isNotBlank(vgroup)) {
+                list = namingManager.getInstancesByVgroupAndNamespace(
+                        namespace, vgroup, HttpMethod.GET.equals(httpMethod));
+            } else if (StringUtils.isNotBlank(cluster)) {
+                list = namingManager.getInstances(namespace, cluster);
+            }
+            if (CollectionUtils.isNotEmpty(list)) {
+                // Randomly select a node from the list
+                NamingServerNode node = 
list.get(ThreadLocalRandom.current().nextInt(list.size()));
+                Node.Endpoint controlEndpoint = node.getControl();
+                if (controlEndpoint != null) {
+                    // Construct the target URL
+                    String baseUrl = "http://"; + controlEndpoint.getHost() + 
":" + controlEndpoint.getPort();
+                    Map<String, Object> queryParamsMap = 
objectToQueryParamMap(objectQueryParams, objectMapper);
+                    String targetUrl = buildUrl(baseUrl, path, queryParams, 
queryParamsMap);
+                    if (node.getRole() == ClusterRole.LEADER) {
+                        headers.add(RAFT_GROUP_HEADER, node.getUnit());
+                    }
+                    HttpEntity<String> entity = new HttpEntity<>(headers);
+                    String responseBody;
+                    try {
+                        ResponseEntity<String> response =
+                                restTemplate.exchange(targetUrl, httpMethod, 
entity, String.class);
+
+                        responseBody = response.getBody();
+
+                        if (!response.getStatusCode().is2xxSuccessful()) {
+                            logger.warn("MCP request returned non-success 
status: {}", response.getStatusCode());
+                        }
+                        return responseBody;
+                    } catch (RestClientException e) {
+                        logger.error("MCP {} Call TC Failed: {}", 
httpMethod.name(), e.getMessage());
+                        throw new RestClientException("Connect TC Failed");

Review Comment:
   The exception is caught but the original error message is discarded. The 
caught RestClientException at line 111 is logged with only its message, and 
then a new generic RestClientException is thrown with "Connect TC Failed". This 
loses valuable debugging information about why the connection failed. The 
original exception should be passed as the cause to the new exception.



##########
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.McpGlobalSessionParamDto;
+import org.apache.seata.mcp.entity.param.McpGlobalAbnormalSessionParam;
+import org.apache.seata.mcp.entity.param.McpGlobalSessionParam;
+import org.apache.seata.mcp.entity.vo.McpGlobalSessionVO;
+import org.apache.seata.mcp.service.ConsoleApiService;
+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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class GlobalSessionTools {
+
+    private final Logger logger = 
LoggerFactory.getLogger(GlobalSessionTools.class);
+
+    private final ConsoleApiService mcpRPCService;
+
+    private final MCPProperties mcpProperties;
+
+    private final ObjectMapper objectMapper;
+
+    private final ModifyConfirmService modifyConfirmService;
+
+    private final List<Integer> exceptionStatus = new ArrayList<>();
+
+    public static final int ABNORMAL_SESSION_PAGE_SIZE = 30;
+
+    public GlobalSessionTools(
+            ConsoleApiService mcpRPCService,
+            MCPProperties mcpProperties,
+            ObjectMapper objectMapper,
+            ModifyConfirmService modifyConfirmService) {
+        this.mcpRPCService = mcpRPCService;
+        this.mcpProperties = mcpProperties;
+        this.objectMapper = objectMapper;
+        this.modifyConfirmService = modifyConfirmService;
+        exceptionStatus.add(GlobalStatus.CommitFailed.getCode());
+        exceptionStatus.add(GlobalStatus.TimeoutRollbackFailed.getCode());
+        exceptionStatus.add(GlobalStatus.RollbackFailed.getCode());
+    }
+
+    @McpTool(description = "Query global transactions")
+    public PageResult<McpGlobalSessionVO> queryGlobalSession(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Query parameter objects") 
McpGlobalSessionParamDto paramDto) {
+        McpGlobalSessionParam param = 
McpGlobalSessionParam.convertFromDtoParam(paramDto);
+        if (param.getTimeStart() != null) {
+            if (param.getTimeEnd() != null) {
+                if (DateUtils.judgeExceedTimeDuration(
+                        param.getTimeStart(), param.getTimeEnd(), 
mcpProperties.getQueryDuration())) {
+                    return PageResult.failure(
+                            "",
+                            String.format(
+                                    "The query time span is not allowed to 
exceed the max query duration: %s hours",
+                                    
DateUtils.convertToHourFromTimeStamp(mcpProperties.getQueryDuration())));
+                }
+            } else {
+                param.setTimeEnd(param.getTimeStart() + 
DateUtils.ONE_DAY_TIMESTAMP);
+            }
+        } else {
+            param.setTimeEnd(null);
+            param.setTimeStart(null);
+        }

Review Comment:
   The condition check has a logic issue. When `endTime` is null but 
`startTime` is not null, the code sets `timeEnd` to `startTime + 
ONE_DAY_TIMESTAMP` (line 93). However, it then immediately proceeds to check if 
the duration exceeds the max query duration, which will always pass since we 
just set it to one day. But then on line 96, when `timeStart` is null, both 
`timeEnd` and `timeStart` are set to null, which means the previous calculation 
on line 93 gets overwritten.
   
   The logic at line 96 should be inside an `else` block or there should be a 
`return` after line 94 to avoid the null assignment overwriting the calculated 
timeEnd.



##########
console/src/main/java/org/apache/seata/mcp/tools/GlobalLockTools.java:
##########
@@ -0,0 +1,137 @@
+/*
+ * 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.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.McpGlobalLockParamDto;
+import org.apache.seata.mcp.entity.param.McpGlobalLockDeleteParam;
+import org.apache.seata.mcp.entity.param.McpGlobalLockParam;
+import org.apache.seata.mcp.entity.vo.McpGlobalLockVO;
+import org.apache.seata.mcp.service.ConsoleApiService;
+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.HashMap;
+import java.util.Map;
+
+@Service
+public class GlobalLockTools {
+
+    private final Logger logger = 
LoggerFactory.getLogger(GlobalLockTools.class);
+
+    private final ConsoleApiService mcpRPCService;
+
+    private final MCPProperties mcpProperties;
+
+    private final ModifyConfirmService modifyConfirmService;
+
+    private final ObjectMapper objectMapper;
+
+    public GlobalLockTools(
+            ConsoleApiService mcpRPCService,
+            MCPProperties mcpProperties,
+            ModifyConfirmService modifyConfirmService,
+            ObjectMapper objectMapper) {
+        this.mcpRPCService = mcpRPCService;
+        this.mcpProperties = mcpProperties;
+        this.modifyConfirmService = modifyConfirmService;
+        this.objectMapper = objectMapper;
+    }
+
+    @McpTool(description = "Query the global lock information")
+    public PageResult<McpGlobalLockVO> queryGlobalLock(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global lock parameters") 
McpGlobalLockParamDto paramDto) {
+        McpGlobalLockParam param = 
McpGlobalLockParam.convertFromParamDto(paramDto);
+        if (param.getTimeStart() != null) {
+            if (param.getTimeEnd() != null) {
+                if (DateUtils.judgeExceedTimeDuration(
+                        param.getTimeStart(), param.getTimeEnd(), 
mcpProperties.getQueryDuration())) {
+                    return PageResult.failure(
+                            "",
+                            String.format(
+                                    "The query time span is not allowed to 
exceed the max query duration: %s hour",
+                                    
DateUtils.convertToHourFromTimeStamp(mcpProperties.getQueryDuration())));
+                }
+            } else {
+                param.setTimeEnd(param.getTimeStart() + 
DateUtils.ONE_DAY_TIMESTAMP);
+            }
+        } else {
+            param.setTimeEnd(null);
+            param.setTimeStart(null);
+        }

Review Comment:
   The same logic issue exists here as in GlobalSessionTools. When `timeStart` 
is null (line 95), both `timeEnd` and `timeStart` are set to null (lines 
86-87), potentially overwriting the calculated `timeEnd` value from line 83. 
This should be structured with proper if-else logic to prevent the time values 
from being incorrectly reset.



##########
console/src/main/java/org/apache/seata/mcp/tools/GlobalLockTools.java:
##########
@@ -0,0 +1,137 @@
+/*
+ * 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.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.McpGlobalLockParamDto;
+import org.apache.seata.mcp.entity.param.McpGlobalLockDeleteParam;
+import org.apache.seata.mcp.entity.param.McpGlobalLockParam;
+import org.apache.seata.mcp.entity.vo.McpGlobalLockVO;
+import org.apache.seata.mcp.service.ConsoleApiService;
+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.HashMap;
+import java.util.Map;
+
+@Service
+public class GlobalLockTools {
+
+    private final Logger logger = 
LoggerFactory.getLogger(GlobalLockTools.class);
+
+    private final ConsoleApiService mcpRPCService;
+
+    private final MCPProperties mcpProperties;
+
+    private final ModifyConfirmService modifyConfirmService;
+
+    private final ObjectMapper objectMapper;
+
+    public GlobalLockTools(
+            ConsoleApiService mcpRPCService,
+            MCPProperties mcpProperties,
+            ModifyConfirmService modifyConfirmService,
+            ObjectMapper objectMapper) {
+        this.mcpRPCService = mcpRPCService;
+        this.mcpProperties = mcpProperties;
+        this.modifyConfirmService = modifyConfirmService;
+        this.objectMapper = objectMapper;
+    }
+
+    @McpTool(description = "Query the global lock information")
+    public PageResult<McpGlobalLockVO> queryGlobalLock(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Global lock parameters") 
McpGlobalLockParamDto paramDto) {
+        McpGlobalLockParam param = 
McpGlobalLockParam.convertFromParamDto(paramDto);
+        if (param.getTimeStart() != null) {
+            if (param.getTimeEnd() != null) {
+                if (DateUtils.judgeExceedTimeDuration(
+                        param.getTimeStart(), param.getTimeEnd(), 
mcpProperties.getQueryDuration())) {
+                    return PageResult.failure(
+                            "",
+                            String.format(
+                                    "The query time span is not allowed to 
exceed the max query duration: %s hour",

Review Comment:
   The error message uses singular "hour" but should be plural "hours" to match 
the message in GlobalSessionTools.java line 89 which correctly uses "hours".



##########
namingserver/src/main/java/org/apache/seata/namingserver/service/ConsoleLocalServiceImpl.java:
##########
@@ -0,0 +1,163 @@
+/*
+ * 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.namingserver.service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.seata.common.metadata.ClusterRole;
+import org.apache.seata.common.metadata.Node;
+import org.apache.seata.common.metadata.namingserver.NamingServerNode;
+import org.apache.seata.common.util.CollectionUtils;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.service.ConsoleApiService;
+import org.apache.seata.mcp.service.impl.ConsoleRemoteServiceImpl;
+import org.apache.seata.namingserver.manager.NamingManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static org.apache.seata.common.Constants.RAFT_GROUP_HEADER;
+import static org.apache.seata.mcp.core.utils.UrlUtils.buildUrl;
+import static org.apache.seata.mcp.core.utils.UrlUtils.objectToQueryParamMap;
+
+@ConditionalOnBean(ConsoleRemoteServiceImpl.class)
+@Primary
+@Service
+public class ConsoleLocalServiceImpl implements ConsoleApiService {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private final NamingManager namingManager;
+
+    private final RestTemplate restTemplate;
+
+    private final ObjectMapper objectMapper;
+
+    public ConsoleLocalServiceImpl(NamingManager namingManager, RestTemplate 
restTemplate, ObjectMapper objectMapper) {
+        this.namingManager = namingManager;
+        this.restTemplate = restTemplate;
+        this.objectMapper = objectMapper;
+    }
+
+    public String getResult(
+            NameSpaceDetail nameSpaceDetail,
+            HttpMethod httpMethod,
+            String path,
+            Object objectQueryParams,
+            Map<String, String> queryParams,
+            HttpHeaders headers) {
+        String namespace = nameSpaceDetail.getNamespace();
+        String cluster = nameSpaceDetail.getCluster();
+        String vgroup = nameSpaceDetail.getvGroup();
+        if (StringUtils.isNotBlank(namespace) && 
(StringUtils.isNotBlank(cluster) || StringUtils.isNotBlank(vgroup))) {
+            List<NamingServerNode> list = null;
+            if (StringUtils.isNotBlank(vgroup)) {
+                list = namingManager.getInstancesByVgroupAndNamespace(
+                        namespace, vgroup, HttpMethod.GET.equals(httpMethod));
+            } else if (StringUtils.isNotBlank(cluster)) {
+                list = namingManager.getInstances(namespace, cluster);
+            }
+            if (CollectionUtils.isNotEmpty(list)) {
+                // Randomly select a node from the list
+                NamingServerNode node = 
list.get(ThreadLocalRandom.current().nextInt(list.size()));

Review Comment:
   Random node selection could lead to uneven load distribution and doesn't 
account for node health or availability. Using `ThreadLocalRandom` for node 
selection means:
   1. Unhealthy or slow nodes might still be selected
   2. No failover mechanism if the selected node is unavailable
   3. No consideration for node capacity or current load
   
   Consider implementing a more robust load balancing strategy with health 
checks, or at least retry logic to attempt other nodes if the selected one 
fails.



##########
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.McpGlobalSessionParamDto;
+import org.apache.seata.mcp.entity.param.McpGlobalAbnormalSessionParam;
+import org.apache.seata.mcp.entity.param.McpGlobalSessionParam;
+import org.apache.seata.mcp.entity.vo.McpGlobalSessionVO;
+import org.apache.seata.mcp.service.ConsoleApiService;
+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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class GlobalSessionTools {
+
+    private final Logger logger = 
LoggerFactory.getLogger(GlobalSessionTools.class);
+
+    private final ConsoleApiService mcpRPCService;
+
+    private final MCPProperties mcpProperties;
+
+    private final ObjectMapper objectMapper;
+
+    private final ModifyConfirmService modifyConfirmService;
+
+    private final List<Integer> exceptionStatus = new ArrayList<>();
+
+    public static final int ABNORMAL_SESSION_PAGE_SIZE = 30;
+
+    public GlobalSessionTools(
+            ConsoleApiService mcpRPCService,
+            MCPProperties mcpProperties,
+            ObjectMapper objectMapper,
+            ModifyConfirmService modifyConfirmService) {
+        this.mcpRPCService = mcpRPCService;
+        this.mcpProperties = mcpProperties;
+        this.objectMapper = objectMapper;
+        this.modifyConfirmService = modifyConfirmService;
+        exceptionStatus.add(GlobalStatus.CommitFailed.getCode());
+        exceptionStatus.add(GlobalStatus.TimeoutRollbackFailed.getCode());
+        exceptionStatus.add(GlobalStatus.RollbackFailed.getCode());
+    }
+
+    @McpTool(description = "Query global transactions")
+    public PageResult<McpGlobalSessionVO> queryGlobalSession(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Query parameter objects") 
McpGlobalSessionParamDto paramDto) {
+        McpGlobalSessionParam param = 
McpGlobalSessionParam.convertFromDtoParam(paramDto);
+        if (param.getTimeStart() != null) {
+            if (param.getTimeEnd() != null) {
+                if (DateUtils.judgeExceedTimeDuration(
+                        param.getTimeStart(), param.getTimeEnd(), 
mcpProperties.getQueryDuration())) {
+                    return PageResult.failure(
+                            "",
+                            String.format(
+                                    "The query time span is not allowed to 
exceed the max query duration: %s hours",
+                                    
DateUtils.convertToHourFromTimeStamp(mcpProperties.getQueryDuration())));
+                }
+            } else {
+                param.setTimeEnd(param.getTimeStart() + 
DateUtils.ONE_DAY_TIMESTAMP);
+            }
+        } else {
+            param.setTimeEnd(null);
+            param.setTimeStart(null);
+        }
+        PageResult<McpGlobalSessionVO> pageResult = null;
+        String result = mcpRPCService.getCallTC(
+                nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL + 
"/query", param, null, null);
+        try {
+            pageResult = objectMapper.readValue(result, new 
TypeReference<PageResult<McpGlobalSessionVO>>() {});
+        } catch (JsonProcessingException e) {
+            logger.error(e.getMessage());
+        }
+        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<McpGlobalSessionVO> getAbnormalSessions(
+            @McpToolParam(description = "Specify the namespace of the TC 
node") NameSpaceDetail nameSpaceDetail,
+            @McpToolParam(description = "Query Param") 
McpGlobalAbnormalSessionParam abnormalSessionParam) {
+        List<McpGlobalSessionVO> result = new ArrayList<>();
+        McpGlobalSessionParamDto param = 
McpGlobalSessionParamDto.convertFromAbnormalParam(abnormalSessionParam);
+        param.setPageSize(ABNORMAL_SESSION_PAGE_SIZE);
+        for (Integer status : exceptionStatus) {
+            param.setStatus(status);
+            List<McpGlobalSessionVO> datas =
+                    queryGlobalSession(nameSpaceDetail, param).getData();
+            if (datas != null && !datas.isEmpty()) {
+                result.addAll(datas);
+            }
+        }

Review Comment:
   Potential N+1 query pattern in abnormal session lookup. The method iterates 
over `exceptionStatus` list (lines 218-225) and calls `queryGlobalSession` for 
each status. Each call likely results in a separate database query or remote 
API call. For 3 exception statuses, this means 3 separate round trips.
   
   Consider modifying the query to accept multiple statuses at once, or 
implement batch querying to reduce the number of remote calls and improve 
performance.



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