UNPKG

@xiaohui-wang/mcpadvisor

Version:

MCP Advisor & Installation - Find the right MCP server for your needs

244 lines (243 loc) 9.82 kB
import axios from 'axios'; import logger from '../../../utils/logger.js'; export class NacosHttpClient { nacosAddr; userName; passwd; client; constructor(nacosAddr, userName, passwd) { if (!nacosAddr) { throw new Error('nacosAddr cannot be an empty string'); } if (!userName) { throw new Error('userName cannot be an empty string'); } if (!passwd) { throw new Error('passwd cannot be an empty string'); } this.nacosAddr = nacosAddr; this.userName = userName; this.passwd = passwd; this.client = axios.create({ baseURL: `http://${this.nacosAddr}`, headers: { 'Content-Type': 'application/json', 'charset': 'utf-8', 'userName': this.userName, 'password': this.passwd } }); } /** * Make a GET request to the Nacos server * @param url The URL to make the request to * @param config Optional Axios request config * @returns The response data * @throws {Error} If the request fails or returns an error status */ async get(url, config) { try { const response = await this.client.get(url, config); return response; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`GET request to ${url} failed: ${errorMessage}`); throw new Error(`Failed to fetch from Nacos: ${errorMessage}`); } } /** * Get service details from Nacos server * @param serviceName The name of the service to fetch details for * @param groupName The group name of the service (default: 'DEFAULT_GROUP') * @returns Service details including metadata * @throws {Error} If the service is not found or there's an error fetching details */ async getServiceDetail(serviceName, groupName = 'DEFAULT_GROUP') { try { // First try to get the service using the MCP API if available try { const mcpServer = await this.getMcpServerByName(serviceName); if (mcpServer) { return { name: serviceName, groupName, metadata: { ...mcpServer.agentConfig, description: mcpServer.description, lastUpdated: new Date().toISOString() } }; } } catch (error) { logger.debug(`Failed to fetch MCP server details for ${serviceName}: ${error}`); // Continue with regular service API if MCP API fails } // Fall back to standard Nacos service API const response = await this.client.get(`/nacos/v1/ns/service`, { params: { serviceName, groupName, namespaceId: 'public', clusterName: 'DEFAULT', healthyOnly: false } }); if (!response.data) { throw new Error(`Service ${serviceName} not found in group ${groupName}`); } return { name: serviceName, groupName, metadata: { ...response.data, lastUpdated: new Date().toISOString() } }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`Failed to get service details for ${serviceName}: ${errorMessage}`); throw new Error(`Failed to get service details: ${errorMessage}`); } } async isReady() { try { const response = await this.client.get('/nacos/v3/admin/ai/mcp/list'); return response.status === 200; } catch (error) { logger.warn(`Nacos health check failed: ${error}`); return false; } } async getMcpServerByName(name) { const url = `/nacos/v3/admin/ai/mcp?mcpName=${name}`; const mcpServer = { name, description: '', agentConfig: {}, }; try { const response = await this.client.get(url); if (response.status === 200) { const data = response.data.data; const server = { name: data.name, description: data.description || '', agentConfig: {}, mcpConfigDetail: data, getAgentConfig: () => ({}), toDict: () => ({ ...data, description: data.description || '' }) }; // Add getName and getDescription methods if not present if (!server.getName) { server.getName = () => data.name; } if (!server.getDescription) { server.getDescription = () => data.description || ''; } if (data.protocol !== 'stdio' && data.backendEndpoints?.length) { const endpoint = data.backendEndpoints[0]; const httpSchema = endpoint.port === 443 ? 'https' : 'http'; let url = `${httpSchema}://${endpoint.address}:${endpoint.port}`; if (data.remoteServerConfig?.exportPath) { const exportPath = data.remoteServerConfig.exportPath.startsWith('/') ? data.remoteServerConfig.exportPath : `/${data.remoteServerConfig.exportPath}`; url += exportPath; } server.agentConfig = { ...server.agentConfig, mcpServers: { [server.name]: { name: server.name, description: server.description, url } } }; } return server; } } catch (error) { logger.warn(`Failed to get mcp server ${name}: ${error}`); } return mcpServer; } async getMcpServers() { const mcpServers = []; try { const pageSize = 100; const pageNo = 1; const url = `/nacos/v3/admin/ai/mcp/list?pageNo=${pageNo}&pageSize=${pageSize}`; const response = await this.client.get(url); if (response.status !== 200) { logger.warn(`Failed to get mcp server list, status: ${response.status}`); return []; } for (const mcpServerDict of response.data.data.pageItems) { if (mcpServerDict.enabled) { const mcpServer = await this.getMcpServerByName(mcpServerDict.name); if (mcpServer.description) { mcpServers.push(mcpServer); } } } } catch (error) { logger.error('Error getting mcp servers:', error); throw new Error(`Failed to get mcp servers: ${error}`); } return mcpServers; } async updateMcpTools(mcpName, tools) { try { const url = `/nacos/v3/admin/ai/mcp?mcpName=${mcpName}`; const response = await this.client.get(url); if (response.status === 200) { const data = response.data.data; const toolList = tools.map(tool => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema })); const endpointSpecification = {}; if (data.protocol !== 'stdio' && data.remoteServerConfig?.serviceRef) { endpointSpecification.data = data.remoteServerConfig.serviceRef; endpointSpecification.type = 'REF'; } const toolSpecification = { ...(data.toolSpec || {}), tools: toolList }; const serverSpecification = { ...data }; delete serverSpecification.toolSpec; delete serverSpecification.backendEndpoints; const params = new URLSearchParams(); params.append('mcpName', mcpName); params.append('serverSpecification', JSON.stringify(serverSpecification)); params.append('endpointSpecification', JSON.stringify(endpointSpecification)); params.append('toolSpecification', JSON.stringify(toolSpecification)); logger.info(`Updating mcp tools for ${mcpName}`); const updateResponse = await this.client.put('/nacos/v3/admin/ai/mcp', params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', // Other headers like userName and password are already configured in the client } }); return updateResponse.status === 200; } return false; } catch (error) { logger.error('Error updating mcp tools:', error); return false; } } }