content: /*
   * Copyright 1999-2025 Alibaba Group Holding Ltd.
   *
   * Licensed 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 com.alibaba.nacos.ai.service;
  
  import com.alibaba.nacos.ai.constant.Constants;
  import com.alibaba.nacos.ai.index.McpServerIndex;
  import com.alibaba.nacos.ai.model.mcp.McpServerIndexData;
  import com.alibaba.nacos.ai.model.mcp.McpServerStorageInfo;
  import com.alibaba.nacos.ai.utils.McpConfigUtils;
  import com.alibaba.nacos.ai.utils.McpRequestUtil;
  import com.alibaba.nacos.api.ai.constant.AiConstants;
  import com.alibaba.nacos.api.ai.model.mcp.FrontEndpointConfig;
  import com.alibaba.nacos.api.ai.model.mcp.McpCapability;
  import com.alibaba.nacos.api.ai.model.mcp.McpEndpointInfo;
  import com.alibaba.nacos.api.ai.model.mcp.McpEndpointSpec;
  import com.alibaba.nacos.api.ai.model.mcp.McpServerBasicInfo;
  import com.alibaba.nacos.api.ai.model.mcp.McpServerDetailInfo;
  import com.alibaba.nacos.api.ai.model.mcp.McpServerVersionInfo;
  import com.alibaba.nacos.api.ai.model.mcp.McpServiceRef;
  import com.alibaba.nacos.api.ai.model.mcp.McpToolSpecification;
  import com.alibaba.nacos.api.ai.model.mcp.registry.KeyValueInput;
  import com.alibaba.nacos.api.ai.model.mcp.registry.ServerVersionDetail;
  import com.alibaba.nacos.api.config.ConfigType;
  import com.alibaba.nacos.api.exception.NacosException;
  import com.alibaba.nacos.api.exception.api.NacosApiException;
  import com.alibaba.nacos.api.model.Page;
  import com.alibaba.nacos.api.model.v2.ErrorCode;
  import com.alibaba.nacos.api.naming.pojo.Instance;
  import com.alibaba.nacos.common.utils.CollectionUtils;
  import com.alibaba.nacos.common.utils.InternetAddressUtil;
  import com.alibaba.nacos.common.utils.JacksonUtils;
  import com.alibaba.nacos.common.utils.StringUtils;
  import com.alibaba.nacos.config.server.model.ConfigRequestInfo;
  import com.alibaba.nacos.config.server.model.form.ConfigForm;
  import com.alibaba.nacos.config.server.model.form.ConfigFormV3;
  import com.alibaba.nacos.config.server.service.ConfigOperationService;
  import com.alibaba.nacos.config.server.service.query.ConfigQueryChainService;
  import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest;
  import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse;
  import com.alibaba.nacos.naming.core.v2.pojo.Service;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.beans.BeanUtils;
  
  import java.time.ZoneOffset;
  import java.time.ZonedDateTime;
  import java.time.format.DateTimeFormatter;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Objects;
  import java.util.Set;
  import java.util.UUID;
  import java.util.stream.Collectors;
  
  import static com.alibaba.nacos.ai.constant.Constants.MCP_SERVER_CONFIG_MARK;
  import static com.alibaba.nacos.ai.utils.McpConfigUtils.buildMcpServerVersionConfigTags;
  
  /**
   * Nacos AI MCP server operation service. Currently, mcp server is present by there configs: 1. mcp server version info
   * {@link McpServerVersionInfo} 2. mcp server description for specified version {@link McpServerDetailInfo} 3. mcp tools
   * info {@link McpToolSpecification} when create the mcp server, we will tag the {@link McpServerVersionInfo} with mcp
   * servername for name fuzzy search.
   *
   * @author xiweng.yy
   */
  @org.springframework.stereotype.Service
  public class McpServerOperationService {
      
      private static final Logger LOGGER = LoggerFactory.getLogger(McpServerOperationService.class);
      
      private final ConfigQueryChainService configQueryChainService;
      
      private final ConfigOperationService configOperationService;
      
      private final McpToolOperationService toolOperationService;
      
      private final McpEndpointOperationService endpointOperationService;
      
      private final McpServerIndex mcpServerIndex;
      
      private final SyncEffectService syncEffectService;
      
      public McpServerOperationService(ConfigQueryChainService configQueryChainService,
              ConfigOperationService configOperationService, McpToolOperationService toolOperationService,
              McpEndpointOperationService endpointOperationService, McpServerIndex mcpServerIndex,
              SyncEffectService syncEffectService) {
          this.configQueryChainService = configQueryChainService;
          this.configOperationService = configOperationService;
          this.toolOperationService = toolOperationService;
          this.endpointOperationService = endpointOperationService;
          this.mcpServerIndex = mcpServerIndex;
          this.syncEffectService = syncEffectService;
      }
      
      /**
       * List mcp server.
       *
       * @param namespaceId namespace id of mcp servers
       * @param mcpName     mcp name pattern, if null or empty, filter all mcp servers.
       * @param search      search type `blur` or `accurate`, means whether to search by fuzzy or exact match by
       *                    `mcpName`.
       * @param pageNo      page number, start from 1
       * @param pageSize    page size each page
       * @return list of {@link McpServerBasicInfo} matched input parameters.
       */
      public Page<McpServerBasicInfo> listMcpServerWithPage(String namespaceId, String mcpName, String search, int pageNo,
              int pageSize) {
          int offset = pageSize * (pageNo - 1);
          Page<McpServerIndexData> indexData = mcpServerIndex.searchMcpServerByName(namespaceId, mcpName, search, offset,
                  pageSize);
          return mapIndexToBasicServerInfo(indexData);
      }
      
      private Page<McpServerBasicInfo> mapIndexToBasicServerInfo(Page<McpServerIndexData> indexData) {
          Page<McpServerBasicInfo> result = new Page<>();
          result.setTotalCount(indexData.getTotalCount());
          result.setPageNumber(indexData.getPageNumber());
          result.setPagesAvailable(indexData.getPagesAvailable());
          
          List<McpServerBasicInfo> finalResult = Collections.emptyList();
          
          if (CollectionUtils.isNotEmpty(indexData.getPageItems())) {
              finalResult = indexData.getPageItems().stream()
                      .map((index) -> buildQueryMcpServerVersionInfoRequest(index.getNamespaceId(), index.getId()))
                      .map(configQueryChainService::handle).map(ConfigQueryChainResponse::getContent)
                      .map(this::transferToMcpServerVersionInfo).collect(Collectors.toList());
          }
          result.setPageItems(finalResult);
          return result;
      }
      
      /**
       * List mcp server.
       *
       * @param namespaceId namespace id of mcp servers
       * @param mcpName     mcp name pattern, if null or empty, filter all mcp servers.
       * @param search      search type `blur` or `accurate`, means whether to search by fuzzy or exact match by
       *                    `mcpName`.
       * @param offset      offset
       * @param limit       limit
       * @return list of {@link McpServerBasicInfo} matched input parameters.
       */
      public Page<McpServerBasicInfo> listMcpServerWithOffset(String namespaceId, String mcpName, String search,
              int offset, int limit) {
          Page<McpServerIndexData> indexData = mcpServerIndex.searchMcpServerByName(namespaceId, mcpName, search, offset,
                  limit);
          return mapIndexToBasicServerInfo(indexData);
      }
      
      /**
       * Get specified mcp server detail info. mcpServerId or namespaceId + mcpServerName is needed.
       *
       * @param namespaceId namespace id of mcp server
       * @param mcpServerId id of mcp server
       * @return detail info with {@link McpServerDetailInfo}
       * @throws NacosException any exception during handling
       */
      public McpServerDetailInfo getMcpServerDetail(String namespaceId, String mcpServerId, String mcpServerName,
              String version) throws NacosException {
          mcpServerId = resolveMcpServerId(namespaceId, mcpServerName, mcpServerId);
          
          McpServerVersionInfo mcpServerVersionInfo = getMcpServerVersionInfo(namespaceId, mcpServerId);
          if (StringUtils.isEmpty(version)) {
              int size = mcpServerVersionInfo.getVersionDetails().size();
              ServerVersionDetail last = mcpServerVersionInfo.getVersionDetails().get(size - 1);
              version = last.getVersion();
          }
          
          ConfigQueryChainRequest request = buildQueryMcpServerRequest(namespaceId, mcpServerId, version);
          ConfigQueryChainResponse response = configQueryChainService.handle(request);
          if (McpConfigUtils.isConfigNotFound(response.getStatus())) {
              throw new NacosApiException(NacosApiException.NOT_FOUND, ErrorCode.MCP_SEVER_VERSION_NOT_FOUND,
                      String.format("mcp server `%s` for version `%s` not found", mcpServerId, version));
          }
          
          McpServerStorageInfo serverSpecification = JacksonUtils.toObj(response.getContent(),
                  McpServerStorageInfo.class);
          
          McpServerDetailInfo result = new McpServerDetailInfo();
          result.setId(mcpServerId);
          result.setNamespaceId(namespaceId);
          BeanUtils.copyProperties(serverSpecification, result);
          
          List<ServerVersionDetail> versionDetails = mcpServerVersionInfo.getVersionDetails();
          String latestVersion = mcpServerVersionInfo.getLatestPublishedVersion();
          for (ServerVersionDetail versionDetail : versionDetails) {
              versionDetail.setIs_latest(versionDetail.getVersion().equals(latestVersion));
          }
          result.setAllVersions(mcpServerVersionInfo.getVersionDetails());
          
          ServerVersionDetail versionDetail = result.getVersionDetail();
          versionDetail.setIs_latest(versionDetail.getVersion().equals(latestVersion));
          result.setVersion(versionDetail.getVersion());
          
          if (Objects.nonNull(serverSpecification.getToolsDescriptionRef())) {
              McpToolSpecification toolSpec = toolOperationService.getMcpTool(namespaceId,
                      serverSpecification.getToolsDescriptionRef());
              result.setToolSpec(toolSpec);
          }
          
          if (!AiConstants.Mcp.MCP_PROTOCOL_STDIO.equalsIgnoreCase(serverSpecification.getProtocol())) {
              injectEndpoint(result);
          }
          return result;
      }
      
      private McpServerVersionInfo getMcpServerVersionInfo(String namespaceId, String mcpServerId)
              throws NacosApiException {
          ConfigQueryChainRequest request = buildQueryMcpServerVersionInfoRequest(namespaceId, mcpServerId);
          ConfigQueryChainResponse response = configQueryChainService.handle(request);
          if (McpConfigUtils.isConfigNotFound(response.getStatus())) {
              throw new NacosApiException(NacosApiException.NOT_FOUND, ErrorCode.MCP_SERVER_NOT_FOUND,
                      String.format("Mcp server [ID: %s] not found in namespace [%s]. Response: %s", mcpServerId,
                              namespaceId, response.getMessage()));
          }
          
          return JacksonUtils.toObj(response.getContent(), McpServerVersionInfo.class);
      }
      
      private void injectEndpoint(McpServerDetailInfo detailInfo) throws NacosException {
          injectBackendEndpointRef(detailInfo);
          injectFrontendEndpointRef(detailInfo);
      }
      
      private void injectBackendEndpointRef(McpServerDetailInfo detailInfo) throws NacosException {
          List<Instance> instances;
          
          instances = endpointOperationService.getMcpServerEndpointInstances(
                  detailInfo.getRemoteServerConfig().getServiceRef());
          
          String protocol = null;
          McpServiceRef serviceRef = detailInfo.getRemoteServerConfig().getServiceRef();
          if (Objects.nonNull(serviceRef)) {
              protocol = serviceRef.getTransportProtocol();
          }
          List<McpEndpointInfo> backendEndpoints = transferToMcpEndpointInfo(instances,
                  detailInfo.getRemoteServerConfig().getExportPath(), 
                  protocol);
          detailInfo.setBackendEndpoints(backendEndpoints);
      }
  
      private List<McpEndpointInfo> transferToMcpEndpointInfoWithHeaders(List<Instance> instances, String exportPath,
              String protocol, List<KeyValueInput> headers) {
          List<McpEndpointInfo> endpointInfos = new LinkedList<>();
          for (Instance each : instances) {
              McpEndpointInfo mcpEndpointInfo = new McpEndpointInfo();
              mcpEndpointInfo.setAddress(each.getIp());
              mcpEndpointInfo.setPort(each.getPort());
              mcpEndpointInfo.setProtocol(protocol);
              mcpEndpointInfo.setHeaders(headers);
              mcpEndpointInfo.setPath(exportPath);
              endpointInfos.add(mcpEndpointInfo);
          }
          return endpointInfos;
      }
      
      private List<McpEndpointInfo> transferToMcpEndpointInfo(List<Instance> instances, String exportPath,
              String protocol) {
          return transferToMcpEndpointInfoWithHeaders(instances, exportPath, protocol, null);
      }
      
      private void injectFrontendEndpointRef(McpServerDetailInfo detailInfo) throws NacosException {
          List<FrontEndpointConfig> frontEndpointConfigs = detailInfo.getRemoteServerConfig()
                  .getFrontEndpointConfigList();
          if (CollectionUtils.isEmpty(frontEndpointConfigs)) {
              detailInfo.setFrontendEndpoints(Collections.emptyList());
              return;
          }
          List<McpEndpointInfo> frontendEndpoints = new LinkedList<>();
          for (FrontEndpointConfig each : frontEndpointConfigs) {
              if (AiConstants.Mcp.MCP_ENDPOINT_TYPE_REF.equals(each.getEndpointType())) {
                  McpServiceRef mcpServiceRef = McpRequestUtil.transferToMcpServiceRef(each.getEndpointData());
                  List<Instance> instances = endpointOperationService.getMcpServerEndpointInstances(mcpServiceRef);
                  List<McpEndpointInfo> endpointInfos = transferToMcpEndpointInfoWithHeaders(instances, each.getPath(),
                          each.getProtocol(), each.getHeaders());
                  frontendEndpoints.addAll(endpointInfos);
              } else if (AiConstants.Mcp.MCP_ENDPOINT_TYPE_DIRECT.equals(each.getEndpointType())) {
                  McpEndpointInfo endpointInfo = new McpEndpointInfo();
                  endpointInfo.setPath(each.getPath());
                  endpointInfo.setProtocol(each.getProtocol());
                  endpointInfo.setHeaders(each.getHeaders());
                  String address = each.getEndpointData().toString();
                  if (InternetAddressUtil.containsPort(address)) {
                      String[] info = InternetAddressUtil.splitIpPortStr(address);
                      endpointInfo.setAddress(info[0]);
                      endpointInfo.setPort(Integer.parseInt(info[1]));
                  } else {
                      endpointInfo.setAddress(address);
                      endpointInfo.setPort(Constants.PROTOCOL_TYPE_HTTP.equals(each.getProtocol()) ? 80 : 443);
                  }
                  frontendEndpoints.add(endpointInfo);
              } else {
                  frontendEndpoints.addAll(detailInfo.getBackendEndpoints());
              }
              detailInfo.setFrontendEndpoints(frontendEndpoints);
          }
      }
      
      /**
       * Create new mcp server.
       *
       * @param namespaceId           namespace id of mcp server
       * @param serverSpecification   mcp server specification, see {@link McpServerBasicInfo}
       * @param toolSpecification     mcp server included tools, see {@link McpToolSpecification}, optional
       * @param endpointSpecification mcp server endpoint specification, see {@link McpEndpointSpec}, optional
       * @throws NacosException any exception during handling
       */
      public String createMcpServer(String namespaceId, McpServerBasicInfo serverSpecification,
              McpToolSpecification toolSpecification, McpEndpointSpec endpointSpecification) throws NacosException {
          
          String existId = resolveMcpServerId(namespaceId, serverSpecification.getName(), StringUtils.EMPTY);
          if (StringUtils.isNotEmpty(existId)) {
              throw new NacosApiException(NacosApiException.CONFLICT, ErrorCode.RESOURCE_CONFLICT,
                      String.format("mcp server `%s` has existed, please update it rather than create.",
                              serverSpecification.getName()));
          }
          
          ServerVersionDetail versionDetail = serverSpecification.getVersionDetail();
          if (null == versionDetail && StringUtils.isNotBlank(serverSpecification.getVersion())) {
              versionDetail = new ServerVersionDetail();
              versionDetail.setVersion(serverSpecification.getVersion());
              serverSpecification.setVersionDetail(versionDetail);
          }
          if (Objects.isNull(versionDetail) || StringUtils.isEmpty(versionDetail.getVersion())) {
              throw new NacosApiException(NacosApiException.INVALID_PARAM, ErrorCode.PARAMETER_VALIDATE_ERROR,
                      "Version must be specified in parameter `serverSpecification`");
          }
          String id;
          String customMcpId = serverSpecification.getId();
          
          if (StringUtils.isEmpty(customMcpId)) {
              id = UUID.randomUUID().toString();
          } else {
              if (!StringUtils.isUuidString(customMcpId)) {
                  throw new NacosApiException(NacosApiException.INVALID_PARAM, ErrorCode.PARAMETER_VALIDATE_ERROR,
                          "parameter `serverSpecification.id` is not match uuid pattern,  must obey uuid pattern");
              }
              if (mcpServerIndex.getMcpServerById(serverSpecification.getId()) != null) {
                  throw new NacosApiException(NacosApiException.INVALID_PARAM, ErrorCode.PARAMETER_VALIDATE_ERROR,
                          "parameter `serverSpecification.id` conflict with exist mcp server id");
              }
              
              id = customMcpId;
          }
          
          serverSpecification.setId(id);
          ZonedDateTime currentTime = ZonedDateTime.now(ZoneOffset.UTC);
          DateTimeFormatter formatter = DateTimeFormatter.ofPattern(Constants.RELEASE_DATE_FORMAT);
          String formattedCurrentTime = currentTime.format(formatter);
          versionDetail.setRelease_date(formattedCurrentTime);
          
          McpServerStorageInfo newSpecification = new McpServerStorageInfo();
          BeanUtils.copyProperties(serverSpecification, newSpecification);
          injectToolAndEndpoint(namespaceId, serverSpecification.getId(), newSpecification, toolSpecification,
                  endpointSpecification);
          
          McpServerVersionInfo versionInfo = buildServerVersionInfo(newSpecification, id, versionDetail);
          
          ConfigRequestInfo configRequestInfo = new ConfigRequestInfo();
          configRequestInfo.setUpdateForExist(Boolean.FALSE);
          
          ConfigFormV3 mcpServerVersionForm = buildMcpServerVersionForm(namespaceId, versionInfo);
          configOperationService.publishConfig(mcpServerVersionForm, configRequestInfo, null);
          
          ConfigForm configForm = buildMcpConfigForm(namespaceId, id, versionDetail.getVersion(), newSpecification);
          long startOperationTime = System.currentTimeMillis();
          configOperationService.publishConfig(configForm, configRequestInfo, null);
          syncEffectService.toSync(configForm, startOperationTime);
          
          // Delete the relevant cache after a successful database operation
          invalidateCacheAfterDbOperation(namespaceId, serverSpecification.getName(), id);
          
          return id;
      }
      
      private static McpServerVersionInfo buildServerVersionInfo(McpServerBasicInfo serverSpecification, String id,
              ServerVersionDetail versionDetail) {
          McpServerVersionInfo versionInfo = new McpServerVersionInfo();
          versionInfo.setName(serverSpecification.getName());
          versionInfo.setId(id);
          versionInfo.setDescription(serverSpecification.getDescription());
          versionInfo.setRepository(serverSpecification.getRepository());
          versionInfo.setFrontProtocol(serverSpecification.getFrontProtocol());
          versionInfo.setProtocol(serverSpecification.getProtocol());
          versionInfo.setCapabilities(serverSpecification.getCapabilities());
          versionInfo.setLatestPublishedVersion(serverSpecification.getVersionDetail().getVersion());
          versionInfo.setVersions(Collections.singletonList(versionDetail));
          return versionInfo;
      }
      
      /**
       * Update existed mcp server.
       *
       * <p>
       * `namespaceId` and `mcpServerId` can't be changed.
       * </p>
       *
       * @param namespaceId           namespace id of mcp server, used to mark which mcp server to update
       * @param serverSpecification   mcp server specification, see {@link McpServerBasicInfo}
       * @param toolSpecification     mcp server included tools, see {@link McpToolSpecification}, optional
       * @param endpointSpecification mcp server endpoint specification, see {@link McpEndpointSpec}, optional
       * @throws NacosException any exception during handling
       */
      public void updateMcpServer(String namespaceId, boolean isPublish, McpServerBasicInfo serverSpecification,
              McpToolSpecification toolSpecification, McpEndpointSpec endpointSpecification) throws NacosException {
          
          String mcpServerId = serverSpecification.getId();
          mcpServerId = resolveMcpServerId(namespaceId, serverSpecification.getName(), mcpServerId);
          if (StringUtils.isEmpty(serverSpecification.getId())) {
              serverSpecification.setId(mcpServerId);
          }
          
          ServerVersionDetail versionDetail = serverSpecification.getVersionDetail();
          if (null == versionDetail && StringUtils.isNotBlank(serverSpecification.getVersion())) {
              versionDetail = new ServerVersionDetail();
              versionDetail.setVersion(serverSpecification.getVersion());
              serverSpecification.setVersionDetail(versionDetail);
          }
          if (Objects.isNull(versionDetail) || StringUtils.isEmpty(versionDetail.getVersion())) {
              throw new NacosApiException(NacosApiException.INVALID_PARAM, ErrorCode.PARAMETER_VALIDATE_ERROR,
                      "Version must be specified in parameter `serverSpecification`");
          }
          final McpServerVersionInfo mcpServerVersionInfo = getMcpServerVersionInfo(namespaceId, mcpServerId);
          
          String updateVersion = versionDetail.getVersion();
          McpServerStorageInfo newSpecification = new McpServerStorageInfo();
          BeanUtils.copyProperties(serverSpecification, newSpecification);
          injectToolAndEndpoint(namespaceId, mcpServerId, newSpecification, toolSpecification, endpointSpecification);
          
          ConfigForm configForm = buildMcpConfigForm(namespaceId, mcpServerId, updateVersion, newSpecification);
          configOperationService.publishConfig(configForm, new ConfigRequestInfo(), null);
          
          List<ServerVersionDetail> versionDetails = mcpServerVersionInfo.getVersionDetails();
          Set<String> versionSet = versionDetails.stream().map(ServerVersionDetail::getVersion)
                  .collect(Collectors.toSet());
          if (!versionSet.contains(updateVersion)) {
              ServerVersionDetail version = new ServerVersionDetail();
              version.setVersion(updateVersion);
              versionDetails.add(version);
              mcpServerVersionInfo.setVersions(versionDetails);
          }
          
          if (isPublish) {
              mcpServerVersionInfo.setName(newSpecification.getName());
              mcpServerVersionInfo.setDescription(newSpecification.getDescription());
              mcpServerVersionInfo.setRepository(newSpecification.getRepository());
              mcpServerVersionInfo.setProtocol(newSpecification.getProtocol());
              mcpServerVersionInfo.setFrontProtocol(newSpecification.getFrontProtocol());
              mcpServerVersionInfo.setCapabilities(newSpecification.getCapabilities());
              mcpServerVersionInfo.setLatestPublishedVersion(updateVersion);
              
              for (ServerVersionDetail detail : versionDetails) {
                  if (detail.getVersion().equals(updateVersion)) {
                      ZonedDateTime currentTime = ZonedDateTime.now(ZoneOffset.UTC);
                      DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
                      String formattedCurrentTime = currentTime.format(formatter);
                      detail.setRelease_date(formattedCurrentTime);
                      detail.setIs_latest(true);
                      break;
                  } else {
                      detail.setIs_latest(false);
                  }
              }
              mcpServerVersionInfo.setVersions(versionDetails);
              mcpServerVersionInfo.setEnabled(newSpecification.isEnabled());
          }
          
          ConfigFormV3 mcpServerVersionForm = buildMcpServerVersionForm(namespaceId, mcpServerVersionInfo);
          long startOperationTime = System.currentTimeMillis();
          configOperationService.publishConfig(mcpServerVersionForm, new ConfigRequestInfo(), null);
          syncEffectService.toSync(mcpServerVersionForm, startOperationTime);
          
          // Delete the relevant cache after a successful database operation
          invalidateCacheAfterDbUpdateOperation(namespaceId, mcpServerVersionInfo.getName(),
                  serverSpecification.getName(), mcpServerId);
      }
      
      /**
       * Delete existed mcp server.
       *
       * @param namespaceId namespace id of mcp server
       * @param mcpServerId name of mcp server
       * @throws NacosException any exception during handling
       */
      public void deleteMcpServer(String namespaceId, String mcpName, String mcpServerId, String version)
              throws NacosException {
          mcpServerId = resolveMcpServerId(namespaceId, mcpName, mcpServerId);
          McpServerVersionInfo mcpServerVersionInfo = getMcpServerVersionInfo(namespaceId, mcpServerId);
          List<String> versionsNeedDelete = new ArrayList<>();
          if (StringUtils.isNotEmpty(version)) {
              versionsNeedDelete.add(version);
          } else {
              versionsNeedDelete = mcpServerVersionInfo.getVersionDetails().stream().map(ServerVersionDetail::getVersion)
                      .collect(Collectors.toList());
          }
          
          for (String versionNeedDelete : versionsNeedDelete) {
              toolOperationService.deleteMcpTool(namespaceId, mcpServerId, versionNeedDelete);
              endpointOperationService.deleteMcpServerEndpointService(namespaceId, mcpServerVersionInfo.getName());
              String serverSpecDataId = McpConfigUtils.formatServerSpecInfoDataId(mcpServerId, versionNeedDelete);
              configOperationService.deleteConfig(serverSpecDataId, Constants.MCP_SERVER_GROUP, namespaceId, null, null,
                      "nacos", null);
              String serverVersionDataId = McpConfigUtils.formatServerVersionInfoDataId(mcpServerId);
              configOperationService.deleteConfig(serverVersionDataId, Constants.MCP_SERVER_VERSIONS_GROUP, namespaceId,
                      null, null, "nacos", null);
          }
          
          // Delete the relevant cache after a successful database operation
          invalidateCacheAfterDbOperation(namespaceId, mcpName, mcpServerId);
      }
      
      private void injectToolAndEndpoint(String namespaceId, String mcpServerId, McpServerStorageInfo serverSpecification,
              McpToolSpecification toolSpecification, McpEndpointSpec endpointSpecification) throws NacosException {
          serverSpecification.setCapabilities(new LinkedList<>());
          boolean hasToolSpec = toolSpecification != null;
          boolean hasTools = hasToolSpec && toolSpecification.getTools() != null;
          boolean hasSecuritySchemes = hasToolSpec && toolSpecification.getSecuritySchemes() != null;
          boolean hasEncryptedData = hasToolSpec && toolSpecification.getEncryptData() != null;
          boolean shouldCreateToolConfig = hasToolSpec && (hasTools || hasSecuritySchemes || hasEncryptedData);
          if (shouldCreateToolConfig) {
              toolOperationService.refreshMcpTool(namespaceId, serverSpecification, toolSpecification);
              serverSpecification.getCapabilities().add(McpCapability.TOOL);
              String version = serverSpecification.getVersionDetail().getVersion();
              String toolSpecDataId = McpConfigUtils.formatServerToolSpecDataId(mcpServerId, version);
              serverSpecification.setToolsDescriptionRef(toolSpecDataId);
          }
          if (null != endpointSpecification) {
              Service service = endpointOperationService.createMcpServerEndpointServiceIfNecessary(namespaceId,
                      serverSpecification.getName(), serverSpecification.getVersionDetail().getVersion(), endpointSpecification);
              String transportProtocol = endpointSpecification.getData().get(Constants.MCP_BACKEND_ISTANCE_PROTOCOL_KEY);
              McpServiceRef serviceRef = new McpServiceRef();
              serviceRef.setNamespaceId(service.getNamespace());
              serviceRef.setGroupName(service.getGroup());
              serviceRef.setServiceName(service.getName());
              serviceRef.setTransportProtocol(transportProtocol);
              serverSpecification.getRemoteServerConfig().setServiceRef(serviceRef);
          }
      }
      
      private ConfigFormV3 buildMcpServerVersionForm(String namespaceId, McpServerVersionInfo mcpServerVersionInfo) {
          ConfigFormV3 configFormV3 = new ConfigFormV3();
          configFormV3.setGroupName(Constants.MCP_SERVER_VERSIONS_GROUP);
          configFormV3.setGroup(Constants.MCP_SERVER_VERSIONS_GROUP);
          configFormV3.setNamespaceId(namespaceId);
          configFormV3.setDataId(McpConfigUtils.formatServerVersionInfoDataId(mcpServerVersionInfo.getId()));
          configFormV3.setContent(JacksonUtils.toJson(mcpServerVersionInfo));
          configFormV3.setType(ConfigType.JSON.getType());
          configFormV3.setAppName(mcpServerVersionInfo.getName());
          configFormV3.setSrcUser("nacos");
          String configTags = buildMcpServerVersionConfigTags(mcpServerVersionInfo.getName());
          configFormV3.setConfigTags(configTags);
          return configFormV3;
      }
      
      private ConfigFormV3 buildMcpConfigForm(String namespaceId, String mcpServerId, String version,
              McpServerBasicInfo serverSpecification) {
          ConfigFormV3 configFormV3 = new ConfigFormV3();
          configFormV3.setGroupName(Constants.MCP_SERVER_GROUP);
          configFormV3.setGroup(Constants.MCP_SERVER_GROUP);
          configFormV3.setNamespaceId(namespaceId);
          configFormV3.setDataId(McpConfigUtils.formatServerSpecInfoDataId(mcpServerId, version));
          configFormV3.setContent(JacksonUtils.toJson(serverSpecification));
          configFormV3.setType(ConfigType.JSON.getType());
          configFormV3.setAppName(serverSpecification.getName());
          configFormV3.setSrcUser("nacos");
          configFormV3.setConfigTags(MCP_SERVER_CONFIG_MARK);
          return configFormV3;
      }
      
      private ConfigQueryChainRequest buildQueryMcpServerRequest(String namespaceId, String mcpServerId, String version) {
          ConfigQueryChainRequest request = new ConfigQueryChainRequest();
          request.setDataId(McpConfigUtils.formatServerSpecInfoDataId(mcpServerId, version));
          request.setGroup(Constants.MCP_SERVER_GROUP);
          request.setTenant(namespaceId);
          return request;
      }
      
      private ConfigQueryChainRequest buildQueryMcpServerVersionInfoRequest(String namespaceId, String mcpServerId) {
          ConfigQueryChainRequest request = new ConfigQueryChainRequest();
          request.setDataId(McpConfigUtils.formatServerVersionInfoDataId(mcpServerId));
          request.setGroup(Constants.MCP_SERVER_VERSIONS_GROUP);
          request.setTenant(namespaceId);
          return request;
      }
      
      private McpServerVersionInfo transferToMcpServerVersionInfo(String content) {
          McpServerVersionInfo versionInfo = JacksonUtils.toObj(content, McpServerVersionInfo.class);
          String latestPublishedVersion = versionInfo.getLatestPublishedVersion();
          for (ServerVersionDetail versionDetail : versionInfo.getVersionDetails()) {
              if (versionDetail.getVersion().equals(latestPublishedVersion)) {
                  versionDetail.setIs_latest(true);
                  versionInfo.setVersionDetail(versionDetail);
                  break;
              } else {
                  versionDetail.setIs_latest(false);
              }
          }
          versionInfo.setVersion(latestPublishedVersion);
          return versionInfo;
      }
      
      private String resolveMcpServerId(String namespaceId, String serverName, String serverId) {
          if (StringUtils.isNotEmpty(serverId)) {
              return serverId;
          }
          
          McpServerIndexData indexData = mcpServerIndex.getMcpServerByName(namespaceId, serverName);
          if (Objects.nonNull(indexData)) {
              return indexData.getId();
          }
          
          return null;
      }
      
      /**
       * Invalidate cache after update mcp server operation.
       *
       * @param namespaceId namespace id of mcp server
       * @param oldMcpName  old mcp server name
       * @param newMcpName  new mcp server name
       * @param mcpServerId mcp server id
       */
      private void invalidateCacheAfterDbUpdateOperation(String namespaceId, String oldMcpName, String newMcpName,
              String mcpServerId) {
          try {
              if (StringUtils.isNotEmpty(oldMcpName) && !oldMcpName.equals(newMcpName)) {
                  mcpServerIndex.removeMcpServerByName(namespaceId, oldMcpName);
              }
              if (StringUtils.isNotEmpty(newMcpName)) {
                  mcpServerIndex.removeMcpServerByName(namespaceId, newMcpName);
              }
              if (StringUtils.isNotEmpty(mcpServerId)) {
                  mcpServerIndex.removeMcpServerById(mcpServerId);
              }
              LOGGER.debug("Cache invalidated after updateMcpServer: namespaceId={}, oldName={}, newName={}, id={}",
                      namespaceId, oldMcpName, newMcpName, mcpServerId);
          } catch (Exception e) {
              LOGGER.warn(
                      "Failed to invalidate cache after updateMcpServer: namespaceId={}, oldName={}, newName={}, id={}, error={}",
                      namespaceId, oldMcpName, newMcpName, mcpServerId, e.getMessage());
          }
      }
      
      /**
       * Unified cache invalidation method.
       *
       * @param namespaceId namespace ID
       * @param mcpName     MCP server name
       * @param mcpServerId MCP server ID
       */
      private void invalidateCacheAfterDbOperation(String namespaceId, String mcpName, String mcpServerId) {
          try {
              if (StringUtils.isNotEmpty(mcpName)) {
                  mcpServerIndex.removeMcpServerByName(namespaceId, mcpName);
              }
              if (StringUtils.isNotEmpty(mcpServerId)) {
                  mcpServerIndex.removeMcpServerById(mcpServerId);
              }
              LOGGER.debug("Cache invalidated after DB operation: namespaceId={}, mcpName={}, mcpId={}", namespaceId,
                      mcpName, mcpServerId);
          } catch (Exception e) {
              LOGGER.warn("Failed to invalidate cache after DB operation: namespaceId={}, mcpName={}, mcpId={}, error={}",
                      namespaceId, mcpName, mcpServerId, e.getMessage());
          }
      }
  }
  