"""
Server management utilities for Universal MCP Client
Version 2.0 - Enhanced with server enabling/disabling support
"""
import asyncio
import re
import logging
import traceback
from typing import Tuple
from config import MCPServerConfig
from mcp_client import UniversalMCPClient
logger = logging.getLogger(__name__)
class ServerManager:
"""Manages MCP server connections and status with enhanced server management"""
def __init__(self, mcp_client: UniversalMCPClient):
self.mcp_client = mcp_client
def convert_hf_space_to_url(self, space_name: str) -> str:
"""
Convert HuggingFace space name to proper URL format.
HuggingFace URL rules:
- Replace "/" with "-"
- Convert to lowercase
- Replace dots and other special chars with "-"
- Remove consecutive hyphens
"""
if "/" not in space_name:
raise ValueError("Space name should be in format: username/space-name")
# Replace "/" with "-"
url_name = space_name.replace("/", "-")
# Convert to lowercase
url_name = url_name.lower()
# Replace dots and other special characters with hyphens
url_name = re.sub(r'[^a-z0-9\-]', '-', url_name)
# Remove consecutive hyphens
url_name = re.sub(r'-+', '-', url_name)
# Remove leading/trailing hyphens
url_name = url_name.strip('-')
return f"https://{url_name}.hf.space"
def add_custom_server(self, name: str, space_name: str) -> Tuple[str, str]:
"""Add a custom MCP server from HuggingFace space name"""
logger.info(f"â Adding MCP server: {name} from space: {space_name}")
if not name or not space_name:
return "â Please provide both server name and space name", ""
space_name = space_name.strip()
try:
# Use the improved URL conversion
mcp_url = self.convert_hf_space_to_url(space_name)
logger.info(f"đ Converted {space_name} â {mcp_url}")
except ValueError as e:
return f"â {str(e)}", ""
config = MCPServerConfig(
name=name.strip(),
url=mcp_url,
description=f"MCP server from HuggingFace space: {space_name}",
space_id=space_name
)
try:
# Run async function
def run_async():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
return loop.run_until_complete(self.mcp_client.add_server_async(config))
finally:
loop.close()
success, message = run_async()
logger.info(f"Server addition result: {success} - {message}")
if success:
# Format success message for accordion display
tools_info = ""
if 'Found' in message and 'tools:' in message:
tools_section = message.split('Found')[1]
tools_info = f"**Available Tools:**\n{tools_section}"
details_html = f"""
Space: {space_name} Base URL: {mcp_url} Status: Connected successfully! Enabled: Yes (new servers are enabled by default) {message}â
{name} - Connection Details
')}
â {name} - Connection Failed
No MCP servers configured yet.
" accordion_html = "" for name, config in self.mcp_client.servers.items(): logger.info(f"đ Processing server: {name}") base_url = config.url.replace("/gradio_api/mcp/sse", "") # Determine health status (assume healthy if it's in the servers dict) health = "đĸ Healthy" accordion_html += f"""Title: {name}
Status: Connected (MCP Protocol)
Health: {health}
Base URL: {base_url}
Description: {config.description}
{f'Space ID: {config.space_id}
' if config.space_id else ''}â {error_msg}
" def remove_server(self, server_name: str) -> Tuple[str, str]: """Remove a specific server""" success = self.mcp_client.remove_server(server_name) if success: return f"â Removed server: {server_name}", "" else: return f"â Server not found: {server_name}", "" def remove_all_servers(self) -> Tuple[str, str]: """Remove all servers""" count = self.mcp_client.remove_all_servers() if count > 0: return f"â Removed all {count} MCP servers", "" else: return "âšī¸ No servers to remove", "" def enable_server(self, server_name: str, enabled: bool = True) -> Tuple[str, str]: """Enable or disable a server""" if server_name in self.mcp_client.servers: self.mcp_client.enable_server(server_name, enabled) status = "enabled" if enabled else "disabled" return f"â Server {server_name} {status}", "" else: return f"â Server not found: {server_name}", "" def get_server_list_with_status(self) -> list: """Get server list with enable/disable status for UI components""" return self.mcp_client.get_server_list_with_status()