code-puppy 0.0.134__py3-none-any.whl → 0.0.136__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- code_puppy/agent.py +15 -17
- code_puppy/agents/agent_manager.py +320 -9
- code_puppy/agents/base_agent.py +58 -2
- code_puppy/agents/runtime_manager.py +68 -42
- code_puppy/command_line/command_handler.py +82 -33
- code_puppy/command_line/mcp/__init__.py +10 -0
- code_puppy/command_line/mcp/add_command.py +183 -0
- code_puppy/command_line/mcp/base.py +35 -0
- code_puppy/command_line/mcp/handler.py +133 -0
- code_puppy/command_line/mcp/help_command.py +146 -0
- code_puppy/command_line/mcp/install_command.py +176 -0
- code_puppy/command_line/mcp/list_command.py +94 -0
- code_puppy/command_line/mcp/logs_command.py +126 -0
- code_puppy/command_line/mcp/remove_command.py +82 -0
- code_puppy/command_line/mcp/restart_command.py +92 -0
- code_puppy/command_line/mcp/search_command.py +117 -0
- code_puppy/command_line/mcp/start_all_command.py +126 -0
- code_puppy/command_line/mcp/start_command.py +98 -0
- code_puppy/command_line/mcp/status_command.py +185 -0
- code_puppy/command_line/mcp/stop_all_command.py +109 -0
- code_puppy/command_line/mcp/stop_command.py +79 -0
- code_puppy/command_line/mcp/test_command.py +107 -0
- code_puppy/command_line/mcp/utils.py +129 -0
- code_puppy/command_line/mcp/wizard_utils.py +259 -0
- code_puppy/command_line/model_picker_completion.py +21 -4
- code_puppy/command_line/prompt_toolkit_completion.py +9 -0
- code_puppy/main.py +23 -17
- code_puppy/mcp/__init__.py +42 -16
- code_puppy/mcp/async_lifecycle.py +51 -49
- code_puppy/mcp/blocking_startup.py +125 -113
- code_puppy/mcp/captured_stdio_server.py +63 -70
- code_puppy/mcp/circuit_breaker.py +63 -47
- code_puppy/mcp/config_wizard.py +169 -136
- code_puppy/mcp/dashboard.py +79 -71
- code_puppy/mcp/error_isolation.py +147 -100
- code_puppy/mcp/examples/retry_example.py +55 -42
- code_puppy/mcp/health_monitor.py +152 -141
- code_puppy/mcp/managed_server.py +100 -97
- code_puppy/mcp/manager.py +168 -156
- code_puppy/mcp/registry.py +148 -110
- code_puppy/mcp/retry_manager.py +63 -61
- code_puppy/mcp/server_registry_catalog.py +271 -225
- code_puppy/mcp/status_tracker.py +80 -80
- code_puppy/mcp/system_tools.py +47 -52
- code_puppy/messaging/message_queue.py +20 -13
- code_puppy/messaging/renderers.py +30 -15
- code_puppy/state_management.py +103 -0
- code_puppy/tui/app.py +64 -7
- code_puppy/tui/components/chat_view.py +3 -3
- code_puppy/tui/components/human_input_modal.py +12 -8
- code_puppy/tui/screens/__init__.py +2 -2
- code_puppy/tui/screens/mcp_install_wizard.py +208 -179
- code_puppy/tui/tests/test_agent_command.py +3 -3
- {code_puppy-0.0.134.dist-info → code_puppy-0.0.136.dist-info}/METADATA +1 -1
- {code_puppy-0.0.134.dist-info → code_puppy-0.0.136.dist-info}/RECORD +59 -41
- code_puppy/command_line/mcp_commands.py +0 -1789
- {code_puppy-0.0.134.data → code_puppy-0.0.136.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.134.dist-info → code_puppy-0.0.136.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.134.dist-info → code_puppy-0.0.136.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.134.dist-info → code_puppy-0.0.136.dist-info}/licenses/LICENSE +0 -0
code_puppy/mcp/registry.py
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
"""
|
|
2
2
|
ServerRegistry implementation for managing MCP server configurations.
|
|
3
3
|
|
|
4
|
-
This module provides a registry that tracks all MCP server configurations
|
|
4
|
+
This module provides a registry that tracks all MCP server configurations
|
|
5
5
|
and provides thread-safe CRUD operations with JSON persistence.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
9
|
import logging
|
|
10
|
-
import os
|
|
11
10
|
import threading
|
|
12
11
|
import uuid
|
|
13
12
|
from pathlib import Path
|
|
14
|
-
from typing import Dict, List, Optional
|
|
13
|
+
from typing import Dict, List, Optional
|
|
15
14
|
|
|
16
15
|
from .managed_server import ServerConfig
|
|
17
16
|
|
|
@@ -22,19 +21,19 @@ logger = logging.getLogger(__name__)
|
|
|
22
21
|
class ServerRegistry:
|
|
23
22
|
"""
|
|
24
23
|
Registry for managing MCP server configurations.
|
|
25
|
-
|
|
24
|
+
|
|
26
25
|
Provides CRUD operations for server configurations with thread-safe access,
|
|
27
26
|
validation, and persistent storage to ~/.code_puppy/mcp_registry.json.
|
|
28
|
-
|
|
27
|
+
|
|
29
28
|
All operations are thread-safe and use JSON serialization for ServerConfig objects.
|
|
30
29
|
Handles file not existing gracefully and validates configurations according to
|
|
31
30
|
server type requirements.
|
|
32
31
|
"""
|
|
33
|
-
|
|
32
|
+
|
|
34
33
|
def __init__(self, storage_path: Optional[str] = None):
|
|
35
34
|
"""
|
|
36
35
|
Initialize the server registry.
|
|
37
|
-
|
|
36
|
+
|
|
38
37
|
Args:
|
|
39
38
|
storage_path: Optional custom path for registry storage.
|
|
40
39
|
Defaults to ~/.code_puppy/mcp_registry.json
|
|
@@ -46,28 +45,28 @@ class ServerRegistry:
|
|
|
46
45
|
self._storage_path = code_puppy_dir / "mcp_registry.json"
|
|
47
46
|
else:
|
|
48
47
|
self._storage_path = Path(storage_path)
|
|
49
|
-
|
|
48
|
+
|
|
50
49
|
# Thread safety lock (reentrant)
|
|
51
50
|
self._lock = threading.RLock()
|
|
52
|
-
|
|
51
|
+
|
|
53
52
|
# In-memory storage: server_id -> ServerConfig
|
|
54
53
|
self._servers: Dict[str, ServerConfig] = {}
|
|
55
|
-
|
|
54
|
+
|
|
56
55
|
# Load existing configurations
|
|
57
56
|
self._load()
|
|
58
|
-
|
|
57
|
+
|
|
59
58
|
logger.info(f"Initialized ServerRegistry with storage at {self._storage_path}")
|
|
60
|
-
|
|
59
|
+
|
|
61
60
|
def register(self, config: ServerConfig) -> str:
|
|
62
61
|
"""
|
|
63
62
|
Add new server configuration.
|
|
64
|
-
|
|
63
|
+
|
|
65
64
|
Args:
|
|
66
65
|
config: Server configuration to register
|
|
67
|
-
|
|
66
|
+
|
|
68
67
|
Returns:
|
|
69
68
|
Server ID of the registered server
|
|
70
|
-
|
|
69
|
+
|
|
71
70
|
Raises:
|
|
72
71
|
ValueError: If validation fails or server already exists
|
|
73
72
|
"""
|
|
@@ -76,71 +75,73 @@ class ServerRegistry:
|
|
|
76
75
|
validation_errors = self.validate_config(config)
|
|
77
76
|
if validation_errors:
|
|
78
77
|
raise ValueError(f"Validation failed: {'; '.join(validation_errors)}")
|
|
79
|
-
|
|
78
|
+
|
|
80
79
|
# Generate ID if not provided or ensure uniqueness
|
|
81
80
|
if not config.id:
|
|
82
81
|
config.id = str(uuid.uuid4())
|
|
83
82
|
elif config.id in self._servers:
|
|
84
83
|
raise ValueError(f"Server with ID {config.id} already exists")
|
|
85
|
-
|
|
84
|
+
|
|
86
85
|
# Check name uniqueness
|
|
87
86
|
existing_config = self.get_by_name(config.name)
|
|
88
87
|
if existing_config and existing_config.id != config.id:
|
|
89
88
|
raise ValueError(f"Server with name '{config.name}' already exists")
|
|
90
|
-
|
|
89
|
+
|
|
91
90
|
# Store configuration
|
|
92
91
|
self._servers[config.id] = config
|
|
93
|
-
|
|
92
|
+
|
|
94
93
|
# Persist to disk
|
|
95
94
|
self._persist()
|
|
96
|
-
|
|
95
|
+
|
|
97
96
|
logger.info(f"Registered server: {config.name} (ID: {config.id})")
|
|
98
97
|
return config.id
|
|
99
|
-
|
|
98
|
+
|
|
100
99
|
def unregister(self, server_id: str) -> bool:
|
|
101
100
|
"""
|
|
102
101
|
Remove server configuration.
|
|
103
|
-
|
|
102
|
+
|
|
104
103
|
Args:
|
|
105
104
|
server_id: ID of server to remove
|
|
106
|
-
|
|
105
|
+
|
|
107
106
|
Returns:
|
|
108
107
|
True if server was removed, False if not found
|
|
109
108
|
"""
|
|
110
109
|
with self._lock:
|
|
111
110
|
if server_id not in self._servers:
|
|
112
|
-
logger.warning(
|
|
111
|
+
logger.warning(
|
|
112
|
+
f"Attempted to unregister non-existent server: {server_id}"
|
|
113
|
+
)
|
|
113
114
|
return False
|
|
114
|
-
|
|
115
|
+
|
|
115
116
|
server_name = self._servers[server_id].name
|
|
116
117
|
del self._servers[server_id]
|
|
117
|
-
|
|
118
|
+
|
|
118
119
|
# Persist to disk
|
|
119
120
|
self._persist()
|
|
120
|
-
|
|
121
|
+
|
|
121
122
|
logger.info(f"Unregistered server: {server_name} (ID: {server_id})")
|
|
122
123
|
return True
|
|
123
|
-
|
|
124
|
+
|
|
124
125
|
def get(self, server_id: str) -> Optional[ServerConfig]:
|
|
125
126
|
"""
|
|
126
127
|
Get server configuration by ID.
|
|
127
|
-
|
|
128
|
+
|
|
128
129
|
Args:
|
|
129
130
|
server_id: ID of server to retrieve
|
|
130
|
-
|
|
131
|
+
|
|
131
132
|
Returns:
|
|
132
133
|
ServerConfig if found, None otherwise
|
|
133
134
|
"""
|
|
134
135
|
with self._lock:
|
|
135
136
|
return self._servers.get(server_id)
|
|
136
|
-
|
|
137
|
+
|
|
137
138
|
def get_by_name(self, name: str) -> Optional[ServerConfig]:
|
|
138
139
|
"""
|
|
139
140
|
Get server configuration by name.
|
|
140
|
-
|
|
141
|
+
|
|
141
142
|
Args:
|
|
142
143
|
name: Name of server to retrieve
|
|
143
|
-
|
|
144
|
+
|
|
144
145
|
Returns:
|
|
145
146
|
ServerConfig if found, None otherwise
|
|
146
147
|
"""
|
|
@@ -149,28 +150,28 @@ class ServerRegistry:
|
|
|
149
150
|
if config.name == name:
|
|
150
151
|
return config
|
|
151
152
|
return None
|
|
152
|
-
|
|
153
|
+
|
|
153
154
|
def list_all(self) -> List[ServerConfig]:
|
|
154
155
|
"""
|
|
155
156
|
Get all server configurations.
|
|
156
|
-
|
|
157
|
+
|
|
157
158
|
Returns:
|
|
158
159
|
List of all ServerConfig objects
|
|
159
160
|
"""
|
|
160
161
|
with self._lock:
|
|
161
162
|
return list(self._servers.values())
|
|
162
|
-
|
|
163
|
+
|
|
163
164
|
def update(self, server_id: str, config: ServerConfig) -> bool:
|
|
164
165
|
"""
|
|
165
166
|
Update existing server configuration.
|
|
166
|
-
|
|
167
|
+
|
|
167
168
|
Args:
|
|
168
169
|
server_id: ID of server to update
|
|
169
170
|
config: New configuration
|
|
170
|
-
|
|
171
|
+
|
|
171
172
|
Returns:
|
|
172
173
|
True if update succeeded, False if server not found
|
|
173
|
-
|
|
174
|
+
|
|
174
175
|
Raises:
|
|
175
176
|
ValueError: If validation fails
|
|
176
177
|
"""
|
|
@@ -178,82 +179,96 @@ class ServerRegistry:
|
|
|
178
179
|
if server_id not in self._servers:
|
|
179
180
|
logger.warning(f"Attempted to update non-existent server: {server_id}")
|
|
180
181
|
return False
|
|
181
|
-
|
|
182
|
+
|
|
182
183
|
# Ensure the ID matches
|
|
183
184
|
config.id = server_id
|
|
184
|
-
|
|
185
|
+
|
|
185
186
|
# Validate configuration
|
|
186
187
|
validation_errors = self.validate_config(config)
|
|
187
188
|
if validation_errors:
|
|
188
189
|
raise ValueError(f"Validation failed: {'; '.join(validation_errors)}")
|
|
189
|
-
|
|
190
|
+
|
|
190
191
|
# Check name uniqueness (excluding current server)
|
|
191
192
|
existing_config = self.get_by_name(config.name)
|
|
192
193
|
if existing_config and existing_config.id != server_id:
|
|
193
194
|
raise ValueError(f"Server with name '{config.name}' already exists")
|
|
194
|
-
|
|
195
|
+
|
|
195
196
|
# Update configuration
|
|
196
197
|
old_name = self._servers[server_id].name
|
|
197
198
|
self._servers[server_id] = config
|
|
198
|
-
|
|
199
|
+
|
|
199
200
|
# Persist to disk
|
|
200
201
|
self._persist()
|
|
201
|
-
|
|
202
|
-
logger.info(
|
|
202
|
+
|
|
203
|
+
logger.info(
|
|
204
|
+
f"Updated server: {old_name} -> {config.name} (ID: {server_id})"
|
|
205
|
+
)
|
|
203
206
|
return True
|
|
204
|
-
|
|
207
|
+
|
|
205
208
|
def exists(self, server_id: str) -> bool:
|
|
206
209
|
"""
|
|
207
210
|
Check if server exists.
|
|
208
|
-
|
|
211
|
+
|
|
209
212
|
Args:
|
|
210
213
|
server_id: ID of server to check
|
|
211
|
-
|
|
214
|
+
|
|
212
215
|
Returns:
|
|
213
216
|
True if server exists, False otherwise
|
|
214
217
|
"""
|
|
215
218
|
with self._lock:
|
|
216
219
|
return server_id in self._servers
|
|
217
|
-
|
|
220
|
+
|
|
218
221
|
def validate_config(self, config: ServerConfig) -> List[str]:
|
|
219
222
|
"""
|
|
220
223
|
Validate server configuration.
|
|
221
|
-
|
|
224
|
+
|
|
222
225
|
Args:
|
|
223
226
|
config: Configuration to validate
|
|
224
|
-
|
|
227
|
+
|
|
225
228
|
Returns:
|
|
226
229
|
List of validation error messages (empty if valid)
|
|
227
230
|
"""
|
|
228
231
|
errors = []
|
|
229
|
-
|
|
232
|
+
|
|
230
233
|
# Basic validation
|
|
231
234
|
if not config.name or not config.name.strip():
|
|
232
235
|
errors.append("Server name is required")
|
|
233
|
-
elif not config.name.replace(
|
|
234
|
-
errors.append(
|
|
235
|
-
|
|
236
|
+
elif not config.name.replace("-", "").replace("_", "").isalnum():
|
|
237
|
+
errors.append(
|
|
238
|
+
"Server name must be alphanumeric (hyphens and underscores allowed)"
|
|
239
|
+
)
|
|
240
|
+
|
|
236
241
|
if not config.type:
|
|
237
242
|
errors.append("Server type is required")
|
|
238
243
|
elif config.type.lower() not in ["sse", "stdio", "http"]:
|
|
239
244
|
errors.append("Server type must be one of: sse, stdio, http")
|
|
240
|
-
|
|
245
|
+
|
|
241
246
|
if not isinstance(config.config, dict):
|
|
242
247
|
errors.append("Server config must be a dictionary")
|
|
243
248
|
return errors # Can't validate further without valid config dict
|
|
244
|
-
|
|
249
|
+
|
|
245
250
|
# Type-specific validation
|
|
246
251
|
server_type = config.type.lower()
|
|
247
252
|
server_config = config.config
|
|
248
|
-
|
|
253
|
+
|
|
249
254
|
if server_type in ["sse", "http"]:
|
|
250
255
|
if "url" not in server_config:
|
|
251
256
|
errors.append(f"{server_type.upper()} server requires 'url' in config")
|
|
252
|
-
elif
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
+
elif (
|
|
258
|
+
not isinstance(server_config["url"], str)
|
|
259
|
+
or not server_config["url"].strip()
|
|
260
|
+
):
|
|
261
|
+
errors.append(
|
|
262
|
+
f"{server_type.upper()} server URL must be a non-empty string"
|
|
263
|
+
)
|
|
264
|
+
elif not (
|
|
265
|
+
server_config["url"].startswith("http://")
|
|
266
|
+
or server_config["url"].startswith("https://")
|
|
267
|
+
):
|
|
268
|
+
errors.append(
|
|
269
|
+
f"{server_type.upper()} server URL must start with http:// or https://"
|
|
270
|
+
)
|
|
271
|
+
|
|
257
272
|
# Optional parameter validation
|
|
258
273
|
if "timeout" in server_config:
|
|
259
274
|
try:
|
|
@@ -262,7 +277,7 @@ class ServerRegistry:
|
|
|
262
277
|
errors.append("Timeout must be positive")
|
|
263
278
|
except (ValueError, TypeError):
|
|
264
279
|
errors.append("Timeout must be a number")
|
|
265
|
-
|
|
280
|
+
|
|
266
281
|
if "read_timeout" in server_config:
|
|
267
282
|
try:
|
|
268
283
|
read_timeout = float(server_config["read_timeout"])
|
|
@@ -270,17 +285,20 @@ class ServerRegistry:
|
|
|
270
285
|
errors.append("Read timeout must be positive")
|
|
271
286
|
except (ValueError, TypeError):
|
|
272
287
|
errors.append("Read timeout must be a number")
|
|
273
|
-
|
|
288
|
+
|
|
274
289
|
if "headers" in server_config:
|
|
275
290
|
if not isinstance(server_config["headers"], dict):
|
|
276
291
|
errors.append("Headers must be a dictionary")
|
|
277
|
-
|
|
292
|
+
|
|
278
293
|
elif server_type == "stdio":
|
|
279
294
|
if "command" not in server_config:
|
|
280
295
|
errors.append("Stdio server requires 'command' in config")
|
|
281
|
-
elif
|
|
296
|
+
elif (
|
|
297
|
+
not isinstance(server_config["command"], str)
|
|
298
|
+
or not server_config["command"].strip()
|
|
299
|
+
):
|
|
282
300
|
errors.append("Stdio server command must be a non-empty string")
|
|
283
|
-
|
|
301
|
+
|
|
284
302
|
# Optional parameter validation
|
|
285
303
|
if "args" in server_config:
|
|
286
304
|
args = server_config["args"]
|
|
@@ -289,26 +307,28 @@ class ServerRegistry:
|
|
|
289
307
|
elif isinstance(args, list):
|
|
290
308
|
if not all(isinstance(arg, str) for arg in args):
|
|
291
309
|
errors.append("All args must be strings")
|
|
292
|
-
|
|
310
|
+
|
|
293
311
|
if "env" in server_config:
|
|
294
312
|
if not isinstance(server_config["env"], dict):
|
|
295
313
|
errors.append("Environment variables must be a dictionary")
|
|
296
|
-
elif not all(
|
|
297
|
-
|
|
314
|
+
elif not all(
|
|
315
|
+
isinstance(k, str) and isinstance(v, str)
|
|
316
|
+
for k, v in server_config["env"].items()
|
|
317
|
+
):
|
|
298
318
|
errors.append("All environment variables must be strings")
|
|
299
|
-
|
|
319
|
+
|
|
300
320
|
if "cwd" in server_config:
|
|
301
321
|
if not isinstance(server_config["cwd"], str):
|
|
302
322
|
errors.append("Working directory must be a string")
|
|
303
|
-
|
|
323
|
+
|
|
304
324
|
return errors
|
|
305
|
-
|
|
325
|
+
|
|
306
326
|
def _persist(self) -> None:
|
|
307
327
|
"""
|
|
308
328
|
Save registry to disk.
|
|
309
|
-
|
|
329
|
+
|
|
310
330
|
This method assumes it's called within a lock context.
|
|
311
|
-
|
|
331
|
+
|
|
312
332
|
Raises:
|
|
313
333
|
Exception: If unable to write to storage file
|
|
314
334
|
"""
|
|
@@ -321,92 +341,110 @@ class ServerRegistry:
|
|
|
321
341
|
"name": config.name,
|
|
322
342
|
"type": config.type,
|
|
323
343
|
"enabled": config.enabled,
|
|
324
|
-
"config": config.config
|
|
344
|
+
"config": config.config,
|
|
325
345
|
}
|
|
326
|
-
|
|
346
|
+
|
|
327
347
|
# Ensure directory exists
|
|
328
348
|
self._storage_path.parent.mkdir(parents=True, exist_ok=True)
|
|
329
|
-
|
|
349
|
+
|
|
330
350
|
# Write to temporary file first, then rename (atomic operation)
|
|
331
|
-
temp_path = self._storage_path.with_suffix(
|
|
332
|
-
with open(temp_path,
|
|
351
|
+
temp_path = self._storage_path.with_suffix(".tmp")
|
|
352
|
+
with open(temp_path, "w", encoding="utf-8") as f:
|
|
333
353
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
334
|
-
|
|
354
|
+
|
|
335
355
|
# Atomic rename
|
|
336
356
|
temp_path.replace(self._storage_path)
|
|
337
|
-
|
|
338
|
-
logger.debug(
|
|
339
|
-
|
|
357
|
+
|
|
358
|
+
logger.debug(
|
|
359
|
+
f"Persisted {len(self._servers)} server configurations to {self._storage_path}"
|
|
360
|
+
)
|
|
361
|
+
|
|
340
362
|
except Exception as e:
|
|
341
363
|
logger.error(f"Failed to persist server registry: {e}")
|
|
342
364
|
raise
|
|
343
|
-
|
|
365
|
+
|
|
344
366
|
def _load(self) -> None:
|
|
345
367
|
"""
|
|
346
368
|
Load registry from disk.
|
|
347
|
-
|
|
369
|
+
|
|
348
370
|
Handles file not existing gracefully by starting with empty registry.
|
|
349
371
|
Invalid entries are logged and skipped.
|
|
350
372
|
"""
|
|
351
373
|
try:
|
|
352
374
|
if not self._storage_path.exists():
|
|
353
|
-
logger.info(
|
|
375
|
+
logger.info(
|
|
376
|
+
f"Registry file {self._storage_path} does not exist, starting with empty registry"
|
|
377
|
+
)
|
|
354
378
|
return
|
|
355
|
-
|
|
379
|
+
|
|
356
380
|
# Check if file is empty
|
|
357
381
|
if self._storage_path.stat().st_size == 0:
|
|
358
|
-
logger.info(
|
|
382
|
+
logger.info(
|
|
383
|
+
f"Registry file {self._storage_path} is empty, starting with empty registry"
|
|
384
|
+
)
|
|
359
385
|
return
|
|
360
|
-
|
|
361
|
-
with open(self._storage_path,
|
|
386
|
+
|
|
387
|
+
with open(self._storage_path, "r", encoding="utf-8") as f:
|
|
362
388
|
data = json.load(f)
|
|
363
|
-
|
|
389
|
+
|
|
364
390
|
if not isinstance(data, dict):
|
|
365
|
-
logger.warning(
|
|
391
|
+
logger.warning(
|
|
392
|
+
f"Invalid registry format in {self._storage_path}, starting with empty registry"
|
|
393
|
+
)
|
|
366
394
|
return
|
|
367
|
-
|
|
395
|
+
|
|
368
396
|
# Load server configurations
|
|
369
397
|
loaded_count = 0
|
|
370
398
|
for server_id, config_data in data.items():
|
|
371
399
|
try:
|
|
372
400
|
# Validate the structure
|
|
373
401
|
if not isinstance(config_data, dict):
|
|
374
|
-
logger.warning(
|
|
402
|
+
logger.warning(
|
|
403
|
+
f"Skipping invalid config for server {server_id}: not a dictionary"
|
|
404
|
+
)
|
|
375
405
|
continue
|
|
376
|
-
|
|
406
|
+
|
|
377
407
|
required_fields = ["id", "name", "type", "config"]
|
|
378
408
|
if not all(field in config_data for field in required_fields):
|
|
379
|
-
logger.warning(
|
|
409
|
+
logger.warning(
|
|
410
|
+
f"Skipping incomplete config for server {server_id}: missing required fields"
|
|
411
|
+
)
|
|
380
412
|
continue
|
|
381
|
-
|
|
413
|
+
|
|
382
414
|
# Create ServerConfig object
|
|
383
415
|
config = ServerConfig(
|
|
384
416
|
id=config_data["id"],
|
|
385
417
|
name=config_data["name"],
|
|
386
418
|
type=config_data["type"],
|
|
387
419
|
enabled=config_data.get("enabled", True),
|
|
388
|
-
config=config_data["config"]
|
|
420
|
+
config=config_data["config"],
|
|
389
421
|
)
|
|
390
|
-
|
|
422
|
+
|
|
391
423
|
# Basic validation
|
|
392
424
|
validation_errors = self.validate_config(config)
|
|
393
425
|
if validation_errors:
|
|
394
|
-
logger.warning(
|
|
426
|
+
logger.warning(
|
|
427
|
+
f"Skipping invalid config for server {server_id}: {'; '.join(validation_errors)}"
|
|
428
|
+
)
|
|
395
429
|
continue
|
|
396
|
-
|
|
430
|
+
|
|
397
431
|
# Store configuration
|
|
398
432
|
self._servers[server_id] = config
|
|
399
433
|
loaded_count += 1
|
|
400
|
-
|
|
434
|
+
|
|
401
435
|
except Exception as e:
|
|
402
|
-
logger.warning(
|
|
436
|
+
logger.warning(
|
|
437
|
+
f"Skipping invalid config for server {server_id}: {e}"
|
|
438
|
+
)
|
|
403
439
|
continue
|
|
404
|
-
|
|
405
|
-
logger.info(
|
|
406
|
-
|
|
440
|
+
|
|
441
|
+
logger.info(
|
|
442
|
+
f"Loaded {loaded_count} server configurations from {self._storage_path}"
|
|
443
|
+
)
|
|
444
|
+
|
|
407
445
|
except json.JSONDecodeError as e:
|
|
408
446
|
logger.error(f"Invalid JSON in registry file {self._storage_path}: {e}")
|
|
409
447
|
logger.info("Starting with empty registry")
|
|
410
448
|
except Exception as e:
|
|
411
449
|
logger.error(f"Failed to load server registry: {e}")
|
|
412
|
-
logger.info("Starting with empty registry")
|
|
450
|
+
logger.info("Starting with empty registry")
|