ysharma HF Staff commited on
Commit
6daa8d9
Β·
verified Β·
1 Parent(s): ada4b72

Create server_manager.py

Browse files
Files changed (1) hide show
  1. server_manager.py +159 -0
server_manager.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Server management utilities for Universal MCP Client
3
+ """
4
+ import asyncio
5
+ import re
6
+ import logging
7
+ import traceback
8
+ from typing import Tuple
9
+
10
+ from config import MCPServerConfig
11
+ from mcp_client import UniversalMCPClient
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ class ServerManager:
16
+ """Manages MCP server connections and status"""
17
+
18
+ def __init__(self, mcp_client: UniversalMCPClient):
19
+ self.mcp_client = mcp_client
20
+
21
+ def convert_hf_space_to_url(self, space_name: str) -> str:
22
+ """
23
+ Convert HuggingFace space name to proper URL format.
24
+
25
+ HuggingFace URL rules:
26
+ - Replace "/" with "-"
27
+ - Convert to lowercase
28
+ - Replace dots and other special chars with "-"
29
+ - Remove consecutive hyphens
30
+ """
31
+ if "/" not in space_name:
32
+ raise ValueError("Space name should be in format: username/space-name")
33
+
34
+ # Replace "/" with "-"
35
+ url_name = space_name.replace("/", "-")
36
+
37
+ # Convert to lowercase
38
+ url_name = url_name.lower()
39
+
40
+ # Replace dots and other special characters with hyphens
41
+ url_name = re.sub(r'[^a-z0-9\-]', '-', url_name)
42
+
43
+ # Remove consecutive hyphens
44
+ url_name = re.sub(r'-+', '-', url_name)
45
+
46
+ # Remove leading/trailing hyphens
47
+ url_name = url_name.strip('-')
48
+
49
+ return f"https://{url_name}.hf.space"
50
+
51
+ def add_custom_server(self, name: str, space_name: str) -> Tuple[str, str]:
52
+ """Add a custom MCP server from HuggingFace space name"""
53
+ logger.info(f"βž• Adding MCP server: {name} from space: {space_name}")
54
+
55
+ if not name or not space_name:
56
+ return "❌ Please provide both server name and space name", ""
57
+
58
+ space_name = space_name.strip()
59
+
60
+ try:
61
+ # Use the improved URL conversion
62
+ mcp_url = self.convert_hf_space_to_url(space_name)
63
+ logger.info(f"πŸ”— Converted {space_name} β†’ {mcp_url}")
64
+
65
+ except ValueError as e:
66
+ return f"❌ {str(e)}", ""
67
+
68
+ config = MCPServerConfig(
69
+ name=name.strip(),
70
+ url=mcp_url,
71
+ description=f"MCP server from HuggingFace space: {space_name}",
72
+ space_id=space_name
73
+ )
74
+
75
+ try:
76
+ # Run async function
77
+ def run_async():
78
+ loop = asyncio.new_event_loop()
79
+ asyncio.set_event_loop(loop)
80
+ try:
81
+ return loop.run_until_complete(self.mcp_client.add_server_async(config))
82
+ finally:
83
+ loop.close()
84
+
85
+ success, message = run_async()
86
+ logger.info(f"Server addition result: {success} - {message}")
87
+
88
+ if success:
89
+ # Format success message for accordion display
90
+ tools_info = ""
91
+ if 'Found' in message and 'tools:' in message:
92
+ tools_section = message.split('Found')[1]
93
+ tools_info = f"**Available Tools:**\n{tools_section}"
94
+
95
+ details_html = f"""
96
+ <details style="margin-top: 10px;">
97
+ <summary style="cursor: pointer; padding: 8px; background: #f0f0f0; border-radius: 4px;"><strong>βœ… {name} - Connection Details</strong></summary>
98
+ <div style="padding: 10px; border-left: 3px solid #28a745; margin-left: 10px; margin-top: 5px;">
99
+ <p><strong>Space:</strong> {space_name}</p>
100
+ <p><strong>Base URL:</strong> {mcp_url}</p>
101
+ <p><strong>Status:</strong> Connected successfully!</p>
102
+ <div style="margin-top: 10px;">
103
+ {tools_info.replace('**', '<strong>').replace('**', '</strong>').replace(chr(10), '<br>')}
104
+ </div>
105
+ </div>
106
+ </details>
107
+ """
108
+ return "βœ… Server added successfully!", details_html
109
+ else:
110
+ error_html = f"""
111
+ <details style="margin-top: 10px;">
112
+ <summary style="cursor: pointer; padding: 8px; background: #f8d7da; border-radius: 4px;"><strong>❌ {name} - Connection Failed</strong></summary>
113
+ <div style="padding: 10px; border-left: 3px solid #dc3545; margin-left: 10px; margin-top: 5px;">
114
+ <p>{message}</p>
115
+ </div>
116
+ </details>
117
+ """
118
+ return f"❌ Failed to add server: {name}", error_html
119
+
120
+ except Exception as e:
121
+ error_msg = f"❌ Failed to add server: {str(e)}"
122
+ logger.error(error_msg)
123
+ logger.error(traceback.format_exc())
124
+ return error_msg, ""
125
+
126
+ def get_server_status(self) -> Tuple[str, str]:
127
+ """Get status of all servers in accordion format"""
128
+ try:
129
+ status = self.mcp_client.get_server_status()
130
+ server_count = f"**Total MCP Servers**: {len(status)}"
131
+
132
+ if not status:
133
+ return server_count, "<p><em>No MCP servers configured yet.</em></p>"
134
+
135
+ accordion_html = ""
136
+
137
+ for name, state in status.items():
138
+ server_config = self.mcp_client.servers[name]
139
+ base_url = server_config.url.replace("/gradio_api/mcp/sse", "")
140
+
141
+ # Determine health status
142
+ health = "🟒 Healthy" if "βœ… Connected" in state else "πŸ”΄ Unhealthy"
143
+
144
+ accordion_html += f"""
145
+ <details style="margin-bottom: 10px;">
146
+ <summary style="cursor: pointer; padding: 8px; background: #e9ecef; border-radius: 4px;"><strong>πŸ”§ {name}</strong></summary>
147
+ <div style="padding: 10px; border-left: 3px solid #007bff; margin-left: 10px; margin-top: 5px;">
148
+ <p><strong>Title:</strong> {name}</p>
149
+ <p><strong>Status:</strong> Connected (MCP Protocol)</p>
150
+ <p><strong>Health:</strong> {health}</p>
151
+ <p><strong>Base URL:</strong> {base_url}</p>
152
+ </div>
153
+ </details>
154
+ """
155
+
156
+ return server_count, accordion_html
157
+
158
+ except Exception as e:
159
+ return "**Total MCP Servers**: 0", f"<p style='color: red;'>❌ Error getting status: {str(e)}</p>"