ysharma HF Staff commited on
Commit
2b232e8
Β·
verified Β·
1 Parent(s): cddef29

Create server_manager.py

Browse files
Files changed (1) hide show
  1. 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()