Spaces:
Running
Running
Create server_manager.py
Browse files- server_manager.py +200 -0
server_manager.py
ADDED
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Server management utilities for Universal MCP Client
|
3 |
+
Version 2.0 - Enhanced with server enabling/disabling support
|
4 |
+
"""
|
5 |
+
import asyncio
|
6 |
+
import re
|
7 |
+
import logging
|
8 |
+
import traceback
|
9 |
+
from typing import Tuple
|
10 |
+
|
11 |
+
from config import MCPServerConfig
|
12 |
+
from mcp_client import UniversalMCPClient
|
13 |
+
|
14 |
+
logger = logging.getLogger(__name__)
|
15 |
+
|
16 |
+
class ServerManager:
|
17 |
+
"""Manages MCP server connections and status with enhanced server management"""
|
18 |
+
|
19 |
+
def __init__(self, mcp_client: UniversalMCPClient):
|
20 |
+
self.mcp_client = mcp_client
|
21 |
+
|
22 |
+
def convert_hf_space_to_url(self, space_name: str) -> str:
|
23 |
+
"""
|
24 |
+
Convert HuggingFace space name to proper URL format.
|
25 |
+
|
26 |
+
HuggingFace URL rules:
|
27 |
+
- Replace "/" with "-"
|
28 |
+
- Convert to lowercase
|
29 |
+
- Replace dots and other special chars with "-"
|
30 |
+
- Remove consecutive hyphens
|
31 |
+
"""
|
32 |
+
if "/" not in space_name:
|
33 |
+
raise ValueError("Space name should be in format: username/space-name")
|
34 |
+
|
35 |
+
# Replace "/" with "-"
|
36 |
+
url_name = space_name.replace("/", "-")
|
37 |
+
|
38 |
+
# Convert to lowercase
|
39 |
+
url_name = url_name.lower()
|
40 |
+
|
41 |
+
# Replace dots and other special characters with hyphens
|
42 |
+
url_name = re.sub(r'[^a-z0-9\-]', '-', url_name)
|
43 |
+
|
44 |
+
# Remove consecutive hyphens
|
45 |
+
url_name = re.sub(r'-+', '-', url_name)
|
46 |
+
|
47 |
+
# Remove leading/trailing hyphens
|
48 |
+
url_name = url_name.strip('-')
|
49 |
+
|
50 |
+
return f"https://{url_name}.hf.space"
|
51 |
+
|
52 |
+
def add_custom_server(self, name: str, space_name: str) -> Tuple[str, str]:
|
53 |
+
"""Add a custom MCP server from HuggingFace space name"""
|
54 |
+
logger.info(f"β Adding MCP server: {name} from space: {space_name}")
|
55 |
+
|
56 |
+
if not name or not space_name:
|
57 |
+
return "β Please provide both server name and space name", ""
|
58 |
+
|
59 |
+
space_name = space_name.strip()
|
60 |
+
|
61 |
+
try:
|
62 |
+
# Use the improved URL conversion
|
63 |
+
mcp_url = self.convert_hf_space_to_url(space_name)
|
64 |
+
logger.info(f"π Converted {space_name} β {mcp_url}")
|
65 |
+
|
66 |
+
except ValueError as e:
|
67 |
+
return f"β {str(e)}", ""
|
68 |
+
|
69 |
+
config = MCPServerConfig(
|
70 |
+
name=name.strip(),
|
71 |
+
url=mcp_url,
|
72 |
+
description=f"MCP server from HuggingFace space: {space_name}",
|
73 |
+
space_id=space_name
|
74 |
+
)
|
75 |
+
|
76 |
+
try:
|
77 |
+
# Run async function
|
78 |
+
def run_async():
|
79 |
+
loop = asyncio.new_event_loop()
|
80 |
+
asyncio.set_event_loop(loop)
|
81 |
+
try:
|
82 |
+
return loop.run_until_complete(self.mcp_client.add_server_async(config))
|
83 |
+
finally:
|
84 |
+
loop.close()
|
85 |
+
|
86 |
+
success, message = run_async()
|
87 |
+
logger.info(f"Server addition result: {success} - {message}")
|
88 |
+
|
89 |
+
if success:
|
90 |
+
# Format success message for accordion display
|
91 |
+
tools_info = ""
|
92 |
+
if 'Found' in message and 'tools:' in message:
|
93 |
+
tools_section = message.split('Found')[1]
|
94 |
+
tools_info = f"**Available Tools:**\n{tools_section}"
|
95 |
+
|
96 |
+
details_html = f"""
|
97 |
+
<details style="margin-top: 10px;">
|
98 |
+
<summary style="cursor: pointer; padding: 8px; background: #f0f0f0; border-radius: 4px;"><strong>β
{name} - Connection Details</strong></summary>
|
99 |
+
<div style="padding: 10px; border-left: 3px solid #28a745; margin-left: 10px; margin-top: 5px;">
|
100 |
+
<p><strong>Space:</strong> {space_name}</p>
|
101 |
+
<p><strong>Base URL:</strong> {mcp_url}</p>
|
102 |
+
<p><strong>Status:</strong> Connected successfully!</p>
|
103 |
+
<p><strong>Enabled:</strong> Yes (new servers are enabled by default)</p>
|
104 |
+
<div style="margin-top: 10px;">
|
105 |
+
{tools_info.replace('**', '<strong>').replace('**', '</strong>').replace(chr(10), '<br>')}
|
106 |
+
</div>
|
107 |
+
</div>
|
108 |
+
</details>
|
109 |
+
"""
|
110 |
+
return "β
Server added successfully!", details_html
|
111 |
+
else:
|
112 |
+
error_html = f"""
|
113 |
+
<details style="margin-top: 10px;">
|
114 |
+
<summary style="cursor: pointer; padding: 8px; background: #f8d7da; border-radius: 4px;"><strong>β {name} - Connection Failed</strong></summary>
|
115 |
+
<div style="padding: 10px; border-left: 3px solid #dc3545; margin-left: 10px; margin-top: 5px;">
|
116 |
+
<p>{message}</p>
|
117 |
+
</div>
|
118 |
+
</details>
|
119 |
+
"""
|
120 |
+
return f"β Failed to add server: {name}", error_html
|
121 |
+
|
122 |
+
except Exception as e:
|
123 |
+
error_msg = f"β Failed to add server: {str(e)}"
|
124 |
+
logger.error(error_msg)
|
125 |
+
logger.error(traceback.format_exc())
|
126 |
+
return error_msg, ""
|
127 |
+
|
128 |
+
def get_server_status(self) -> Tuple[str, str]:
|
129 |
+
"""Get status of all servers in accordion format"""
|
130 |
+
try:
|
131 |
+
logger.info(f"π Getting server status, found {len(self.mcp_client.servers)} servers")
|
132 |
+
|
133 |
+
# Get server status from mcp_client
|
134 |
+
server_count = len(self.mcp_client.servers)
|
135 |
+
server_status_text = f"**Total MCP Servers**: {server_count}"
|
136 |
+
|
137 |
+
if not self.mcp_client.servers:
|
138 |
+
logger.info("π No servers found")
|
139 |
+
return server_status_text, "<p><em>No MCP servers configured yet.</em></p>"
|
140 |
+
|
141 |
+
accordion_html = ""
|
142 |
+
|
143 |
+
for name, config in self.mcp_client.servers.items():
|
144 |
+
logger.info(f"π Processing server: {name}")
|
145 |
+
base_url = config.url.replace("/gradio_api/mcp/sse", "")
|
146 |
+
|
147 |
+
# Determine health status (assume healthy if it's in the servers dict)
|
148 |
+
health = "π’ Healthy"
|
149 |
+
|
150 |
+
accordion_html += f"""
|
151 |
+
<details style="margin-bottom: 10px;">
|
152 |
+
<summary style="cursor: pointer; padding: 8px; background: #e9ecef; border-radius: 4px;"><strong>π§ {name}</strong></summary>
|
153 |
+
<div style="padding: 10px; border-left: 3px solid #007bff; margin-left: 10px; margin-top: 5px;">
|
154 |
+
<p><strong>Title:</strong> {name}</p>
|
155 |
+
<p><strong>Status:</strong> Connected (MCP Protocol)</p>
|
156 |
+
<p><strong>Health:</strong> {health}</p>
|
157 |
+
<p><strong>Base URL:</strong> {base_url}</p>
|
158 |
+
<p><strong>Description:</strong> {config.description}</p>
|
159 |
+
{f'<p><strong>Space ID:</strong> {config.space_id}</p>' if config.space_id else ''}
|
160 |
+
</div>
|
161 |
+
</details>
|
162 |
+
"""
|
163 |
+
|
164 |
+
logger.info(f"π Generated accordion HTML for {server_count} servers")
|
165 |
+
return server_status_text, accordion_html
|
166 |
+
|
167 |
+
except Exception as e:
|
168 |
+
error_msg = f"Error getting server status: {str(e)}"
|
169 |
+
logger.error(f"β {error_msg}")
|
170 |
+
logger.error(traceback.format_exc())
|
171 |
+
return "**Total MCP Servers**: Error", f"<p style='color: red;'>β {error_msg}</p>"
|
172 |
+
|
173 |
+
def remove_server(self, server_name: str) -> Tuple[str, str]:
|
174 |
+
"""Remove a specific server"""
|
175 |
+
success = self.mcp_client.remove_server(server_name)
|
176 |
+
if success:
|
177 |
+
return f"β
Removed server: {server_name}", ""
|
178 |
+
else:
|
179 |
+
return f"β Server not found: {server_name}", ""
|
180 |
+
|
181 |
+
def remove_all_servers(self) -> Tuple[str, str]:
|
182 |
+
"""Remove all servers"""
|
183 |
+
count = self.mcp_client.remove_all_servers()
|
184 |
+
if count > 0:
|
185 |
+
return f"β
Removed all {count} MCP servers", ""
|
186 |
+
else:
|
187 |
+
return "βΉοΈ No servers to remove", ""
|
188 |
+
|
189 |
+
def enable_server(self, server_name: str, enabled: bool = True) -> Tuple[str, str]:
|
190 |
+
"""Enable or disable a server"""
|
191 |
+
if server_name in self.mcp_client.servers:
|
192 |
+
self.mcp_client.enable_server(server_name, enabled)
|
193 |
+
status = "enabled" if enabled else "disabled"
|
194 |
+
return f"β
Server {server_name} {status}", ""
|
195 |
+
else:
|
196 |
+
return f"β Server not found: {server_name}", ""
|
197 |
+
|
198 |
+
def get_server_list_with_status(self) -> list:
|
199 |
+
"""Get server list with enable/disable status for UI components"""
|
200 |
+
return self.mcp_client.get_server_list_with_status()
|